WPF:UpdateSourceTrigger =具有双值的PropertyChanged问题

时间:2014-10-02 14:14:11

标签: c# wpf xaml data-binding datagrid

我正在开发一个WPF项目,我将DataGrid绑定到一个ObservableCollection。这些值正在被正确限制,但我遇到的问题是我无法使用双值编辑列。它不会让我在细胞中插入一段时间。

这就是我对XAML的看法:

<DataGrid Name="dataGrid" AutoGenerateColumns="False" 
              CanUserResizeColumns="True" CanUserAddRows="False" CanUserSortColumns="True" ItemsSource="{Binding}"
              HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" 
              ColumnWidth="*" Margin="0,51,186,58" 
              RowEditEnding="dataGrid_RowEditEnding">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Field 1" Binding="{Binding Field1, UpdateSourceTrigger=PropertyChanged}" />
            <DataGridTextColumn Header="Field 2" Binding="{Binding Field2, UpdateSourceTrigger=PropertyChanged}" />
            <DataGridTextColumn Header="Field 3" Binding="{Binding Field3, UpdateSourceTrigger=PropertyChanged}" />
            <DataGridTextColumn Header="Field 4" Binding="{Binding Field4, UpdateSourceTrigger=PropertyChanged}" />
            <DataGridCheckBoxColumn Header="Field 5" Binding="{Binding Field5, UpdateSourceTrigger=PropertyChanged}" />
            <DataGridTextColumn Header="Field 6" Binding="{Binding Field6, UpdateSourceTrigger=PropertyChanged}" />
        </DataGrid.Columns>

<DataGrid>

这是班级(抱歉奇怪的班级属性:/我是故意这样做的)

class FieldClass : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private int _field1;
    public int Field1
    {
        get { return _field1; }
        set 
        {
            _field1 = value;
            OnPropertyChanged("Field1");
        }
    }

    private int _field2;
    public int Field2
    {
        get { return _field2; }
        set 
        {
            _field2 = value;
            OnPropertyChanged("Field2");
        }
    }

    private double _field3;
    public double Field3
    {
        get { return _field3; }
        set
        {
            _field3 = value;
            OnPropertyChanged("Field3");
        }
    }

    private double _field4;
    public double Field4
    {
        get { return _field4; }
        set 
        {
            _field4 = value;
            OnPropertyChanged("Field4");
        }
    }

    private bool _field5;
    public bool Field5
    {
        get { return _field5; }
        set 
        {
            _field5 = value;
            OnPropertyChanged("Field5");
        }
    }

    private double _field6;
    public double Field6
    {
        get { return _field6; }
        set 
        {
            _field6 = value;
            OnPropertyChanged("Field6");
        }
    }

    public FieldClass()
    {
        _field1 = 0;
        _field2 = 0;
        _field3 = 0.0;
        _field4 = 0.0;
        _field5 = false;
        _field6 = 0.0;
    }

    // Create the OnPropertyChanged method to raise the event 
    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }
}

如何在DataGrid上创建它,如果我想更新列的值(假设我想更新Field3或任何具有double的字段),我可以插入一个像2.1这样的double值吗? / p>

是否需要数据模板?不确定如何去做,仍然是初学者。

感谢您的帮助!

3 个答案:

答案 0 :(得分:2)

如果你真的想通过PropertyChanged触发获得行为,可以尝试使用Binding的IsAsync = true,但我不确定这是否是正确的解决方案。

<DataGridTextColumn Header="Field 3" Binding="{Binding Field3, UpdateSourceTrigger=PropertyChanged, StringFormat=\{0:n\}, IsAsync=True}" />

答案 1 :(得分:1)

我会说解决方案是将 DataGridTextColumn 更改为 DataGridTemplateColumn 并在其模板中使用 NumericUpDown。 NumericUpDown 应该比 TextBox 更好地处理这种情况。

这是一个带有 Extended WPF Toolkit 控件的示例(如有必要,可以隐藏旋转按钮使其看起来像 TextBox)

