【问题标题】:Two-way binding in DataGrid: source not updatedDataGrid 中的双向绑定:源未更新
【发布时间】:2014-12-30 15:36:52
【问题描述】:

我有一个双向绑定的问题,从源到目标都可以正常工作,但是目标上的更新永远不会传播到源。

我在 DataGrid 中显示了一个自定义 UserControl,它显示了带有星号的评分:

* View.xaml

    <DataGrid x:Name="Datagrid" Style="{StaticResource ResourceKey=DataGridStyle}" Grid.Row="1" AutoGenerateColumns="False" IsReadOnly="True" ItemsSource="{Binding Path=GameList}" SelectedItem="{Binding Path=SelectedGame}" SelectionChanged="datagrid_SelectionChanged">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Title" Binding="{Binding Title}" />
            <DataGridTemplateColumn Header="Rating" SortMemberPath="Rating">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <controls:RatingControl NumberOfStars="{Binding Path=Rating}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>

* RatingControl.xaml.cs

public partial class RatingControl : UserControl
{
    #region Public Dependency properties
    public int NumberOfStars
    {
        get { return (int)GetValue(NumberOfStarsProperty); }
        set { if ((int)GetValue(NumberOfStarsProperty) != value) { SetValue(NumberOfStarsProperty, value); } }
    }

    public static readonly DependencyProperty NumberOfStarsProperty =
        DependencyProperty.Register("NumberOfStars", typeof(int), typeof(RatingControl), 
          new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnRatingChanged));

    #endregion

    [...]

    private void Border_PreviewMouseDown(object sender, MouseButtonEventArgs e)
    {
        e.Handled = true;
        NumberOfStars = tempRating;
    }

* Game.cs

public class Game : INotifyPropertyChanged
{
    private int _rating;
    public int Rating
    {
        get { return _rating; }
        set { _rating = value; RaiseChangedEvent("Rating"); }
    }
    public event PropertyChangedEventHandler PropertyChanged;

    public void RaiseChangedEvent(string propertyName)
    {
        if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

当我单击 RatingControl 上的星号时,我更新了 NumberOfStars 依赖属性,但我的模型的 Rating 属性没有更新。我错过了什么?

【问题讨论】:

  • {Binding Path=Rating, Mode=TwoWay} here 或在注册控件的依赖属性时将 TwoWay 绑定模式设置为默认值。
  • 不,你读我的问题有点太快了。这不是必需的,因为我在 DependencyProperty 中指定了 FrameworkPropertyMetadataOptions.BindsTwoWayByDefault。
  • 是的,请原谅我。顺便说一句,你不应该检查这个'if ((int)GetValue(NumberOfStarsProperty) != value',因为设计者绕过了属性访问。
  • 我不记得 FrameworkMetadataOptions 的默认更新触发器是什么...
  • 我想是 PropertyChanged...

标签: c# wpf xaml data-binding wpfdatagrid


【解决方案1】:

当使用单元格模板等时,这似乎是 DataGrid 中的一个错误。无论出于何种原因,除非明确设置了 UpdateSourceTrigger,否则源将不会被更新。奇怪的是,UpdateSourceTrigger 将设置为“默认”,但其行为却像是设置为“显式”。

为了演示错误或意外行为,我以您的示例为例,并对其进行了一些调整。您可以单击各种用户控件,然后查看默认的两种方式绑定适用于所有内容,但网格中的项目除外。

注意:
此答案的其余部分只是一个代码转储,因此如果您对此答案满意,请不要再阅读。

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">

    <StackPanel>

        <DataGrid x:Name="grid" Grid.Row="1" AutoGenerateColumns="False" IsReadOnly="True" ItemsSource="{Binding Path=GameList}" MinHeight="100" >
            <DataGrid.Columns>
                <DataGridTextColumn Header="Title" Binding="{Binding Title}" />
                <DataGridTemplateColumn Header="Rating">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <local:MyControl Number="{Binding Path=Rating}" Loaded="TemplateControlLoaded" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>

        <ListBox Name="lst" ItemsSource="{Binding GameList}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <ListBoxItem>
                        <local:MyControl Number="{Binding Rating}" />
                    </ListBoxItem>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <local:MyControl x:Name="ctl" Loaded="MyControl_Loaded" Number="{Binding Path=Rating}" />
    </StackPanel>

</Window>

MainWindow.xaml.cs

// ============================================================================================================================
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            grid.DataContext = new MyClassContainer();
            lst.DataContext = new MyClassContainer();
            ctl.DataContext = new MyClass();
        }

