wpf datagrid : 在 wpf 中创建一个 DatagridNumericColumn

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19374471/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 09:51:34  来源:igfitidea点击:

wpf datagrid : create a DatagridNumericColumn in wpf

c#wpfvalidationdatagrid

提问by Mussammil

I have a requirement that I want to make a datagridcolumn which only accepts numeric values(integer) ,when the user enter something other than numbers handle the textbox . I tried a lot of webpages ,Iam tired of these ,I greately appreciate anybody have the helping mind.

我有一个要求,我想制作一个仅接受数值(整数)的数据网格列,当用户输入数字以外的其他内容处理文本框时。我尝试了很多网页,我厌倦了这些,我非常感谢任何有帮助的人。

回答by Omri Btian

Based on @nit suggestion, you can create your own class derived from DataGridTextColumnlike this:

根据@nit 的建议,您可以创建自己的类,DataGridTextColumn如下所示:

public class DataGridNumericColumn : DataGridTextColumn
{
    protected override object PrepareCellForEdit(System.Windows.FrameworkElement editingElement, System.Windows.RoutedEventArgs editingEventArgs)
    {
        TextBox edit = editingElement as TextBox;
        edit.PreviewTextInput += OnPreviewTextInput;

        return base.PrepareCellForEdit(editingElement, editingEventArgs);
    }

    void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
    {
        try
        {
            Convert.ToInt32(e.Text);
        }
        catch
        {
            // Show some kind of error message if you want

            // Set handled to true
            e.Handled = true;
        }
    }
}

In the PrepareCellForEditmethod you register the OnPreviewTextInputmethod to the editing TextBoxPreviewTextInputevent, where you validate for numeric values.

PrepareCellForEdit方法中,您将该方法注册OnPreviewTextInput到编辑TextBoxPreviewTextInput事件,在那里验证数值。

In xaml, you simply use it:

在 xaml 中,您只需使用它:

    <DataGrid ItemsSource="{Binding SomeCollection}">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding NonNumericProperty}"/>
            <local:DataGridNumericColumn Binding="{Binding NumericProperty}"/>
        </DataGrid.Columns>
    </DataGrid>

Hope this helps

希望这可以帮助

回答by Nitin

If you dont want to show any validation errors and just want to block any non-numeral value then you can create the DataGridTemplateColumnand in CellEditingTemplateuse the TextBox.

如果您不想显示任何验证错误而只想阻止任何非数字值,那么您可以创建DataGridTemplateColumnCellEditingTemplate使用TextBox.

                <DataGridTemplateColumn Width="100*">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Path=NumericProperty}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <TextBox PreviewTextInput="TextBox_PreviewTextInput" Text="{Binding Path=NumericProperty}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>
                </DataGridTemplateColumn>

and in PreviewTextInputof the TextBox set e.Handled = trueif value is other than integer:

和在PreviewTextInput文本框的集合e.Handled = true,如果值小于整数其他:

       private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
        {
            try
            {
                Convert.ToInt32(e.Text);
            }
            catch
            {
                e.Handled = true;
            }
        }

回答by Brian Watt

I got here looking for a solution to the same problem: constraining the input into cells on a DataGridto be numeric. But the accepted answer did not work for me. The following did:

我来到这里寻找相同问题的解决方案:将输入限制DataGrid为数字的单元格。但是接受的答案对我不起作用。做了以下事情:

  1. For the DataGridadd an event handler for PreparingForCellEdit.
  2. In that event handler, cast the EditingElementto a TextBoxand add an event handler for PreviewTextInputto the TextBox.
  3. In the PreviewTextInputevent handler set e.Handledto true, if the input should not be allowed.
  1. 对于DataGridPreparingForCellEdit.添加事件处理程序。
  2. 在该事件处理程序中,将 强制转换EditingElement为 aTextBox并将事件处理程序添加PreviewTextInputTextBox
  3. PreviewTextInput事件处理程序中设置e.Handled为true,如果输入不应该被允许。

The above steps work if the user clicks the cell to edit. However, if the cell is not in edit mode, the PreparingForCellEditevent will not be called. To perform validation in that case:

如果用户单击要编辑的单元格,则上述步骤有效。但是,如果单元格未处于编辑模式,PreparingForCellEdit则不会调用该事件。在这种情况下执行验证:

  1. Add an event handler to the DataGridfor PreviewTextInput.
  2. In that event handler, safely cast e.OriginalSourceto a DataGridCell(exiting, if it is not a DataGridCell), check the DataGridCell'sIsEditingproperty, and if the cell is not editing set e.Handledto true.
  1. 将事件处理程序添加到DataGridfor PreviewTextInput
  2. 在该事件处理程序中,安全地转换e.OriginalSource为 a DataGridCell(退出,如果不是 a DataGridCell),检查DataGridCell'sIsEditing属性,如果单元格未编辑设置e.Handled为 true。