<DataGridTemplateColumn Header="Field1" Width="200">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <xctk:DoubleUpDown AllowSpin="False" ShowButtonSpinner="False" 
                               BorderThickness="0"
                               CultureInfo="en-US"
                               Value="{Binding Field1, UpdateSourceTrigger=PropertyChanged}"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

License 允许非商业使用。


作为快速修复,我只需将 Delay 属性(以毫秒为单位)添加到 DataGridTextColumn 绑定。它仅在用户停止输入后经过该时间段后才绑定更新。

<DataGridTextColumn Header="Field1"
                    Width="200"
                    Binding="{Binding Field1, Delay=500, UpdateSourceTrigger=PropertyChanged}"/>

这对我来说足够合理:如果我输入一些带小数分隔符的数字,我输入分隔符和下一个数字的速度足以适应延迟。

延迟从 .NET Framework 4.5 开始可用。

答案 2 :(得分:1)

文章摘要"Not an easy solution for a simple common problem. Binding a TextBox to a numeric property"

1) 问题描述。

本主题解决的主要问题是在绑定到非整数属性的 TextBox 中输入小数点的问题。在 TwoWay 模式下使用 UpdateSourceTrigger = PropertyChanged 绑定时,它会表现出来。 除了点,也不能输入无关紧要的零。 虽然这对于前导零 (000123) 很少需要,但尾随零 (123.000) 会带来问题。 假设输入数字 123.0004,通过简单的绑定将不起作用。

第二个偶然解决且经常需要的任务是限制用户输入“仅数字”。 它以更一般的形式解决:通过限制仅输入数值。符号类型允许使用符号,非整数允许使用小数点,特定文化中允许使用数字组分隔符,科学表示的符号。

2) 问题的原因。在 Binding 中实现的内部逻辑。

考虑当您输入一个带有小数点和非重要零的数字时会发生什么:

  • 输入了数字123.000
  • 触发对源属性(UpdateSource)的赋值;
  • 为了赋值,字符串被转换为123.0;
  • 号码被转移到Reflection;
  • 反射分配传入的编号并调用绑定更新目标(UpdateTarget);
  • 绑定将数字 123.0 转换为其字符串表示形式。在这里,点和无关紧要的零被丢弃!字符串“123”是从数字123.0中得到的;
  • 这个字符串被传递给 DependencyObject 以分配给 Text 属性。但是在 Text 属性中有一个字符串“123.000”。由于它不等于字符串“123”,因此将值“123”分配给 Text 属性 - 点和零都消失了!

为了不发生赋值,需要不仅将新值与当前值进行比较,而是将两个值都转换为源属性的数字类型并比较这些数字。 但是 DependencyObject 对绑定一无所知。甚至不知道这个属性是否有绑定。

要解决此问题,您甚至需要在将数字转换为字符串之前分析 Text 属性的当前值。如果可以从 Text 中获得相同的数字,则不应发生赋值。

3) 应用多转换器来绑定 Double 属性。

首先,让我们通过多转换器实现最明显的解决方案。 它的工作算法非常简单——它接收两个值:绑定到源属性和绑定到目标属性。 通过比较两个值,它可以返回数字的字符串表示形式,也可以通过返回 Binding.DoNothing 来撤消分配。 考虑到传输到转换器的区域性,将数字转换为字符串并返回。 用于调试将输出添加到 Debug,并在显示此输出的控制窗口中。

在绑定中的 XAML 中添加了另外两个验证器以演示类型转换。

MultiConverter 的完整代码:

using System;
using System.Diagnostics;
using System.Globalization;
using System.Windows.Data;
 
namespace BindingStringToNumeric
{
    /// <summary>Сравнивает полученное <see cref="Double"/> число со <see cref="String"/> текстом.<br/>
    /// Если из текста получается такое же число, то присвоение значение по привязке отменяется.</summary>
    /// <remarks>Значения должны приходить в массиве в параметре values метода <see cref="IMultiValueConverter.Convert(object[], Type, object, CultureInfo)"/><br/>
    /// В массиве должо быть два значения:<br/>
    /// 0 - значение источника в <see cref="Double"/> типе,<br/>
    /// 1 - <see cref="String"/> Text с которым надо сравнить число.</remarks>
    public class DoubleConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            Debug.Write(GetType() + $".Convert.values: {values[0]}, \"{values[1]}\"");
            double source = (double)values[0];
            string text = (string)values[1];
 
