可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Is there any decent way to get a WPF control which is bound to a decimal value?
When I just bind the TextBox or DataGridTextColumn to a decimal, data entry sucks big time.
<TextBox Text="{Binding MyDecimal, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
When I try to enter "0,5" in this TextBox I'll get "5" as a result. It is nearly impossible to enter "0,5" at all (Apart from entering 1,5 and replacing the "1" with a "0").
When I use StringFormat data entry still sucks small time:
<TextBox Text="{Binding MyDecimal, StringFormat=F1, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
Now when I try and enter "0,5" I'll end up with "0,5,0", which still is wrong but at least I can remove the trailing ",0" without much Problems.
Still, entering decimals using WPF sucks big time because this entry fields are very prone to data entry errors, which is a real pain especially for values!
So what am I supposed to use for decimal data entry in wpf? Or does Microsoft not support decimal data??
回答1:
I currently use this behavior for digital and decimal input:
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
}
The XAML usage looks like this:
<TextBox Text="{Binding Sum}">
<i:Interaction.Behaviors>
<Behaviors:TextBoxInputBehavior InputMode="DecimalInput"/>
</i:Interaction.Behaviors>
</TextBox>
回答2:
The WPF Extended toolkit has a DecimalUpDown control that may suit your needs. It's free to use, and it's better to use this than to try and roll your own.
As for validating the input on it, there are a number of ways of applying validation, here is one detailed in MSDN. I detail another approach for custom bindable validation in two posts on my blog (you would apply the validation to the Value
property binding on the DecimalUpDown control).
回答3:
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;
}
回答4:
I also came across this issue; with UpdateSourceTrigger=PropertyChanged
it seems that the binding tries to update the text as you are typing it. To fix this issue we changed our input fields so that UpdateSourceTrigger=LostFocus
, e.g.:
<TextBox Text="{Binding MyDecimal, UpdateSourceTrigger=LostFocus, ValidatesOnDataErrors=True, StringFormat=n1}" />
You can define your own validation errors by using the IDataErrorInfo
interface. You just need to add the following to your backing model:
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";
}
回答5:
I implemented my own TextBox. It updates the source, when there is a number in the text, otherwise not. On lost Focus, I read the source property. All you have to do is replace the TextBox with this class and bind the "Number" Property which is of type 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
)
);
}
回答6:
This will allow only decimals to be entered into the textbox and nothing else.
The viewmodel looks like this:
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);
}
}
}
The XAML usage looks like this:
<TextBox Text="{Binding decimalVal,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
回答7:
Im new, so I cant comment his answer, but I fixed the negative number issues in blindmeis's code.
Just modify the
if (input.Contains("-"))
section of 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);
}
}
}
回答8:
if you want the textbox to only allow decimal then write previewinputtext event for that textbox.
then in that event write this code
decimal result;
e.Handled=!decimal.TryParse((sender as TextBox).Text + e.Text, out result)
回答9:
This regex works
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));
}