none
DataRowView.PropertyChanged event doesn't raise RRS feed

  • Question

  • Hi Guys,

    I have a very strange problem...

    I create a DataSet with two tables and a master/detail relation (constraint enabled).
    In one column of the master table I add an expression column that calculates the grandtotal of the detail rows: "Sum(Child(FKHeader).Total)"

    When I insert a new record in master DataTable, I set the "IDHeader" field (which is primary key and the key of the relation) to -1, and the detail rows are inserted with this IDHeader value too.

    When the user wants to save the records, I change the IDHeader value of the master record to assign a real ID, and automatically it changes also in the detail records (because of the constraints).

    Now, here comes two problems:

    1) the DataRowView I use to present (in WPF) the master row doesn't get notified of the IDHeader column change, and the interface is not updated (the user still see "-1" as the value IDHeader)

    2) The PropertyChanged event of DataRowView doesn't raise for the IDHeader property

    I think that the main problem is the second, and the first is dependent upon it.

    After many tests and much headache I found that the problem was the Expression column that calculates the grandtotal, if I remove the Expression, the IDHeader column is notified again and all works correctly.

    I checked also the DataTable.ColumnChanged event, and this event is always raised, with or without the Expression column, so the problem is in the DataRowView class.

    I think it is a framework bug. I tested it under framework 4.0 and also on 3.5, and nothing changed.

    Here is the code to reproduce the error:

    DataSet definition:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Data;
    using System.ComponentModel;
    
    namespace TestRelatedViewError
    {
    	public class TestDataSet : DataSet
    	{
    		public TestDataSet(bool enablegrandtotal)
    			: base()
    		{
    			//create the tables
    			DataTable dtlog = new DataTable("Log");
    			dtlog.Columns.Add("Time", typeof(DateTime));
    			dtlog.Columns.Add("Log", typeof(string));
    			this.Tables.Add(dtlog);
    			this.LogView = dtlog.DefaultView;
    
    			DataTable dtheader = new DataTable("Header");
    			dtheader.Columns.Add("IDHeader", typeof(decimal)).AllowDBNull = false;
    			dtheader.Columns.Add("Text", typeof(string));
    			dtheader.PrimaryKey = new DataColumn[] { dtheader.Columns["IDHeader"] };
    
    			DataTable dtrows = new DataTable("Rows");
    			dtrows.Columns.Add("IDRow", typeof(decimal)).AllowDBNull = false;
    			dtrows.Columns.Add("IDHeader", typeof(decimal)).AllowDBNull = false;
    			dtrows.Columns.Add("ProductCode", typeof(string)).AllowDBNull = false;
    			dtrows.Columns.Add("Quantity", typeof(decimal)).AllowDBNull = false;
    			dtrows.Columns.Add("Price", typeof(decimal)).AllowDBNull = false;
    			//add an expression to calculate the total
    			dtrows.Columns.Add("Total", typeof(decimal), "Quantity*Price");
    
    			//ad tables and relations
    			this.Tables.Add(dtheader);
    			this.Tables.Add(dtrows);
    			//relation between rows and header with Constraints active
    			this.Relations.Add(new DataRelation("FKHeader", dtheader.Columns["IDHeader"], dtrows.Columns["IDHeader"], true));
    
    			string expression = "0";
    			if (enablegrandtotal)
    			{
    				expression = "Sum(Child(FKHeader).Total)";
    			}
    			dtheader.Columns.Add("GrandTotal", typeof(decimal), expression);
    
    			//add a row to header
    			DataRow rowheader = dtheader.NewRow();
    			rowheader["IDHeader"] = -1;
    			if (enablegrandtotal)
    			{
    				rowheader["Text"] = "Test with error";
    			}
    			else
    			{
    				rowheader["Text"] = "Test without error";
    			}
    			dtheader.Rows.Add(rowheader);
    			//assign the RowViewHeader to the only one row in the table
    			this.RowViewHeader = dtheader.DefaultView[0];
    			//create a RelatedView to show and maintain the child rows
    			this.RowsView = this.RowViewHeader.CreateChildView("FKHeader");
    
    			//add some rows
    			for (int i = 0; i < 10; i++)
    			{
    				DataRow row = dtrows.NewRow();
    				row["IDRow"] = -i;
    				row["IDHeader"] = rowheader["IDHeader"];
    				row["ProductCode"] = string.Format("Product{0}", i);
    				row["Quantity"] = i;
    				row["Price"] = i * 20;
    				dtrows.Rows.Add(row);
    			}
    
    			//add event handlers to log changes
    
    			//ColumnChanged event raises every time
    			dtheader.ColumnChanged += (lsender, le) =>
    			{
    				switch (le.Column.ColumnName)
    				{
    					case "IDHeader":
    						this.AddLog(string.Format("ColumnChanged passed, enablegrandtotal: {0}", enablegrandtotal));
    						if (enablegrandtotal)
    						{
    							if (this.RowViewHeader != null)
    							{
    								this.RowsView = this.RowViewHeader.CreateChildView("FKHeader");
    							}
    						}
    						break;
    				}
    			};
    
    			//PropertyChanged event raises only when enablegrandtotal is false
    			this.RowViewHeader.PropertyChanged += (lsender, le) =>
    			{
    				if (le.PropertyName == "IDHeader")
    				{
    					this.AddLog(string.Format("PropertyChanged passed, enablegrandtotal: {0}", enablegrandtotal));
    					this.RowsView = this.RowViewHeader.CreateChildView("FKHeader");
    				}
    			};
    		}
    
    		#region Properties
    		private DataRowView FRowViewHeader = null;
    		public DataRowView RowViewHeader
    		{
    			get { return this.FRowViewHeader; }
    			set
    			{
    				this.FRowViewHeader = value;
    				this.OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("RowViewHeader"));
    			}
    		}
            
    		private DataView FRowsView = null;
    		public DataView RowsView
    		{
    			get { return this.FRowsView; }
    			set
    			{
    				this.FRowsView = value;
    				this.OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("RowsView"));
    			}
    		}
    
    		private DataView FLogView = null;
    		public DataView LogView
    		{
    			get { return this.FLogView; }
    			set
    			{
    				this.FLogView = value;
    				this.OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("LogView"));
    			}
    		}
    		#endregion
    
    		#region Methods
    		private void AddLog(string text)
    		{
    			DataTable dtlog = this.Tables["Log"];
    			dtlog.Rows.Add(DateTime.Now, text);
    		}
    		#endregion
    
    		#region INotifyPropertyChanged members
    		public event PropertyChangedEventHandler PropertyChanged;
    		protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    		{
    			if (this.PropertyChanged != null)
    			{
    				this.PropertyChanged(this, e);
    			}
    		}
    		#endregion
    
    	}
    }
    
    


    Xaml presentation, Window.xaml:

    <Window x:Class="TestRelatedViewError.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">
        <DockPanel>
            <StackPanel Orientation="Vertical" DockPanel.Dock="Right">
                <CheckBox x:Name="chkDoError" Content="Enable GrandTotal Expression"/>
                <Button Content="1) Populate" Click="Populate_Click"/>
                <Button Content="2) Change IDHeader" Click="ChangeIDHeader_Click"/>
            </StackPanel>
            <TextBlock DockPanel.Dock="Top" TextWrapping="Wrap">
                When you enable GrandTotal Expression:
                <LineBreak/>
                1) the PropertyChanged Event of DataRowView never get called when IDHeader changes
                <LineBreak/>
                2) And also the IDHeader Binding at the bottom doesn't change
            </TextBlock>
            <StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom">
                <StackPanel.Resources>
                    <Style TargetType="{x:Type TextBlock}">
                        <Setter Property="Margin" Value="2"/>
                    </Style>
                </StackPanel.Resources>
                <TextBlock Text="Header: " FontWeight="Bold" Foreground="Red"/>
                <TextBlock Text="{Binding Path=RowViewHeader.IDHeader}" Foreground="Red"/>
                <TextBlock Text="Text: " FontWeight="Bold"/>
                <TextBlock Text="{Binding Path=RowViewHeader.Text}"/>
                <TextBlock Text="GrandTotal: " FontWeight="Bold"/>
                <TextBlock Text="{Binding Path=RowViewHeader.GrandTotal}"/>
            </StackPanel>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="*"/>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>
                <ListView x:Name="lsvRows" ItemsSource="{Binding Path=RowsView}" Grid.Row="0">
                    <ListView.View>
                        <GridView>
                            <GridViewColumn Header="IDRow" DisplayMemberBinding="{Binding Path=IDRow}"/>
                            <GridViewColumn Header="IDHeader" DisplayMemberBinding="{Binding Path=IDHeader}"/>
                            <GridViewColumn Header="ProductCode" DisplayMemberBinding="{Binding Path=ProductCode}"/>
                            <GridViewColumn Header="Quantity" DisplayMemberBinding="{Binding Path=Quantity}"/>
                            <GridViewColumn Header="Price" DisplayMemberBinding="{Binding Path=Price}"/>
                            <GridViewColumn Header="Total" DisplayMemberBinding="{Binding Path=Total}"/>
                        </GridView>
                    </ListView.View>
                </ListView>
                <ListBox x:Name="lsbLog" ItemsSource="{Binding Path=LogView}" DisplayMemberPath="Log" Grid.Row="1"/>
            </Grid>
        </DockPanel>
    </Window>
    
    


    Window.xaml.cs:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace TestRelatedViewError
    {
    	/// <summary>
    	/// Logica di interazione per MainWindow.xaml
    	/// </summary>
    	public partial class MainWindow : Window
    	{
    		public MainWindow()
    		{
    			InitializeComponent();
    		}
    
    		private void Populate_Click(object sender, RoutedEventArgs e)
    		{
    			this.DataContext = new TestDataSet(chkDoError.IsChecked.Value);
    		}
    
    		private void ChangeIDHeader_Click(object sender, RoutedEventArgs e)
    		{
    			TestDataSet ds = (TestDataSet)this.DataContext;
    			ds.RowViewHeader["IDHeader"] = 1;
    			BindingExpression b = lsvRows.GetBindingExpression(ListView.ItemsSourceProperty);
    			b.UpdateTarget();
    		}
    	}
    }
    
    

    Click the "Populate" button to populate the DataSet, then the "Change IDHeader" to change the value of the IDHeader.

    You will notice that when "Enable GrandTotal Expression" is checked, the PropertyChanged event doesn't get logged, because it is not raised.

    Regards

    Francesco

     

     

     

     

    Wednesday, October 19, 2011 12:20 PM