The effect of the above is that the user will have to click into the cell in order to edit its contents and, as such, the PreparingForCellEdit/ PreviewTextInputcombination above will be invoked for all changes to the cell's contents.

上面的效果是用户必须单击单元格才能编辑其内容,因此,对于单元格内容的所有更改,将调用上面的PreparingForCellEdit/PreviewTextInput组合。

回答by Ankush Madankar

Use TryParseinstead, this helps to restrict input values to integer numbers only.

TryParse改为使用,这有助于将输入值限制为仅整数。

    /// <summary>
    /// This class help to create data grid cell which only support interger numbers.
    /// </summary>
    public class DataGridNumericColumn : DataGridTextColumn
    {
        protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
        {
            TextBox edit = editingElement as TextBox;

            if (edit != null) edit.PreviewTextInput += OnPreviewTextInput;

            return base.PrepareCellForEdit(editingElement, editingEventArgs);
        }

        private void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
        {
            int value;

            if (!int.TryParse(e.Text, out value))
                e.Handled = true;
        }
    }

回答by Yoav

Just to extend @Omribitan's answer, Here is the solution with a data Pasteguard added:

只是为了扩展@Omribitan 的回答,这是Paste添加了数据保护的解决方案:

public class NumericTextColumn : DataGridTextColumn
{
    protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
    {
        var edit = editingElement as TextBox;
        edit.PreviewTextInput += Edit_PreviewTextInput;
        DataObject.AddPastingHandler(edit, OnPaste);
        return base.PrepareCellForEdit(editingElement, editingEventArgs);
    }

    private void OnPaste(object sender, DataObjectPastingEventArgs e)
    {
        var data = e.SourceDataObject.GetData(DataFormats.Text);
        if (!IsDataValid(data)) e.CancelCommand();
    }

    private void Edit_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        e.Handled = !IsDataValid(e.Text);
    }

    bool IsDataValid(object data)
    {
        try
        {
            Convert.ToInt32(data);
            return true;
        }
        catch
        {
            return false;
        }
    }
}

回答by bokibeg

For whatever it's worth, here's how I solved it. This solution allows you to specify a variety of options when validating input, allows string formatting to be used (e.g. '$15.00' in the data grid) and more.

不管它值多少钱,这就是我解决它的方法。此解决方案允许您在验证输入时指定各种选项,允许使用字符串格式(例如,数据网格中的“$15.00”)等等。

The null value and string formatting provided by the Bindingclass itself do not suffice as neither act correctly when the cell is editable so this class covers it. What this does is it uses another class that I've been using for a long time already: TextBoxInputBehavior, it has been an invaluable asset for me and it originally came from WPF – TextBox Input Behaviorblog post albeit the version here seems much older (but well tested). So what I did I just transferred this existing functionality I already had on my TextBoxes to the my custom column and thus I have the same behaviour in both. Isn't that neat?

Binding类本身提供的空值和字符串格式不足以在单元格可编辑时正确操作,因此此类覆盖它。它的作用是它使用了另一个我已经使用了很长时间的类:TextBoxInputBehavior,它对我来说是一个无价的资产,它最初来自WPF – TextBox Input Behavior博客文章,尽管这里的版本似乎更旧(但测试良好)。所以我所做的只是将我已经在 TextBox 上的现有功能转移到我的自定义列,因此我在两者中都有相同的行为。是不是很整洁?

Here's the code of the custom column:

这是自定义列的代码:

public class DataGridNumberColumn : DataGridTextColumn
{
    private TextBoxInputBehavior _behavior;

    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        var element = base.GenerateElement(cell, dataItem);

        // A clever workaround the StringFormat issue with the Binding set to the 'Binding' property. If you use StringFormat it
        // will only work in edit mode if you changed the value, otherwise it will retain formatting when you enter editing.
        if (!string.IsNullOrEmpty(StringFormat))
        {
            BindingOperations.ClearBinding(element, TextBlock.TextProperty);
            BindingOperations.SetBinding(element, FrameworkElement.TagProperty, Binding);
            BindingOperations.SetBinding(element,
                TextBlock.TextProperty,
                new Binding
                {
                    Source = element,
                    Path = new PropertyPath("Tag"),
                    StringFormat = StringFormat
                });
        }

