В WPF текстовое поле для ввода десятичных значений
есть ли достойный способ получить элемент управления WPF, который привязан к десятичному значению?
когда я просто привязываю TextBox или DataGridTextColumn к десятичной дроби, ввод данных сосет большое время.
<TextBox Text="{Binding MyDecimal, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
когда я попытаюсь ввести " 0,5 "в этом текстовом поле, я получу" 5 " в результате. Почти невозможно ввести " 0,5 "вообще (кроме ввода 1,5 и замены" 1 "на"0").
когда я использую ввод данных StringFormat, все еще сосет маленький время:
<TextBox Text="{Binding MyDecimal, StringFormat=F1, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
теперь, когда я пытаюсь ввести "0,5", я закончу с" 0,5,0 ",что все еще неправильно, но, по крайней мере, я могу удалить трейлинг", 0 " без особых проблем.
тем не менее, ввод десятичных знаков с помощью WPF сосет большое время, потому что это поля ввода очень склонны к ошибкам ввода данных, что является реальной болью, особенно для значений!
Итак, что я должен использовать для десятичного ввода данных в wpf? Или Microsoft не поддерживает decimal Дейта??
9 ответов
в настоящее время я использую это поведение для цифрового и десятичного ввода:
public class TextBoxInputBehavior : Behavior<TextBox>
{
const NumberStyles validNumberStyles = NumberStyles.AllowDecimalPoint |
NumberStyles.AllowThousands |
NumberStyles.AllowLeadingSign;
public TextBoxInputBehavior()
{
this.InputMode = TextBoxInputMode.None;
this.JustPositivDecimalInput = false;
}
public TextBoxInputMode InputMode { get; set; }
public static readonly DependencyProperty JustPositivDecimalInputProperty =
DependencyProperty.Register("JustPositivDecimalInput", typeof(bool),
typeof(TextBoxInputBehavior), new FrameworkPropertyMetadata(false));
public bool JustPositivDecimalInput
{
get { return (bool)GetValue(JustPositivDecimalInputProperty); }
set { SetValue(JustPositivDecimalInputProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewTextInput += AssociatedObjectPreviewTextInput;
AssociatedObject.PreviewKeyDown += AssociatedObjectPreviewKeyDown;
DataObject.AddPastingHandler(AssociatedObject, Pasting);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PreviewTextInput -= AssociatedObjectPreviewTextInput;
AssociatedObject.PreviewKeyDown -= AssociatedObjectPreviewKeyDown;
DataObject.RemovePastingHandler(AssociatedObject, Pasting);
}
private void Pasting(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(typeof(string)))
{
var pastedText = (string)e.DataObject.GetData(typeof(string));
if (!this.IsValidInput(this.GetText(pastedText)))
{
System.Media.SystemSounds.Beep.Play();
e.CancelCommand();
}
}
else
{
System.Media.SystemSounds.Beep.Play();
e.CancelCommand();
}
}
private void AssociatedObjectPreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
{
if (!this.IsValidInput(this.GetText(" ")))
{
System.Media.SystemSounds.Beep.Play();
e.Handled = true;
}
}
}
private void AssociatedObjectPreviewTextInput(object sender, TextCompositionEventArgs e)
{
if (!this.IsValidInput(this.GetText(e.Text)))
{
System.Media.SystemSounds.Beep.Play();
e.Handled = true;
}
}
private string GetText(string input)
{
var txt = this.AssociatedObject;
int selectionStart = txt.SelectionStart;
if (txt.Text.Length < selectionStart)
selectionStart = txt.Text.Length;
int selectionLength = txt.SelectionLength;
if (txt.Text.Length < selectionStart + selectionLength)
selectionLength = txt.Text.Length - selectionStart;
var realtext = txt.Text.Remove(selectionStart, selectionLength);
int caretIndex = txt.CaretIndex;
if (realtext.Length < caretIndex)
caretIndex = realtext.Length;
var newtext = realtext.Insert(caretIndex, input);
return newtext;
}
private bool IsValidInput(string input)
{
switch (InputMode)
{
case TextBoxInputMode.None:
return true;
case TextBoxInputMode.DigitInput:
return CheckIsDigit(input);
case TextBoxInputMode.DecimalInput:
decimal d;
//wen mehr als ein Komma
if (input.ToCharArray().Where(x => x == ',').Count() > 1)
return false;
if (input.Contains("-"))
{
if (this.JustPositivDecimalInput)
return false;
if (input.IndexOf("-",StringComparison.Ordinal) > 0)
return false;
if(input.ToCharArray().Count(x=>x=='-') > 1)
return false;
//minus einmal am anfang zulässig
if (input.Length == 1)
return true;
}
var result = decimal.TryParse(input, validNumberStyles, CultureInfo.CurrentCulture, out d);
return result;
default: throw new ArgumentException("Unknown TextBoxInputMode");
}
return true;
}
private bool CheckIsDigit(string wert)
{
return wert.ToCharArray().All(Char.IsDigit);
}
}
public enum TextBoxInputMode
{
None,
DecimalInput,
DigitInput
}
использование XAML выглядит следующим образом:
<TextBox Text="{Binding Sum}">
<i:Interaction.Behaviors>
<Behaviors:TextBoxInputBehavior InputMode="DecimalInput"/>
</i:Interaction.Behaviors>
</TextBox>
на расширенный инструментарий WPF есть DecimalUpDown контроль, который может удовлетворить ваши потребности. Это свободно использовать, и лучше использовать это, чем пытаться свернуть свой собственный.
Что касается проверки ввода на нем, существует несколько способов применения проверки,вот один подробно в MSDN. Я!--10-->деталь другой подход для пользовательской проверки привязки в двух сообщениях в моем блоге (вы бы применили проверку к Value
привязка свойств элемента управления DecimalUpDown).
private void DecimalTextBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
bool approvedDecimalPoint = false;
if (e.Text == ".")
{
if (!((TextBox)sender).Text.Contains("."))
approvedDecimalPoint = true;
}
if (!(char.IsDigit(e.Text, e.Text.Length - 1) || approvedDecimalPoint))
e.Handled = true;
}
Я также наткнулся на этот вопрос; с UpdateSourceTrigger=PropertyChanged
Кажется, что привязка пытается обновить текст по мере его ввода. Чтобы устранить эту проблему, мы изменили наши поля ввода, чтобы UpdateSourceTrigger=LostFocus
, например:
<TextBox Text="{Binding MyDecimal, UpdateSourceTrigger=LostFocus, ValidatesOnDataErrors=True, StringFormat=n1}" />
вы можете определить свои собственные ошибки проверки с помощью IDataErrorInfo
интерфейс. Вам просто нужно добавить следующее к вашей модели поддержки:
public class MyModel : IDataErrorInfo
{
/* my properties */
public string Error { get { return null; } }
public string this[string name]
{
get
{
switch (name)
{
case "MyDecimal":
return NumberHelper.IsValidValue(MyDecimal) ? message : null;
default: return null;
}
}
}
private string message = "Invalid value";
}
я реализовал свой собственный TextBox. Он обновляет Источник, когда в тексте есть номер, иначе нет. Потеряв фокус, я прочитал свойство source. Все, что вам нужно сделать, это заменить текстовое поле этим классом и привязать свойство "Number", которое имеет тип double.
public class DoubleTextBox: TextBox
{
public DoubleTextBox()
{
TextChanged += DoubleTextBox_TextChanged;
LostFocus += DoubleTextBox_LostFocus;
}
void DoubleTextBox_LostFocus(object sender, System.Windows.RoutedEventArgs e)
{
Text = Number.ToString("N2");
}
void DoubleTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
double zahl;
if (string.IsNullOrWhiteSpace(Text))
{
Number = 0;
}
else if (double.TryParse(Text, out zahl))
{
Number = Double.Parse(zahl.ToString("N2"));
}
else
{
ValidationError validationError =
new ValidationError(new ExceptionValidationRule(), GetBindingExpression(NumberProperty));
validationError.ErrorContent = "Keine gültige Zahl";
Validation.MarkInvalid(
GetBindingExpression(NumberProperty),
validationError);
}
}
public double Number
{
get { return (double)this.GetValue(NumberProperty); }
set { this.SetValue(NumberProperty, value); }
}
public static readonly DependencyProperty NumberProperty = DependencyProperty.Register(
"Number", typeof(double), typeof(DoubleTextBox),
new FrameworkPropertyMetadata
(
0d,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault
)
);
}
Im новый, поэтому я не могу прокомментировать его ответ, но я исправил проблемы с отрицательным числом в blindmeis'ы.
просто измените тег
if (input.Contains("-"))
на IsValidInput () to...
if (input.Contains("-"))
{
if (this.JustPositivDecimalInput)
return false;
//minus einmal am anfang zulässig
//minus once at the beginning
if (input.IndexOf("-", StringComparison.Ordinal) == 0 && input.ToCharArray().Count(x => x == '-') == 1)
{
if(input.Length == 1)
{
//INPUT IS "-"
return true;
}
else if (input.Length == 2)
{
//VALIDATE NEGATIVE DECIMALS...INPUT IS "-."
if (input.IndexOf(".", StringComparison.Ordinal) == 1)
{
return true;
}
}
else
{
return decimal.TryParse(input, validNumberStyles, CultureInfo.CurrentCulture, out d);
}
}
}
Если вы хотите, чтобы текстовое поле разрешало только decimal, напишите событие previewinputtext для этого текстового поля. тогда в этом случае напишите этот код
decimal result;
e.Handled=!decimal.TryParse((sender as TextBox).Text + e.Text, out result)
это регулярное выражение работает
private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
Regex regex = new Regex("^[.][0-9]+$|^[0-9]*[.]{0,1}[0-9]*$");
e.Handled = !regex.IsMatch((sender as TextBox).Text.Insert((sender as TextBox).SelectionStart,e.Text));
}
Это позволит вводить только десятичные дроби в текстовое поле и ничего больше.
viewmodel выглядит так:
private string _decimalVal = "0";
public string decimalVal
{
get { return _decimalVal.ToString(); }
set
{
if (string.IsNullOrEmpty(value) || value == "-")
SetProperty(ref _decimalVal, value);
else if (Decimal.TryParse(value, out decimal newVal))
{
if (newVal == 0)
value = "0";
SetProperty(ref _decimalVal, value = (value.Contains(".")) ? Convert.ToDecimal(value).ToString("0.00") : value);
}
}
}
использование XAML выглядит следующим образом:
<TextBox Text="{Binding decimalVal,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />