繁体   English   中英

wpf datagrid : 在 wpf 中创建一个 DatagridNumericColumn

[英]wpf datagrid : create a DatagridNumericColumn in wpf

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

"

根据@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;
        }
    }
}

PrepareCellForEdit方法中,将OnPreviewTextInput方法注册到编辑TextBox PreviewTextInput事件,在事件中验证数值。

在xaml中,您只需使用它:

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

希望这可以帮助

如果您不想显示任何验证错误并且只想阻止任何非数字值,那么您可以创建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>

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

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

我来到这里寻找解决同一问题的方法:将DataGrid上的单元格输入约束为数字。 但接受的答案对我不起作用。 以下做了:

  1. 对于DataGridPreparingForCellEdit添加一个事件处理程序。
  2. 在该事件处理程序中,将EditingElement转换为TextBox ,并将PreviewTextInput的事件处理程序添加到TextBox
  3. PreviewTextInput事件处理程序中,如果不允许输入,则将e.Handled设置为true。

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

  1. 事件处理程序添加到DataGridPreviewTextInput
  2. 在该事件处理程序中,安全地将e.OriginalSource转换为DataGridCell (退出,如果它不是DataGridCell ),检查DataGridCell's IsEditing属性,如果单元格未编辑,则将e.Handled设置为true。

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

请改用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;
        }
    }

只是为了扩展@ 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;
        }
    }
}

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

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

这是自定义列的代码:

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

我做的是我偷看了DataGridTextColumn的源代码并以几乎相同的方式处理TextBox创建加上我将自定义行为附加到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;
    }
}

以上行为类的所有良好功劳归于盲目,我只是随着时间推移而调整它。 检查他的博客后,我看到他有一个更新的版本,所以你可以检查出来。 我很高兴发现我也可以在DataGrid上使用他的行为!

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

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

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

希望这有助于某人。

我继续使用 Omris 方法,但是我希望能够在输入后删除单元格值,以防他们想清除它

我这样做的方法是覆盖 CommitCellEdit 方法并使字符串为空而不是空白。 我也用十进制? 就我而言

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