简体   繁体   English

DataGrid中的双向绑定:源未更新

[英]Two-way binding in DataGrid: source not updated

I have an issue on a two-way binding that works fine from source to target, but updates on the target are never propagated to the source. 我在双向绑定中遇到一个问题,该绑定从源到目标都可以正常工作,但是目标上的更新永远不会传播到源。

I display in a DataGrid a custom UserControl that shows a rating with stars: 我在DataGrid中显示一个自定义UserControl,该控件显示带有星号的等级:

* View.xaml * 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 * 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 * 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));
    }
}

When I click on a star on RatingControl, I update the NumberOfStars dependency property, but the Rating property of my model is not updated. 当我单击RatingControl上的星形时,我会更新NumberOfStars依赖项属性,但不会更新模型的Rating属性。 What am I missing ? 我想念什么?

This appears to be a bug in DataGrid when using cell templates, etc. For whatever reason, the source will simply not be updated unless UpdateSourceTrigger is explicitly set. 使用单元模板等时,这似乎是DataGrid中的错误。无论出于何种原因,除非显式设置UpdateSourceTrigger,否则根本不会更新源。 What is odd is that the the UpdateSourceTrigger will be set to 'Default' but acts like it is set to 'Explicit'. 奇怪的是,UpdateSourceTrigger将被设置为“默认”,但其行为类似于被设置为“显式”。

To demonstrate the bug, or unexpected behaviour, I have taken your example, and tweaked it a bit. 为了演示该错误或意外行为,我以您的示例为例,并对其进行了一些调整。 You can click on the various user controls and see that the default two way bindings work for everything, except for those items in the grid. 您可以单击各种用户控件,然后看到默认的双向绑定适用于除网格中的那些项目以外的所有内容。

NOTE: 注意:
The rest of this answer is just a code dump, so if you are satisfied with this answer, read no further. 该答案的其余部分只是一个代码转储,因此,如果您对此答案感到满意,请不要继续阅读。

MainWindow.xaml 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 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 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 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;
        }

    }

StaticResource更改为DynamicResource

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM