自定义字符串格式文本输入wpf

时间:2019-02-21 12:02:41

标签: c# wpf string-formatting

我的值格式为[double-type-value] [unit],其中单位可以是“ g”或“ mg”(g表示克,mg表示毫克)。有没有一种方法允许用户仅以该格式在TextBox中输入文本。例如,像迷你文本框,仅接受数字,迷你组合框,其中普通文本框中的值为“ g”或“ mg”或其他?在文本框中输入某些内容之前,最好将单位的默认值设置为“ g”,这样,如果有更多的文本框,用户不必在文本框末尾键入g或mg。

编辑 我使用的是MVVM模式,因此后面的代码违反了它。

4 个答案:

答案 0 :(得分:1)

您实际上应该处理三个​​事件:

  • PreviewTextInput
  • PreviewKeyDown-防止输入空白字符,因为它们不是由PreviewTextInput处理的
  • DataObject.Pasting附加事件,以防止用户从剪贴板粘贴无效文本

最好将此逻辑封装在行为中。 有类似行为的示例:TextBoxIntegerInputBehaviorTextBoxDoubleInputBehavior

答案 1 :(得分:0)

您可以对PreviewTextInput上的事件DataObject.PastingPreviewKeyDownTextBox使用正则表达式,以检查新字符串是否与regex相匹配,如果没有,则可以取消操作。

像这样:

xaml

...
<TextBox PreviewTextInput="txtbox_PreviewTextInput" DataObject.Pasting="txtbox_Pasting" PreviewKeyDown="txtbox_PreviewKeyDown" />
...

后面的代码:

public partial class MainWindow : Window
{
    private Regex gramOrMilliGramRegex = new Regex("^[0-9.-]+(m?g)?$");

    public MainWindow ()
    {
        InitializeComponent();
    }

    private void txtbox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        if(sender is TextBox txtbox)
        {
            string newString = txtbox.Text.Substring(0, txtbox.CaretIndex) + e.Text + txtbox.Text.Substring(txtbox.CaretIndex); //Build the new string
            e.Handled = !gramOrMilliGramRegex.IsMatch(e.Text); //Check if it matches the regex
        }

    }

    private void txtbox_Pasting(object sender, DataObjectPastingEventArgs e)
    {
        if(sender is TextBox txtbox)
        {
            string newString = txtbox.Text.Substring(0, txtbox.CaretIndex) + e.DataObject.GetData(typeof(string)) as string + txtbox.Text.Substring(txtbox.CaretIndex); //Build new string
            if (!digitOnlyRegex.IsMatch(newString)) //Check if it matches the regex
            {
                e.CancelCommand();
            }
        }

    private void txtbox_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        //Prevents whitespace
        if (e.Key == Key.Space)
        {
            e.Handled = true;
        }
        base.OnPreviewKeyDown(e);
    }
}


更新:正如您现在所提到的,您正在使用MVVM,并且不想违反该模式。

您将需要将这些事件路由到ViewModel中的命令,并将这些事件放在上面。

您可以通过在xaml的TextBox中使用以下代码来做到这一点:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cmd ="http://www.galasoft.ch/mvvmlight"

...

<TextBox>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="PreviewTextInput">
            <cmd:EventToCommand Command="{Binding Mode=OneWay, Path=PreviewTextInputCommand}" PassEventArgsToCommand="True" />
        </i:EventTrigger>
        <i:EventTrigger EventName="DataObject.Pasting">
            <cmd:EventToCommand Command="{Binding Mode=OneWay, Path=DataObject_PastingCommand}" PassEventArgsToCommand="True" />
        </i:EventTrigger>
        <i:EventTrigger EventName="PreviewKeyDown">
            <cmd:EventToCommand Command="{Binding Mode=OneWay, Path=PreviewKeyDownCommand}" PassEventArgsToCommand="True" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</TextBox>

答案 2 :(得分:0)

由于这种输入的性质,我建议您创建一些CustomControl,更具体地说是一个TextBox,它可以限制Input并将Text转换为相应的值-> a GramTextBox

GramTextBox有一个称为DependencyProperty的{​​{1}},它代表输入的Gram的值,并且可以绑定到Text(注意:绑定必须包含ViewModel,因为Mode=TwoWay试图更新绑定的GramTextBox)。

代码

Source

用法