            object ret;
            // Получение из текста числа (в переданной культуре) и сравнение его с числом источника.
            // Если они равны, то отменяется присвоение значения.
            if (double.TryParse(text, NumberStyles.Any, culture, out double target) && target == source)
                ret = Binding.DoNothing;
 
            // Иначе число источника переводится в строку в заданнной культуре  и возвращается.
            else
                ret = source.ToString(culture);
 
            Debug.WriteLine($"; return: {ret ?? "null"}");
            return ret; ;
        }
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            Debug.Write(GetType() + $".ConvertBack.value: \"{value}\" to ");
            object ret = null;
 
            string text = (string)value;
 
            // Если строка пустая, то это считается эквивалентом нуля.
            if (string.IsNullOrWhiteSpace(text))
                ret = 0.0;
 
            // Иначе проверяется возвожность перевода строки в число в заданной культуре.
            // Если перевод возможен, то возвращается полученное число.
            else if (double.TryParse(text, NumberStyles.Any, culture, out double target))
                ret = target;
 
            Debug.WriteLine($"return: {ret ?? "null"}");
 
            // Если ret значение не присваивалось, то значит строка некорректна
            // Тогда возвращается null, что вызывает ошибку валидации.
            if (ret == null)
                return null;
 
            // Иначе возвращается массив с одним элементом: полученным числом.
            return new object[] { ret };
        }
 
    }
}

完整的 XAML 窗口:

<Window x:Class="AppBindingToNumeric.DoubleConverterWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:AppBindingToNumeric" 
        xmlns:dgn="clr-namespace:WpfCustomControls.Diagnostics;assembly=WpfCustomControls"
        xmlns:bnd="clr-namespace:BindingStringToNumeric;assembly=BindingToNumeric"
        mc:Ignorable="d" FontSize="20"
        Title="Example #2: Binding to Double Property with MultiConverter"
        Height="450" Width="1000">
    <FrameworkElement.Resources>
        <bnd:DoubleConverter x:Key="DoubleConverter"/>
        <local:Numbers x:Key="Numbers"/>
    </FrameworkElement.Resources>
    <FrameworkElement.DataContext>
        <Binding Mode="OneWay" Source="{StaticResource Numbers}"/>
    </FrameworkElement.DataContext>
    <Grid Background="LightGreen">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <UniformGrid Background="LightBlue" Columns="2">
            <TextBlock Text="TextBlock"/>
            <TextBox Margin="5" Text="{Binding DoubleValue}" IsEnabled="False"/>
            <TextBlock Text="BindingMode=TwoWay"/>
            <TextBox x:Name="tbValidate" Margin="5">
                <TextBox.Text>
                    <MultiBinding Converter="{StaticResource DoubleConverter}" UpdateSourceTrigger="PropertyChanged">
                        <MultiBinding.ValidationRules>
                            <local:DebugValidationRule Title="MultiBinding"/>
                        </MultiBinding.ValidationRules>
                        <Binding Path="DoubleValue">
                            <Binding.ValidationRules>
                                <local:DebugValidationRule Title="Binding"/>
                            </Binding.ValidationRules>
                        </Binding>
                        <Binding Path="Text" ElementName="tbValidate"/>
                    </MultiBinding>
                </TextBox.Text>
            </TextBox>
            <TextBlock Text="BindingMode=OneTime"/>
            <TextBox x:Name="tbDogitsOnly" Margin="5">
                <TextBox.Text>
                    <MultiBinding Converter="{StaticResource DoubleConverter}" UpdateSourceTrigger="PropertyChanged">
                        <Binding Path="DoubleValue"/>
                        <Binding Path="Text" ElementName="tbDogitsOnly" Mode="OneTime"/>
                    </MultiBinding>
                </TextBox.Text>
            </TextBox>
        </UniformGrid>
        <dgn:DebugBox Grid.Row="1" Margin="10" FontSize="18"
                      IsOutputsText="{Binding IsActive,
                      RelativeSource={RelativeSource FindAncestor,
                      AncestorType={x:Type Window}}}"/>
    </Grid>