        return element;
    }

    protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
    {
        if (!(editingElement is TextBox textBox))
            return null;

        var originalText = textBox.Text;

        _behavior = new TextBoxInputBehavior
        {
            IsNumeric = true,
            EmptyValue = EmptyValue,
            IsInteger = IsInteger
        };

        _behavior.Attach(textBox);

        textBox.Focus();

        if (editingEventArgs is TextCompositionEventArgs compositionArgs) // User has activated editing by already typing something
        {
            if (compositionArgs.Text == "\b") // Backspace, it should 'clear' the cell
            {
                textBox.Text = EmptyValue;
                textBox.SelectAll();
                return originalText;
            }

            if (_behavior.ValidateText(compositionArgs.Text))
            {
                textBox.Text = compositionArgs.Text;
                textBox.Select(textBox.Text.Length, 0);
                return originalText;
            }
        }

        if (!(editingEventArgs is MouseButtonEventArgs) || !PlaceCaretOnTextBox(textBox, Mouse.GetPosition(textBox)))
            textBox.SelectAll();

        return originalText;
    }

    private static bool PlaceCaretOnTextBox(TextBox textBox, Point position)
    {
        int characterIndexFromPoint = textBox.GetCharacterIndexFromPoint(position, false);
        if (characterIndexFromPoint < 0)
            return false;
        textBox.Select(characterIndexFromPoint, 0);
        return true;
    }

    protected override void CancelCellEdit(FrameworkElement editingElement, object uneditedValue)
    {
        UnwireTextBox();
        base.CancelCellEdit(editingElement, uneditedValue);
    }

    protected override bool CommitCellEdit(FrameworkElement editingElement)
    {
        UnwireTextBox();
        return base.CommitCellEdit(editingElement);
    }

    private void UnwireTextBox() => _behavior.Detach();

    public static readonly DependencyProperty EmptyValueProperty = DependencyProperty.Register(
        nameof(EmptyValue),
        typeof(string),
        typeof(DataGridNumberColumn));

    public string EmptyValue
    {
        get => (string)GetValue(EmptyValueProperty);
        set => SetValue(EmptyValueProperty, value);
    }

    public static readonly DependencyProperty IsIntegerProperty = DependencyProperty.Register(
        nameof(IsInteger),
        typeof(bool),
        typeof(DataGridNumberColumn));

    public bool IsInteger
    {
        get => (bool)GetValue(IsIntegerProperty);
        set => SetValue(IsIntegerProperty, value);
    }

    public static readonly DependencyProperty StringFormatProperty = DependencyProperty.Register(
        nameof(StringFormat),
        typeof(string),
        typeof(DataGridNumberColumn));

    public string StringFormat
    {
        get => (string) GetValue(StringFormatProperty);
        set => SetValue(StringFormatProperty, value);
    }
}

What I did is I peeked into the source code of DataGridTextColumnand handled the TextBox creation in almostthe same way plus I attached the custom behaviour to the TextBox.

我所做的是查看源代码DataGridTextColumn并以几乎相同的方式处理 TextBox 创建,并将自定义行为附加到 TextBox。

Here's the code of the behavior I attached (this is a behavior you can use on any TextBox):

这是我附加的行为代码(这是您可以在任何 TextBox 上使用的行为):

public class TextBoxInputBehavior : Behavior<TextBox>
{
    #region DependencyProperties

    public static readonly DependencyProperty RegularExpressionProperty = DependencyProperty.Register(
        nameof(RegularExpression), 
        typeof(string), 
        typeof(TextBoxInputBehavior), 
        new FrameworkPropertyMetadata(".*"));

    public string RegularExpression
    {
        get
        {
            if (IsInteger)
                return @"^[0-9\-]+$";
            if (IsNumeric)
                return @"^[0-9.\-]+$";
            return (string)GetValue(RegularExpressionProperty);
        }
        set { SetValue(RegularExpressionProperty, value); }
    }

    public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register(
        nameof(MaxLength), 
        typeof(int), 
        typeof(TextBoxInputBehavior),
        new FrameworkPropertyMetadata(int.MinValue));

    public int MaxLength
    {
        get { return (int)GetValue(MaxLengthProperty); }
        set { SetValue(MaxLengthProperty, value); }
    }

    public static readonly DependencyProperty EmptyValueProperty = DependencyProperty.Register(
        nameof(EmptyValue), 
        typeof(string), 
        typeof(TextBoxInputBehavior));

    public string EmptyValue
    {
        get { return (string)GetValue(EmptyValueProperty); }
        set { SetValue(EmptyValueProperty, value); }
    }

    public static readonly DependencyProperty IsNumericProperty = DependencyProperty.Register(
        nameof(IsNumeric), 
        typeof(bool), 
        typeof(TextBoxInputBehavior));

    public bool IsNumeric
    {
        get { return (bool)GetValue(IsNumericProperty); }
        set { SetValue(IsNumericProperty, value); }
    }

    public static readonly DependencyProperty IsIntegerProperty = DependencyProperty.Register(
        nameof(IsInteger),
        typeof(bool),
        typeof(TextBoxInputBehavior));

    public bool IsInteger
    {
        get { return (bool)GetValue(IsIntegerProperty); }
        set
        {
            if (value)
                SetValue(IsNumericProperty, true);
            SetValue(IsIntegerProperty, value);
        }
    }