将此public sealed class GramTextBox : TextBox { //Constructor public GramTextBox() : base() { Text = "0g"; //Initial value TextChanged += OnTextChanged; DataObject.AddPastingHandler(this, OnPaste); } //Style override (get the Style of a TextBox for the GramTextBox) static GramTextBox() { DefaultStyleKeyProperty.OverrideMetadata(typeof(GramTextBox), new FrameworkPropertyMetadata(typeof(TextBox))); } //Define a DependencyProperty to make it bindable (dont forget 'Mode=TwoWay' due the bound value is updated from this GramTextBox) [Category("Common"), Description("Converted double value from the entered Text in gram")] [Browsable(true)] [Bindable(true)] public double Gram { get { return (double)GetValue(PathDataProperty); } set { SetCurrentValue(PathDataProperty, value); } } public static DependencyProperty PathDataProperty = DependencyProperty.Register("Gram", typeof(double), typeof(GramTextBox), new PropertyMetadata(0d)); //Extract the Gram value when Text has changed private void OnTextChanged(object sender, TextChangedEventArgs e) { ExtractGram(Text); } //Suppress space input protected override void OnPreviewKeyDown(KeyEventArgs e) { e.Handled = e.Key == Key.Space; } //Check text inputs protected override void OnPreviewTextInput(TextCompositionEventArgs e) { e.Handled = !IsValidText(Text.Insert(CaretIndex, e.Text)); } //check paste inputs private void OnPaste(object sender, DataObjectPastingEventArgs e) { //Check if pasted object is string if(e.SourceDataObject.GetData(typeof(string)) is string text) { //Check if combined string is valid if(!IsValidText(Text.Insert(CaretIndex, text))) { e.CancelCommand(); } } else { e.CancelCommand(); } } //Check valid format for extraction (supports incomplete inputs like 0.m -> 0g) private bool IsValidText(string text) { return Regex.IsMatch(text, @"^([0-9]*?\.?[0-9]*?m?g?)$"); } //Extract value from entered string private void ExtractGram(string text) { //trim all unwanted characters (only allow 0-9 dots and m or g) text = Regex.Replace(text, @"[^0-9\.mg]", String.Empty); //Expected Format -> random numbers, dots and couple m/g //trim all text after the letter g text = text.Split('g')[0]; //Expected Format -> random numbers, dots and m //trim double dots (only one dot is allowed) text = Regex.Replace(text, @"(?<=\..*)(\.)", String.Empty); //Expected Format -> random numbers with one or more dots and m //Check if m is at the end of the string to indicate milli (g was trimmed earlier) bool isMilli = text.EndsWith("m"); //Remove all m, then only a double number should remain text = text.Replace("m", String.Empty); //Expected Format -> random numbers with possible dot //trim all leading zeros text = text.TrimStart(new char[] { '0' }); //Expected Format -> random numbers with possible dot //Check if dot is at the beginning if (text.StartsWith(".")) { text = $"0{text}"; } //Expected Format -> random numbers with possible dot //Check if dot is at the end if (text.EndsWith(".")) { text = $"{text}0"; } //Expected Format -> random numbers with possible dot //Try to convert the remaining String to a Number, if it fails -> 0 Double.TryParse(text, out double result); //Update Gram Property (divide when necessary) Gram = (isMilli) ? result / 1000d : result; } } 放在Class中,然后在YOURNAMESPACE中添加名称空间别名

XAML

现在xmlns:cc="clr-namespace:YOURNAMESPACE" 可以这样使用

GramTextBox

每次<cc:GramTextBox Gram="{Binding VMDoubleProperty, Mode=TwoWay}" ... /> 中的Property更改时(例如,来自键盘/粘贴等的有效输入),它将更新ViewModel中绑定的Text

注释

意图是像GramTextBox.00g0.0m这样的废话将.mg Gram设置为Property(像后备值一样) )。

个人笔记

感谢@Pavel提供0

修改

要在PasteHandler中使用此GramTextBox,可以覆盖DataGrid中的CellTemplate

Column

答案 3 :(得分:-1)

为防止用户输入数字,您必须使用PrevieTextInput事件,为此创建一个自定义控件是有意义的。下面几行内容将阻止用户输入数字以外的任何内容

    <Grid>            
        <TextBox Text="{Binding Text}" PreviewTextInput="TextBox_PreviewTextInput"/>
        <TextBlock HorizontalAlignment="Right" Margin="5,0">g</TextBlock>
    </Grid>


    private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        var tb = sender as TextBox;
        e.Handled = !double.TryParse(tb.Text+e.Text, out double d);
    }

P.S。如果您不喜欢使用try Catch,则可以为此使用正则表达式