Подтвердить что ты не робот

Как я могу форматировать десятичную привязку к TextBox, не зная моих пользователей?

Я пытаюсь отобразить отформатированное десятичное число в TextBox, используя привязку данных в WPF.

Цели

Цель 1: при установке десятичного значения в коде отобразите 2 десятичных знака в текстовом поле.

Цель 2: Когда пользователь взаимодействует с (вводит) в TextBox, не срывает его/ее.

Цель 3: привязки должны обновлять источник в PropertyChanged.

Попытки

Попытка 1: Без форматирования.

Здесь мы начинаем почти с нуля.

<TextBox Text="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" />

Нарушает цель 1. SomeDecimal = 4.5 отобразит "4.50000" в текстовом поле.

Попытка 2: Использовать StringFormat в привязке.

<TextBox Text="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged, StringFormat=F2}" />

Нарушает цель 2. Скажите SomeDecimal значение 2.5, а TextBox отображает "2.50". Если мы выберем все и наберем "13.5", мы получим "13.5.00" в TextBox, потому что форматирование "полезно" вставляет десятичные знаки и нули.

Попытка 3: использовать расширенный набор инструментов WPF MaskedTextBox.

http://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox

Предполагая, что я правильно читаю документацию, маска ## 0.00 означает "две необязательные цифры, за которой следует требуемая цифра, десятичная точка и еще две требуемые цифры". Это заставляет меня сказать "максимально возможное число, может войти в этот TextBox 999.99", но пусть говорят, что я в порядке с этим.

<xctk:MaskedTextBox Value="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" Mask="##0.00" />

Нарушает цель 2. TextBox начинается с ___.__ и выбирает его и набирает 5.75 уроков 575.__. Единственный способ получить 5.75 - выбрать TextBox и набрать <space><space>5.75.

Попытка 4: использовать расширенный WPF Toolkit DecimalUpDown spinner.

http://wpftoolkit.codeplex.com/wikipage?title=DecimalUpDown

<xctk:DecimalUpDown Value="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" FormatString="F2" />

Нарушает цель 3. DecimalUpDown с радостью игнорирует UpdateSourceTrigger = PropertyChanged. Один из координаторов на странице расширенного WPF Toolkit Codeplex предлагает модифицированный контрольный шаблон в http://wpftoolkit.codeplex.com/discussions/352551/. Это удовлетворяет цели 3, но нарушает цель 2, проявляя то же поведение, что и в попытке 2.

Попытка 5: использование стилей datatriggers, используйте только форматирование, если пользователь не редактирует.

Связывание с двойным с StringFormat в TextBox

Даже если бы этот был удовлетворен всем трем целям, я бы не хотел его использовать. (A) Текстовые поля становятся 12 строк каждый вместо 1, а мое приложение содержит много и много текстовых полей. (B) Все мои текстовые поля уже имеют атрибут Style, который указывает на глобальный StaticResource, который устанавливает Margin, Height и другие вещи. (C) Возможно, вы заметили, что приведенный ниже код устанавливает путь привязки дважды, что нарушает принципал DRY.

<TextBox>
    <TextBox.Style>
        <Style TargetType="{x:Type TextBox}">
            <Setter Property="Text" Value="{Binding Path=SomeDecimal, StringFormat=F2}" />
            <Style.Triggers>
                <Trigger Property="IsFocused" Value="True">
                    <Setter Property="Text" Value="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </TextBox.Style>
</TextBox>

Все эти неудобные вещи в стороне...

Нарушает цель 2. Сначала щелчок на TextBox, который отображает "13.50", внезапно отображает "13.5000", что неожиданно. Во-вторых, если начать с пустого TextBox, и я набираю "13.50", TextBox будет содержать "1350". Я не могу объяснить, почему, но нажатие клавиши периода не вставляет десятичные знаки, если курсор находится в правом конце строки в TextBox. Если TextBox содержит строку с длиной > 0, и я перемещаю курсор в любую точку, кроме правого конца строки, тогда я могу вставить десятичные точки.

Попытка 6: сделайте это сама.

