简体   繁体   中英

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:

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

When I click on a star on RatingControl, I update the NumberOfStars dependency property, but the Rating property of my model is not updated. 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. What is odd is that the the UpdateSourceTrigger will be set to 'Default' but acts like it is set to 'Explicit'.

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

<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;
        }

    }

StaticResource更改为DynamicResource

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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