пользовательский формат строки ввода текста wpf - PullRequest
0 голосов
/ 21 февраля 2019

У меня есть значения в формате [double-type-value] [unit], где unit может быть «g» или «mg» (g для граммов и mg для миллиграммов).Есть ли способ разрешить пользователю вводить текст в TextBox ТОЛЬКО в этом формате.Например, чтобы быть похожим на мини-текстовое поле, которое принимает только числа и мини-поле со списком, где значения «г» или «мг» в обычном текстовом поле или что-то еще?Было бы неплохо, чтобы единица измерения имела значение по умолчанию, равное «g», прежде чем что-то будет напечатано в текстовом поле, чтобы пользователю не приходилось вводить g или mg в конце текстового поля всегда, если есть больше текстовых полей.

РЕДАКТИРОВАТЬ Я использую шаблон MVVM, поэтому код нарушает его.

Ответы [ 4 ]

0 голосов
/ 21 февраля 2019

В связи с характером этого ввода я предлагаю вам создать CustomControl, более конкретный TextBox, способный ограничить Input и преобразовать Text в соответствующее значение -> a * 1005.*.

GramTextBox имеет DependencyProperty, называемый Gram, который представляет собой значение введенного Text и может быть привязан к ViewModel (ПРИМЕЧАНИЕ: привязка должна содержать Mode=TwoWayGramTextBox пытается обновить границу 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 изменяется (например, допустимые вводы с клавиатуры / вставки и т. д.).

Примечания

Предполагается, что бессмысленные входы, такие как .00g, 0.0m, .mg, устанавливают Gram Property в 0 (как запасное значение).

Личное примечание

Спасибо @Pavel за PasteHandler

Редактировать

Чтобы использовать GramTextBox в DataGrid, вы можете переопределить CellTemplate Column:

<DataGrid AutoGenerateColumns="False" ... >
    <DataGrid.Columns>
       <!-- Put some other Columns here like DataGridTextColumn -->
       <DataGridTemplateColumn Header="Mass">
           <DataGridTemplateColumn.CellTemplate>
               <DataTemplate>
                   <cc:GramTextBox Gram="{Binding VMDoubleProperty, Mode=TwoWay}" ... />
               </DataTemplate>
           </DataGridTemplateColumn.CellTemplate>
       </DataGridTemplateColumn>
       <!-- Put some other Columns here -->
   </DataGrid.Columns>
</DataGrid> 
0 голосов
/ 21 февраля 2019

Вы должны обработать три события:

  • PreviewTextInput
  • PreviewKeyDown - чтобы запретить ввод пробельных символов, так как они не обрабатываются PreviewTextInput
  • DataObject.Pasting прикрепленное событие, чтобы запретить пользователю вставлять недопустимый текст из буфера обмена

Лучше заключить эту логику в поведение.Есть примеры аналогичного поведения: TextBoxIntegerInputBehavior , TextBoxDoubleInputBehavior .

0 голосов
/ 21 февраля 2019

Вы можете использовать регулярное выражение с событиями PreviewTextInput, DataObject.Pasting и PreviewKeyDown на TextBox, чтобы проверить, соответствует ли новая строка 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 и поместить туда события выше.

Вы можете сделать это, используя этот код в вашем TextBox в xaml:

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>
0 голосов
/ 21 февраля 2019

Чтобы пользователи не могли вводить что-либо, кроме цифр, необходимо использовать событие 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);
    }

PS. Если вам не нравится использование try Catch, вы можете использовать регулярное выражение для этого

...