Я собираюсь приступить к jouney боли и страдания, либо подклассифицируя TextBox, либо создав прикрепленное свойство, и сам написав код форматирования. Он будет наполнен струнными манипуляциями и вызовет существенную потерю волос.


Есть ли у кого-нибудь предложения по форматированию десятичных знаков, привязанных к текстовым полям, которые удовлетворяют всем вышеперечисленным целям?

4b9b3361

Ответ 1

Попробуйте разрешить это на уровне ViewModel. Что это:

public class FormattedDecimalViewModel : INotifyPropertyChanged
    {
        private readonly string _format;

        public FormattedDecimalViewModel()
            : this("F2")
        {

        }

        public FormattedDecimalViewModel(string format)
        {
            _format = format;
        }

        private string _someDecimalAsString;
        // String value that will be displayed on the view.
        // Bind this property to your control
        public string SomeDecimalAsString
        {
            get
            {
                return _someDecimalAsString;
            }
            set
            {
                _someDecimalAsString = value;
                RaisePropertyChanged("SomeDecimalAsString");
                RaisePropertyChanged("SomeDecimal");
            }
        }

        // Converts user input to decimal or initializes view model
        public decimal SomeDecimal
        {
            get
            {
                return decimal.Parse(_someDecimalAsString);
            }
            set
            {
                SomeDecimalAsString = value.ToString(_format);
            }
        }

        // Applies format forcibly
        public void ApplyFormat()
        {
            SomeDecimalAsString = SomeDecimal.ToString(_format);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

ОБРАЗЕЦ

Xaml:

<TextBox x:Name="tb" Text="{Binding Path=SomeDecimalAsString, UpdateSourceTrigger=PropertyChanged}" />

Код позади:

public MainWindow()
{
    InitializeComponent();
    FormattedDecimalViewModel formattedDecimalViewModel = new FormattedDecimalViewModel { SomeDecimal = (decimal)2.50 };
    tb.LostFocus += (s, e) => formattedDecimalViewModel.ApplyFormat(); // when user finishes to type, will apply formatting
    DataContext = formattedDecimalViewModel;
}

Ответ 2

Я создал следующее пользовательское поведение для перемещения курсора пользователя после десятичной точки при использовании StringFormat={}{0:0.00}, что вынуждает десятичное место присутствовать, однако это может вызвать следующую проблему:

Нарушает цель 2. Скажите SomeDecimal значение 2.5, а TextBox отображает "2.50". Если мы выберем все и наберем "13.5", мы получим "13.5.00" в TextBox, потому что форматирование "полезно" вставляет десятичные знаки и нули.

Я взломал это с помощью пользовательского поведения, которое будет перемещать курсор пользователя после десятичной точки, когда они нажимают кнопку. Ключ:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace GUI.Helpers.Behaviors
{
    public class DecimalPlaceHotkeyBehavior : Behavior<TextBox>
    {
        #region Methods
        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.PreviewKeyDown += AssociatedObject_PreviewKeyDown;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.PreviewKeyDown -= AssociatedObject_PreviewKeyDown;
        }

        protected override Freezable CreateInstanceCore()
        {
            return new DecimalPlaceHotkeyBehavior();
        }
        #endregion

        #region Event Methods
        private void AssociatedObject_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if (e.Key == System.Windows.Input.Key.OemPeriod || e.Key == System.Windows.Input.Key.Decimal)
            {
                var periodIndex = AssociatedObject.Text.IndexOf('.');
                if (periodIndex != -1)
                {
                    AssociatedObject.CaretIndex = (periodIndex + 1);
                    e.Handled = true;
                }
            }
        }
        #endregion

        #region Initialization
        public DecimalPlaceHotkeyBehavior()
            : base()
        {
        }
        #endregion
    }
}

Я использую его следующим образом:

<TextBox xmlns:Behaviors="clr-namespace:GUI.Helpers.Behaviors" 
         xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
         Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, StringFormat={}{0:0.00}}">
        <i:Interaction.Behaviors>
            <Behaviors:DecimalPlaceHotkeyBehavior></Behaviors:DecimalPlaceHotkeyBehavior>
        </i:Interaction.Behaviors>
</TextBox>