</Window>

转换器的视频测试:https://youtu.be/TauKTs7279Y

4) 用于绑定到任何类型的数字属性的通用多转换器。

对于成熟的通用转换器,我们首先需要创建一个通用方法来获取任何数字类型的验证解析器。 我通过创建为每个数字类型返回一个对象的解析器、一个存储这些解析器的字典以及一个按类型返回解析器的方法来解决这个问题。

一切都在静态类中实现。 逻辑很简单,所以没有详细的注释。仅指定了 XML 文档标签。

源代码已发布在 GitHub 上(链接到答案末尾的存储库),所以我不在这里发布它们。

转换器的视频测试:https://youtu.be/0LFHlgxvQso

此解决方案的最大缺点是难以在 XAML 中应用它。绑定之一或其参数中的错误将导致转换器无法正常工作。 为简化使用,需要将此转换器封装在一个标记扩展中,并在同一个地方实现Text属性的旧值到转换器的传递。

5) 扩展标记,包括创建附加属性、私有转换器和私有 MultiBinding。

整个代码超过 700 行。 它已经在 GitHub 上发布,在这里重新发布没有意义。

这里只是一个例子:

            <TextBox Margin="5" Text="{bnd:BindToNumeric DoubleValue}"/>
            <TextBox Margin="5" Text="{bnd:BindToNumeric DecimalValue, IsNumericOnly=False}"/>

作为参考,还有тest Window的完整XAML:

<Window x:Class="AppBindingToNumeric.MarkupExtensionWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:AppBindingToNumeric"
        xmlns:bnd="clr-namespace:BindingStringToNumeric;assembly=BindingToNumeric"
        xmlns:dgn="clr-namespace:WpfCustomControls.Diagnostics;assembly=WpfCustomControls"
        mc:Ignorable="d"
        Title="Example #4: Binding to a Numeric Properties with the Markup Extension"
        Height="450" Width="1000" FontSize="20">
    <FrameworkElement.Resources>
        <local:Numbers x:Key="Numbers" DoubleValue="123" DecimalValue="456" IntegerValue="799"/>
    </FrameworkElement.Resources>
    <FrameworkElement.DataContext>
        <Binding Mode="OneWay" Source="{StaticResource Numbers}"/>
    </FrameworkElement.DataContext>
    <Grid Background="LightGreen">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <UniformGrid Background="LightBlue" Columns="3">
            <TextBlock Text="To Double - Numeric Only"/>
            <TextBox Margin="5" Text="{bnd:BindToNumeric DoubleValue}"/>
            <TextBox Margin="5" Text="{Binding DoubleValue}" IsEnabled="False"/>
 
            <TextBlock Text="To Decimal - Any Value"/>
            <TextBox Margin="5" Text="{bnd:BindToNumeric DecimalValue, IsNumericOnly=False}"/>
            <TextBox Margin="5" Text="{Binding DecimalValue}" IsEnabled="False"/>
 
            <TextBlock Text="To Integer - Numeric Only"/>
            <TextBox Margin="5" Text="{bnd:BindToNumeric IntegerValue}"/>
            <TextBox Margin="5" Text="{Binding IntegerValue}" IsEnabled="False"/>
 
        </UniformGrid>
        <dgn:DebugBox Grid.Row="1" Margin="10" FontSize="18"
                      IsOutputsText="{Binding IsActive,
                      RelativeSource={RelativeSource FindAncestor,
                      AncestorType={x:Type Window}}}"/>
    </Grid>
</Window>

GitHub 的源代码:https://github.com/EldHasp/CyberForumMyCycleRepos/tree/master/BindingToNumber