All replies

  • Hi Formentz,

          I'm debugging your code now, it's really like you said, but I have not resolve it now, I'll research it and solve the problem ASAP.

    Best Regards

    Allen


    Allen Li [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Friday, October 21, 2011 9:30 AM
    Moderator
  • Hi Francesco,

     

    I tried your sample, but the result is PropertyChanged got fired.

    The problem is if (le.PropertyName == "IDHeader"), but if you have "Enable GrandTotal Expression" checked, at the first time you click 2)change IDHeader, the PropertyName is GrandTotal but not IDHeader, so the following code is not called:

                        this.AddLog(string.Format("PropertyChanged passed, enablegrandtotal: {0}", enablegrandtotal));
                        this.RowsView = this.RowViewHeader.CreateChildView("FKHeader");

    If we click 2)change IDHeader again, you will see the expected output.

    Do the following actions with your sample to understand the above behavior:

    1. Set a breakpoint at if (le.PropertyName == "IDHeader")

    2. Press F5 to start debugging.

    3. check "Enable GrandTotal Expression"

    4. Click 1)Populate

    5. Click 2)change IDHeader

    6. repeat #4 and #5 twice. see what happens.

    I think there should be some pending changes on GrandTotal.

     

     

     


    Alan Yao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, October 24, 2011 8:57 AM
  • Hi Alan,

    sorry for the late answer, I didn't notice that you answered :(

    You are right, but the problem here is not that the PropertyChanged is not fired at all, but that the PropertyChanged of the property "IDHeader" is not fired, "GrandTotal" gets notified correctly but IDHeader is not.

    If you try the steps you suggested you'll notice that only "GrandTotal" is notified and not "IDHeader", for this reason in the interface the textblock "IDHeader: -1" remains unchanged when "Enable GrandTotal Expression" is checked, but it changes to "1" when the checkbox is unchecked.

    Regards

    Francesco

    Thursday, November 10, 2011 1:14 PM