有没有合适的方法来获得一个绑定到十进制值的WPF控件?
当我将TextBox或DataGridTextColumn绑定到小数时,数据输入会耗费大量时间。
<TextBox Text="{Binding MyDecimal, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
当我尝试在此TextBox中输入“0,5”时,我会得到“5”。根本不可能输入“0,5”(除了输入1,5并用“0”替换“1”)。
当我使用StringFormat时,数据输入仍然很少:
<TextBox Text="{Binding MyDecimal, StringFormat=F1, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
现在,当我尝试输入“0,5”时,我最终会得到“0,5,0”,这仍然是错误的,但至少我可以删除尾随的“0”而没有太多问题。
尽管如此,使用WPF输入小数会吸引大量时间,因为此输入字段非常容易出现数据输入错误,这对于值来说真的很痛苦!
那么我应该在wpf中用于十进制数据输入?或者Microsoft不支持十进制数据?
答案 0 :(得分:18)
我目前使用此行为进行数字和十进制输入:
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>
答案 1 :(得分:7)
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;
}
答案 2 :(得分:5)
WPF Extended toolkit有DecimalUpDown控件,可能符合您的需求。它可以免费使用,最好使用它而不是尝试自己动手。
至于验证输入,有许多方法可以应用验证,here is one详见MSDN。我detail another approach在我的博客上的两个帖子中进行自定义绑定验证(您可以将验证应用于DecimalUpDown控件上的Value
属性绑定)。
答案 3 :(得分:4)
我也遇到过这个问题;使用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";
}
答案 4 :(得分:2)
我实现了自己的TextBox。当文本中有数字时,它会更新源,否则不会。在丢失焦点时,我读取了源属性。您所要做的就是用此类替换TextBox并绑定类型为double的“Number”属性。
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
)
);
}
答案 5 :(得分:2)
这将只允许在文本框中输入小数,而不能输入其他任何内容。
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}" />
答案 6 :(得分:1)
我很新,所以我不能评论他的答案,但我修正了 blindmeis 代码中的负数问题。
只需修改
即可if (input.Contains("-"))
IsValidInput ()到... 的部分
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);
}
}
}
答案 7 :(得分:1)
如果您希望文本框仅允许小数,则为该文本框编写previewinputtext事件。 那么在那个事件中写下这段代码
decimal result;
e.Handled=!decimal.TryParse((sender as TextBox).Text + e.Text, out result)
答案 8 :(得分:1)
这个正则表达式起作用
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));
}
答案 9 :(得分:1)
我知道这则帖子很旧,但首先出现在Google搜索中。由于system.windows.interactivity程序包(此程序包的旧版本)出错,我继续搜索。
MSDN上的这篇帖子解决了我的问题,这是一个一行的解决方案,就在像这样在主窗口上的initializecomponent之前:
Public Sub New()
' This call is required by the designer.
FrameworkCompatibilityPreferences.KeepTextBoxDisplaySynchronizedWithTextProperty = False
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
End Sub
希望这会对其他Google搜索者有所帮助。
答案 10 :(得分:1)
从 .NET 4.5 开始,有一个更简单的修复方法,为绑定添加“延迟”
<TextBox Text="{Binding MyDouble, UpdateSourceTrigger=PropertyChanged, Delay=1000}" />
在绑定系统尝试替换句点(将“1.”更改为“1”)之前,用户现在有 1 秒(1000 毫秒)的时间。 这应该让他们有时间在 '.' 之后输入额外的字符。以免被删除。
答案 11 :(得分:0)
我发现仅使用PreviewTextInput事件只会在您输入一些数字后想要输入负数时引起问题 1-> 12-> 123->-123(向后移动光标)
在PreviewTextInput事件中,移动插入符号将不起作用(发送者为TextBox)。Text+ e.Text
使用以下命令获取正则表达式表达式链接作为基础Decimal number regular expression, where digit after decimal is optional
确定@“ ^ [+-]?\ d *。?\ d * $”最适合我。
string previousText = "";
int previousCaretIndex = 0;
private void txtB_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
previousText = ((System.Windows.Controls.TextBox)sender).Text;
previousCaretIndex = ((System.Windows.Controls.TextBox)sender).CaretIndex;
}
private void txtB_TextChanged(object sender, TextChangedEventArgs e)
{
if(!Regex.IsMatch(((System.Windows.Controls.TextBox)sender).Text, @"^[+-]?\d*\.?\d*$"))
{
((System.Windows.Controls.TextBox)sender).Text = previousText;
((System.Windows.Controls.TextBox)sender).CaretIndex = previousCaretIndex;
e.Handled = true;
}
}
答案 12 :(得分:0)
通过这种方法将防止将非整数和非十进制值复制和粘贴到 TextBox
中,我在任何其他答案中都没有看到:
private void TextBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
var textBoxText = ((System.Windows.Controls.TextBox)sender).Text;
var regex = new System.Text.RegularExpressions.Regex(@"^\d+\.?\d*$");
if (textBoxText.Length > 0)
{
textBoxText += e.Text;
e.Handled = !regex.IsMatch(textBoxText);
}
else
{
e.Handled = !regex.IsMatch(e.Text);
}
}
private void TextBox_PreviewExecuted(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
if (e.Command == System.Windows.Input.ApplicationCommands.Paste)
{
if (System.Windows.Clipboard.ContainsText())
{
e.Handled = !new System.Text.RegularExpressions.Regex(@"^\d+\.?\d*$").IsMatch(System.Windows.Clipboard.GetText());
}
}
}
// In case user copies and pastes 2 times or more.
// E.G. "1.0" might give "1.01.0" and so on.
// E.G. if the regex expression is for the range of 1-100.
// Then user might delete some numbers from the input which would give "0" or "00" etc.
private void TextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
var textBox = (System.Windows.Controls.TextBox)sender;
if (!new System.Text.RegularExpressions.Regex(@"^\d+\.?\d*$").IsMatch(textBox.Text.Trim()))
{
textBox.Clear();
}
}
XAML:
<TextBox PreviewTextInput="TextBox_PreviewTextInput" CommandManager.PreviewExecuted="TextBox_PreviewExecuted" TextChanged="TextBox_TextChanged" HorizontalAlignment="Left" VerticalAlignment="Top" Width="120"/>
顺便说一句,如果您想更改其行为以接受其他模式(例如正则表达式),您可以将正则表达式:@"^\d+\.?\d*$"
更改为适合您需要的其他内容,这种方法似乎更多简单可靠。
编辑
在某些情况下取决于正则表达式,例如HH:mm:ss 的日期时间正则表达式,其中 TextChanged
不会接受类似 00: 的内容,当您键入尝试达到 00:20:00 时,该时间会在第三个数字 00: 处停止,因此在这种情况下,如果您没有更好的正则表达式,而不是使用 TextChanged
使用以下内容:
private void TextBox_LostFocus(object sender, System.Windows.RoutedEventArgs e)
{
var textBox = (System.Windows.Controls.TextBox)sender;
var textBoxText = textBox.Text.Trim();
if (textBoxText.Length == 0)
{
this.error = false; // It can be true too, depends on your logic.
}
else
{
this.error = !new System.Text.RegularExpressions.Regex(@"^\d+\.?\d*$").IsMatch(textBoxText);
if (this.error)
{
textBox.Background = System.Windows.Media.Brushes.Red;
}
else
{
textBox.ClearValue(System.Windows.Controls.TextBox.BackgroundProperty);
}
}
}
error
变量是一个成员变量,您应该使用它在表单末尾进行验证,例如通过点击按钮。