简体   繁体   English

wpf datagrid : 在 wpf 中创建一个 DatagridNumericColumn

[英]wpf datagrid : create a DatagridNumericColumn in wpf

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

"

Based on @nit suggestion, you can create your own class derived from DataGridTextColumn like 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 PrepareCellForEdit method you register the OnPreviewTextInput method to the editing TextBox PreviewTextInput event, where you validate for numeric values. PrepareCellForEdit方法中,将OnPreviewTextInput方法注册到编辑TextBox PreviewTextInput事件,在事件中验证数值。

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 希望这可以帮助

If you dont want to show any validation errors and just want to block any non-numeral value then you can create the DataGridTemplateColumn and in CellEditingTemplate use the TextBox . 如果您不想显示任何验证错误并且只想阻止任何非数字值,那么您可以创建DataGridTemplateColumn并在CellEditingTemplate使用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 PreviewTextInput of the TextBox set e.Handled = true if 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;
            }
        }

I got here looking for a solution to the same problem: constraining the input into cells on a DataGrid to be numeric. 我来到这里寻找解决同一问题的方法:将DataGrid上的单元格输入约束为数字。 But the accepted answer did not work for me. 但接受的答案对我不起作用。 The following did: 以下做了:

  1. For the DataGrid add an event handler for PreparingForCellEdit . 对于DataGridPreparingForCellEdit添加一个事件处理程序。
  2. In that event handler, cast the EditingElement to a TextBox and add an event handler for PreviewTextInput to the TextBox . 在该事件处理程序中,将EditingElement转换为TextBox ,并将PreviewTextInput的事件处理程序添加到TextBox
  3. In the PreviewTextInput event handler set e.Handled to true, if the input should not be allowed. 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 PreparingForCellEdit event will not be called. 但是,如果单元格未处于编辑模式,则不会调用PreparingForCellEdit事件。 To perform validation in that case: 在这种情况下执行验证:

  1. Add an event handler to the DataGrid for PreviewTextInput . 事件处理程序添加到DataGridPreviewTextInput
  2. In that event handler, safely cast e.OriginalSource to a DataGridCell (exiting, if it is not a DataGridCell ), check the DataGridCell's IsEditing property, and if the cell is not editing set e.Handled to true. 在该事件处理程序中,安全地将e.OriginalSource转换为DataGridCell (退出,如果它不是DataGridCell ),检查DataGridCell's IsEditing属性,如果单元格未编辑,则将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 / PreviewTextInput combination above will be invoked for all changes to the cell's contents. 上述效果是用户必须单击进入单元格以编辑其内容,因此,对于单元格内容的所有更改,将调用上面的PreparingForCellEdit / PreviewTextInput组合。

Use TryParse instead, 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;
        }
    }

Just to extend @Omribitan's answer, Here is the solution with a data Paste guard 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;
        }
    }
}

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 (eg '$15.00' in the data grid) and more. 此解决方案允许您在验证输入时指定各种选项,允许使用字符串格式(例如,数据网格中的“$ 15.00”)等等。

The null value and string formatting provided by the Binding class itself do not suffice as neither act correctly when the cell is editable so this class covers it. Binding类本身提供的空值和字符串格式不足以在单元格可编辑时无法正常操作,因此该类将覆盖它。 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 Behavior blog post albeit the version here seems much older (but well tested). 这样做是因为它使用了我已经使用了很长时间的另一个类: TextBoxInputBehavior ,它对我来说是一个非常宝贵的资产,它最初来自WPF - TextBox输入行为博客文章,尽管这里的版本似乎更老了(但经过充分测试)。 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. 所以我做了什么我只是将我已经在TextBox上的现有功能转移到了我的自定义列,因此我在两者中都有相同的行为。 Isn't that neat? 这不是很整洁吗?

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 DataGridTextColumn and handled the TextBox creation in almost the 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! 我很高兴发现我也可以在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. 希望这有助于某人。

I went on from Omris approach however i wanted to be able to delete the cell value after it has input in case they wanted to clear it我继续使用 Omris 方法,但是我希望能够在输入后删除单元格值,以防他们想清除它

The way i did this was overriding the CommitCellEdit method and making the string null instead of blank.我这样做的方法是覆盖 CommitCellEdit 方法并使字符串为空而不是空白。 Im also using decimal?我也用十进制? in my case就我而言

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);
    }

    protected override bool CommitCellEdit(System.Windows.FrameworkElement editingElement)
    {
        TextBox tb = editingElement as TextBox;
        if (string.IsNullOrEmpty(tb.Text))
            tb.Text = null;
        
        return base.CommitCellEdit(editingElement);
    }
    void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
    
    {
        try
        {
            Convert.ToDecimal(e.Text);
        }
        catch
        {
            // Show some kind of error message if you want

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM