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

Posted

技术标签:

【中文标题】如何在 DataGridTemplateColumn 内的控件上双向绑定属性?【英文标题】:How to bind a property both ways on a control inside a DataGridTemplateColumn? 【发布时间】:2021-11-26 10:36:50 【问题描述】:

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

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

当底层Data被添加或修改时,NumberBox更新就好了。但是,当我在框中输入值时,Data 不会更新。

我找到了建议实施INotifyPropertyChanged 的答案。首先,不确定我应该实施什么。其次,我尝试在我的DataPoint 和我的NumberBox 上实现它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>

数字框.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;
    

【问题讨论】:

如果你尝试&lt;BindingTest:NumberBox Value="Binding Data, Mode=TwoWay"/&gt;会怎样? @CorentinPane 我已经尝试过了 【参考方案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);
         
     
    

    控件中的TextBox 应该绑定到Value 属性:

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

    ValueData属性的绑定模式应该设置为TwoWay和,这是因为输入控件在DataGridCellTemplate中,@ 987654330@应该设置为PropertyChanged

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

【讨论】:

这主要是可行的,但是绑定文本是有问题的,因为它似乎与解析混淆。当我输入一个十进制数字时,它似乎使用英文数字格式,这不是我的系统设置的方式。 (我已经完全删除了 PreviewTextInput 事件,所以这不是导致问题的那一行)。【参考方案2】:

mm8's answer 为我指明了正确的方向。

确实缺少两个关键要素: 更改 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)));

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

【讨论】:

以上是关于如何在 DataGridTemplateColumn 内的控件上双向绑定属性?的主要内容,如果未能解决你的问题,请参考以下文章

如何在表单提交后保留文本(如何在提交后不删除自身?)

如何在异步任务中调用意图?或者如何在 onPostExecute 中开始新的活动?

在 Avkit 中如何使用这三行代码,以及如何将音乐静音”

如何在 JDBC 中启动事务?

如何在 Fragment 中调用 OnActivityResult 以及它是如何工作的?

如何使用 Firebase 在 Web 上托管 Flutter?它的效果如何?