如何在 DataGridTemplateColumn 内的控件上双向绑定 属性?

How to bind a property both ways on a control inside a DataGridTemplateColumn?

我有 ObservableCollectionDataPoint 个对象。 class 有一个 Data 属性。我有一个DataGrid。我有一个名为 NumberBox 的自定义 UserControl,它是 TextBox 的包装器,但对于数字,具有可绑定的 Value 属性(或至少是预期的)成为)。

我希望 DataGridNumberBox 列中显示我的 Data,以便可以显示和更改值。我使用了 DataGridTemplateColumn,将 Value 属性 绑定到 Data,将 ItemsSource 设置为我的 ObservableCollection.

添加或修改基础 Data 时,NumberBox 更新得很好。但是,当我在框中输入一个值时,Data 不会更新。

我找到了建议实施 INotifyPropertyChanged 的答案。首先,不确定我应该实施什么。其次,我尝试在 DataPointNumberBox 上实现它 thusly。我找到了建议将 Mode=TwoWay, UpdateSourceTrigger=PropertyChange 添加到我的 Value 绑定的答案。我已经分别和一起尝试过这两种方法。显然,问题依旧。

下面是我目前正在使用的最低限度的项目,试图让这个东西工作。我错过了什么?

MainWindow.xaml

<Window xmlns:BindingTest="clr-namespace:BindingTest" x:Class="BindingTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid Name="Container" AutoGenerateColumns="False" CanUserSortColumns="False" CanUserResizeColumns="False" CanUserResizeRows="False" CanUserReorderColumns="False" CanUserAddRows="False" CanUserDeleteRows="True">
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="Sample Text" Width="100" >
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <BindingTest:NumberBox Value="{Binding Data}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
        <Button Name="BTN" Click="Button_Click" Height="30" VerticalAlignment="Bottom" Content="Check"/>
    </Grid>
</Window>

MainWindow.cs

public partial class MainWindow : Window
{
    private ObservableCollection<DataPoint> Liste { get; set; }

    public MainWindow()
    {
        InitializeComponent();

        Liste = new ObservableCollection<DataPoint>();

        Container.ItemsSource = Liste;

        DataPoint dp1 = new DataPoint(); dp1.Data = 1;
        DataPoint dp2 = new DataPoint(); dp2.Data = 2;
        Liste.Add(dp1);
        Liste.Add(dp2);
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        BTN.Content = Liste[0].Data;
    }
}

DataPoint.cs

public class DataPoint
{
    public double Data { get; set; }
}

NumberBox.xaml

<UserControl x:Class="BindingTest.NumberBox"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="28" d:DesignWidth="200">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>

        <TextBox Name="Container" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalContentAlignment="Center"/>
    </Grid>
</UserControl>

NumberBox.cs

public partial class NumberBox : UserControl
{
    public event EventHandler ValueChanged;

    public NumberBox()
    {
        InitializeComponent();
    }

    private double _value;
    public double Value
    {
        get { return _value; }
        set
        {
            _value = value;
            Container.Text = value.ToString(CultureInfo.InvariantCulture);
            if (ValueChanged != null) ValueChanged(this, new EventArgs());
        }
    }

    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
        "Value",
        typeof(double),
        typeof(NumberBox),
        new PropertyMetadata(OnValuePropertyChanged)
    );

    public static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        double? val = e.NewValue as double?;
        (d as NumberBox).Value = val.Value;
    }
}

What am I missing?

实际上有很多东西。

  1. 依赖项 属性 的 CLR 属性 应该 获取和设置依赖项 属性 的值:

     public partial class NumberBox : UserControl
     {
         public NumberBox()
         {
             InitializeComponent();
         }
    
         public double Value
         {
             get => (double)GetValue(ValueProperty);
             set => SetValue(ValueProperty, value);
         }
    
         public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
             "Value",
             typeof(double),
             typeof(NumberBox),
             new PropertyMetadata(0.0)
         );
    
         private void Container_PreviewTextInput(object sender, TextCompositionEventArgs e)
         {
             Value = double.Parse(Container.Text, CultureInfo.InvariantCulture);
         }
     }
    
  2. 控件中的 TextBox 应该绑定到 Value 属性:

     <TextBox Name="Container" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalContentAlignment="Center"
              Text="{Binding Value, RelativeSource={RelativeSource AncestorType=UserControl}}"
              PreviewTextInput="Container_PreviewTextInput"/>
    
  3. ValueData属性之间的绑定模式应该设置为TwoWay也是,这是因为输入控件在DataGridCellTemplate中,UpdateSourceTrigger应该设置为PropertyChanged:

     <local:NumberBox x:Name="nb" Value="{Binding Data, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    

给我指明了正确的方向。

确实缺少两个关键要素: 更改 NumberBox.xaml 中的 TextBox 例如

<TextBox Name="Container"
     Text="{Binding Value, RelativeSource={RelativeSource AncestorType=UserControl}}"
     HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalContentAlignment="Center"
    />

并更改 MainWindow.xaml 中的绑定,例如

...
<DataTemplate>
    <BindingTest:NumberBox 
        Value="{Binding Data, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
...

通过这两项修改,ValueData 都在我的 DataGridTemplaceColumn.

中进行了双向更新

然而绑定到文本被证明是有问题的。无论您的系统如何设置,WPF 显然都使用 en-US 文化,这真的不好,因为此控件处理数字。使用 this trick 解决了这个问题,现在可以识别正确的小数点分隔符。就我而言,我已将它添加到静态构造函数中以获得相同的效果,因此 NumberBox 可以按原样用于其他应用程序。

static NumberBox()
{
    FrameworkElement.LanguageProperty.OverrideMetadata(
        typeof(FrameworkElement),
        new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));
}

我已经在我的测试项目中对此进行了测试,然后再次测试了与我的真实项目的集成。据我所知,它是成立的,所以我会考虑回答这个问题。