    public static readonly DependencyProperty AllowSpaceProperty = DependencyProperty.Register(
        nameof(AllowSpace),
        typeof (bool),
        typeof (TextBoxInputBehavior));

    public bool AllowSpace
    {
        get { return (bool) GetValue(AllowSpaceProperty); }
        set { SetValue(AllowSpaceProperty, value); }
    }

    #endregion

    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.PreviewTextInput += PreviewTextInputHandler;
        AssociatedObject.PreviewKeyDown += PreviewKeyDownHandler;
        DataObject.AddPastingHandler(AssociatedObject, PastingHandler);
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        if (AssociatedObject == null)
            return;

        AssociatedObject.PreviewTextInput -= PreviewTextInputHandler;
        AssociatedObject.PreviewKeyDown -= PreviewKeyDownHandler;
        DataObject.RemovePastingHandler(AssociatedObject, PastingHandler);
    }

    private void PreviewTextInputHandler(object sender, TextCompositionEventArgs e)
    {
        string text;
        if (AssociatedObject.Text.Length < AssociatedObject.CaretIndex)
            text = AssociatedObject.Text;
        else
            text = TreatSelectedText(out var remainingTextAfterRemoveSelection)
                ? remainingTextAfterRemoveSelection.Insert(AssociatedObject.SelectionStart, e.Text)
                : AssociatedObject.Text.Insert(AssociatedObject.CaretIndex, e.Text);
        e.Handled = !ValidateText(text);
    }

    private void PreviewKeyDownHandler(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Space)
            e.Handled = !AllowSpace;

        if (string.IsNullOrEmpty(EmptyValue))
            return;

        string text = null;

        // Handle the Backspace key
        if (e.Key == Key.Back)
        {
            if (!TreatSelectedText(out text))
            {
                if (AssociatedObject.SelectionStart > 0)
                    text = AssociatedObject.Text.Remove(AssociatedObject.SelectionStart - 1, 1);
            }
        }
        // Handle the Delete key
        else if (e.Key == Key.Delete)
        {
            // If text was selected, delete it
            if (!TreatSelectedText(out text) && AssociatedObject.Text.Length > AssociatedObject.SelectionStart)
            {
                // Otherwise delete next symbol
                text = AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, 1);
            }
        }

        if (text == string.Empty)
        {
            AssociatedObject.Text = EmptyValue;
            if (e.Key == Key.Back)
                AssociatedObject.SelectionStart++;
            e.Handled = true;
        }
    }

    private void PastingHandler(object sender, DataObjectPastingEventArgs e)
    {
        if (e.DataObject.GetDataPresent(DataFormats.Text))
        {
            var text = Convert.ToString(e.DataObject.GetData(DataFormats.Text));

            if (!ValidateText(text))
                e.CancelCommand();
        }
        else
            e.CancelCommand();
    }

    public bool ValidateText(string text)
    {
        return new Regex(RegularExpression, RegexOptions.IgnoreCase).IsMatch(text) && (MaxLength == int.MinValue || text.Length <= MaxLength);
    }

    /// <summary>
    /// Handle text selection.
    /// </summary>
    /// <returns>true if the character was successfully removed; otherwise, false.</returns>
    private bool TreatSelectedText(out string text)
    {
        text = null;
        if (AssociatedObject.SelectionLength <= 0)
            return false;

        var length = AssociatedObject.Text.Length;
        if (AssociatedObject.SelectionStart >= length)
            return true;

        if (AssociatedObject.SelectionStart + AssociatedObject.SelectionLength >= length)
            AssociatedObject.SelectionLength = length - AssociatedObject.SelectionStart;

        text = AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, AssociatedObject.SelectionLength);
        return true;
    }
}

All the good credit for above Behaviour class goes to blindmeis, I merely tweaked it over time. After checking his Blog I see he has a newer version of it so you may check it out. I was very happy to find out I could use his behaviour on DataGrid as well!

上述 Behavior 类的所有功劳都归于 Blindmeis,我只是随着时间的推移对其进行了调整。查看他的博客后,我看到他有一个更新的版本,所以你可以查看一下。我很高兴地发现我也可以在 DataGrid 上使用他的行为!

This solution worked really well, you can edit the cell properly via mouse/keyboard, paste the contents properly, use any binding source update triggers, use any string formatting etc. - it just works.

这个解决方案非常有效,您可以通过鼠标/键盘正确编辑单元格,正确粘贴内容,使用任何绑定源更新触发器,使用任何字符串格式等 - 它只是有效。

Here's an example of how to use it:

以下是如何使用它的示例:

    <local:DataGridNumberColumn Header="Nullable Int Currency" IsInteger="True" Binding="{Binding IntegerNullable, TargetNullValue=''}" StringFormat="{}{0:C}" />

Hope this helps someone.

希望这可以帮助某人。