        private void TemplateControlLoaded(object sender, RoutedEventArgs e)
        {
            BindingExpression be = (sender as FrameworkElement).GetBindingExpression(MyControl.NumberProperty);
            Console.WriteLine("Template trigger = " + be.ParentBinding.UpdateSourceTrigger.ToString());
        }

        private void MyControl_Loaded(object sender, RoutedEventArgs e)
        {
            BindingExpression be = (sender as FrameworkElement).GetBindingExpression(MyControl.NumberProperty);
            Console.WriteLine("Instance trigger = " + be.ParentBinding.UpdateSourceTrigger.ToString());
        }
    }


    // ============================================================================================================================
    public class MyClassContainer
    {
        public List<MyClass> GameList { get; set; }

        public MyClassContainer()
        {
            GameList = new List<MyClass>();
            GameList.Add(new MyClass()
            {
                Rating = 150
            });
        }
    }


    // ============================================================================================================================
    public class MyClass : INotifyPropertyChanged
    {


        private int _Rating;
        public int Rating
        {
            get { return _Rating; }
            set
            {
                _Rating = value; NotifyChange("Rating");
                Console.WriteLine("changed!");
            }
        }

        private void NotifyChange(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }


        public event PropertyChangedEventHandler PropertyChanged;
    }

MyControl.xaml

   <UserControl x:Class="WpfApplication1.MyControl"
                 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="300" d:DesignWidth="300" x:Name="this">
        <Grid Background="Red" PreviewMouseDown="Grid_PreviewMouseDown">
            <TextBlock Text="{Binding ElementName=this, Path=Number}" FontWeight="Bold" />
        </Grid>
    </UserControl>

MyControl.xaml.cs

public partial class MyControl : UserControl
    {
        public MyControl()
        {
            InitializeComponent();
        }

        public static readonly DependencyProperty NumberProperty = DependencyProperty.Register("Number", typeof(int), typeof(MyControl), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
        public int Number
        {
            get { return (int)GetValue(NumberProperty); }
            set { SetValue(NumberProperty, value); }
        }

        private void Grid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            e.Handled = true;
            this.Number = 1000;
        }

    }

【讨论】:

  • 非常好的答案。事实上,我什至尝试在 DependencyProperty 注册中强制 UpdateSourceTrigger = PropertyChanged,但它也没有按预期工作。
  • 我在 WPF 源代码中挖掘了一段时间以弄清楚到底发生了什么,但很快就放弃了并称之为“错误”:) 我猜这些单元格模板会以某种方式干扰......
  • 其实可能不是bug...DatagridColumn 主要有2 个子类型:DatagridTemplateColumn 和DatagridBoundColumn。基本上,DatagridTemplateColumn 没有实现必要的绑定工具......这篇文章让我意识到:blogs.msdn.com/b/vinsibal/archive/2008/08/19/….
  • 是的,我确信这不是一个适当的“错误”,但它肯定是出乎意料的行为。另一方面,MS 习惯于使用旧的“这不是一个错误,它是一个功能”的借口来解释这样的事情。无论如何,很高兴它成功了,我们都必须学习一些东西。
【解决方案2】:

StaticResource 更改为 DynamicResource

【讨论】:

  • 我在发布游戏课程时遗漏了几行。我用完整的代码更新了我的问题,这似乎与你的非常相似。
  • 关于 Mode=TwoWay,请参阅上述问题的 cmets。
  • 感谢您的反馈,我更改了答案。
猜你喜欢
  • 1970-01-01
  • 2015-04-26
  • 1970-01-01
  • 1970-01-01
  • 2023-03-13
  • 2021-01-31
  • 1970-01-01
  • 1970-01-01
  • 2013-06-01
相关资源
最近更新 更多