Datagrid dirty?

Nov 27, 2008 at 4:52 PM
Hi

I have a datagrid bound to a datatable returning a string and 3 booleans. As the datatable can vary in number of columns returned, I decided to autgenerate the cols.

I like to check if there are unsaved changes in the grid before starting a function.
Therefore I check my  datacontext using datatable.GetChanges, which works fine if I change a boolean value and leave the gridrow in which the change took place.
If I leave the cursor in the changed row, the datatables has no changes.
It seems to me that the control did not update the Binding source in this case.

I already tried to do this using the following function that works well for textboxes:

   Protected Sub UpdateFocusedField()

      Dim oFwE As FrameworkElement = TryCast(Keyboard.FocusedElement, FrameworkElement)

      If oFwE IsNot Nothing Then

         Dim oExpr As BindingExpression = Nothing

         If TypeOf oFwE Is TextBox Then
            oExpr = oFwE.GetBindingExpression(TextBox.TextProperty)
         ElseIf TypeOf oFwE Is CheckBox Then
            oExpr = oFwE.GetBindingExpression(CheckBox.IsCheckedProperty)
         End If

         If oExpr IsNot Nothing Then
            oExpr.UpdateSource()
         End If

      End If

   End Sub

But I am not quite sure if I use the correct Prop in the line
            oExpr = oFwE.GetBindingExpression(CheckBox.IsCheckedProperty)

To make it short, the source is not updated when I leave the cursor in the changed gridrow

My questions:
1) Is there a better way (maybe does the datagrid itself has some knowledge of "dirty" data)?
2) If not, is there a way to update the source, maybe by setting the updatesourcetrigger of the binding to "PropertyChanged"? Maybe you can post me an example of how to achieve this using a setter? (I use autogenerate on the columns)

Regards
Klaus

 

Nov 27, 2008 at 5:10 PM
Hi,

I have written an article which details how to synchronise a bound DataGrid with a DataSet (i.e. DataTables), this might be of interested to you:

http://www.codeproject.com/KB/WPF/WPFDataGridExamples.aspx

The reason why you are not seeing the datasource updated if you leave the cursor in the current row (which you are editing) is due to the transactional behaviour of the DataGrid. Changes are committed to a Row when it loses focus. However, this is only teh case if the item bound to the row implements IEditableObject, which is the case when you bind a DataTable (it is actually the DefaultView that is bound).

All of this is described in the above article.

If you have any problems - give me a shout.

Regards,
Colin E.
--
http://wpfadventures.wordpress.com/ - my WPF blog


Nov 28, 2008 at 10:48 AM
Colin

thanks for your (updated) article link, very helpful indeed.

But my problem still remains:

When I get it right, a row change implicitely "updates" my datasource.
So if I want to react to changes without changing the row, I have to explicitly update my source.
Therefore I have a function called UpdateFocusedField that I send you in the initial post for this discussion.

As I found out in the meantime, this function works perfectly for grids that are bound to an observable collection of objects.
There, I do not have to change the row to "see" changes.
My problem is still  my datagrid that is bound to a datatable. This datatable is completely generic, as it can have a variable number of columns (I'll paste the code below)
In this grid, the source is only updated when I leave the changed row. Even a call of UpdateFocusedField does not change this behaviour. Only leaving the row helps.

Many thanks for your support
Regards
Klaus

generic datatable code:
   Public Function GetUserGrpModulesTable() As DataTable

      Dim oUserGrpAction As New BEUserGroupActions
      Dim lUsed As Long = -1
      Dim oActionModule As New DS.BEModuleActions
      Dim oGrpList As ObservableCollection(Of BE.BEUserGroup) = oUserGrpAction.GetList()
      Dim dt As New DataTable("UserGrpModules")

      Try
         dt.Columns.Add("module_name", Type.GetType("System.String"))
         For Each oGrp In oUserGrpAction.GetList()
            dt.Columns.Add(UCase(oGrp.UserGrpName), Type.GetType("System.Boolean"))
         Next

         Dim pk As DataColumn() = New DataColumn(0) {}
         pk(0) = dt.Columns("module_name")
         dt.PrimaryKey = pk
         dt.Columns("module_name").ReadOnly = True

         For Each oModule In oActionModule.GetList
            If Not oModule.PrivateYN Then
               Dim dr As DataRow
               dr = dt.NewRow()
               dr("module_name") = oModule.ModuleName
               For Each oGrp In oUserGrpAction.GetList()
                  Call GS.GetObject.CurrCompDB.ExecuteScalar(String.Format("SELECT COUNT(*) FROM BUSERGRPMODULES WHERE user_grp = '{0}' and module_name = '{1}'", oGrp.UserGrpName, oModule.ModuleName), lUsed)
                  dr(UCase(oGrp.UserGrpName)) = (lUsed > 0)
               Next
               dt.Rows.Add(dr)
            End If
         Next
      Catch ex As Exception

      End Try

      Call dt.AcceptChanges()

      Return dt

   End Function

Nov 28, 2008 at 11:52 AM
Hi,

The key difference here is that when you bind to a Datatable, each row is bound to a DataRowView. This class implements IEditableObject, whereas I am assuming the objects in your collection do not?

This interface allows transactional editing. The DataGrid is implemented to invoked the IEditableObject.EndEdit when a row is committed (e.g. by leaving the row or hitting Enter).

If you want to 'see' the changes to your data you will have to invoke the IEditableObject.EndEdit method yourself as each cell looses focus.

Colin E.
--
http://wpfadventures.wordpress.com/ - my WPF blog.
Nov 28, 2008 at 12:05 PM
Colin
I am completely confused:
I have one datagrid bound to an observable collection of my objects. Here I call UpdateFocusedField when loosing focus and "see" the changes without leaving the row.
A second datagrid is bound to a datatable. Here I do not see changes before leaving the row even if I call UpdateFocusedField when focus leaves
From your answer I would assume that the datatable grid should not be the problem, but the collection grid! But that is not the case!
Regards
Klaus


From: ColinEber [mailto:notifications@codeplex.com]
Sent: Freitag, 28. November 2008 12:53
To: Klaus Wiesel
Subject: Re: Datagrid dirty? [wpf:40956]

From: ColinEber

Hi,

The key difference here is that when you bind to a Datatable, each row is bound to a DataRowView. This class implements IEditableObject, whereas I am assuming the objects in your collection do not?

This interface allows transactional editing. The DataGrid is implemented to invoked the IEditableObject.EndEdit when a row is committed (e.g. by leaving the row or hitting Enter).

If you want to 'see' the changes to your data you will have to invoke the IEditableObject.EndEdit method yourself as each cell looses focus.

Colin E.
--
http://wpfadventures.wordpress.com/ - my WPF blog.
Nov 28, 2008 at 12:28 PM
Hi,

If you change your object in your collection into an IEditableObject as per the following:

http://blogs.infragistics.com/blogs/joshs/archive/2008/05/08/creating-objects-that-support-edit-cancellation-via-ieditableobject.aspx

You will no doubt see the same problem there.

It is like using a database, if you start a transaction and make some changes, you will not see the results if you open another connection until the transaction is committed.
Colin E.


Nov 28, 2008 at 1:09 PM
Colin

thanks for your patience, but I still do not see the solution for my datatable bound grid.

How can I update my source without leaving the row?

Regards
Klaus
Nov 28, 2008 at 2:34 PM
Hi,

As I mentioned, the solution lies with committing the changes to your row as you move from cell to cell. Try this example:

<Window.Resources>        
    <!-- create an instance of our DataProvider class -->
    <ObjectDataProvider x:Key="CustomerDataProvider" ObjectType="{x:Type local:CustomerDataProvider}"/>
    <!-- define the method which is invoked to obtain our data -->
    <ObjectDataProvider x:Key="Customers" ObjectInstance="{StaticResource CustomerDataProvider}" MethodName="GetCustomers"/>
</Window.Resources>

<StackPanel DataContext="{Binding Source={StaticResource Customers}}">        
    <dg:DataGrid Height="200"  ItemsSource="{Binding}" Name="dataGrid" IsSynchronizedWithCurrentItem="true"
              CellEditEnding="dataGrid_CellEditEnding"  CurrentCellChanged="dataGrid_CurrentCellChanged"/>        
    <TextBox Text="{Binding Path=CompanyName}"/>        
</StackPanel>

i.e. a DataGrid - bound in code behind to a DataTable (Customers from the Northwind database). I also have a TextBox bound to the current item displaying the CompanyName.

In code-behind ... when the cell edit is ending, I record the current row:

private DataRowView rowBeingEdited = null;

private void dataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
    DataRowView rowView = e.Row.Item as DataRowView;
    rowBeingEdited = rowView;
}

The key thing to realise is that this event is fired when editing is ENDING and not ENDED - this throws me all the time! i.e. the cell data has not been written to your bound object yet!!!

When I move to another cell I commit changes then re-start editing:

private void dataGrid_CurrentCellChanged(object sender, EventArgs e)
{
    if (rowBeingEdited != null)
    {
        rowBeingEdited.EndEdit();
        rowBeingEdited.BeginEdit();
    }
}

With this code in place you should see that the textbox which displays company name is updated before your move to the next row.

Regards,
Colin E.
--
http://wpfadventures.wordpress.com/ - my WPF blog
http://www.codeproject.com/KB/WPF/WPFDataGridExamples.aspx - WPF DataGrid Practical Examples





Nov 28, 2008 at 4:21 PM
Colin
we are close:
After inserting the two events I see changes after leaving a changed cell but staying in the changed row.
But when I only click the checkbox and stay in the very cell, I have the same effect as before.
Maybe this is due to the fact that I only have checkboxes in my datagrid?
Another thing that puzzles me: CellEditEnding is also called if I leave the checkbox cell without changing the checkbos "isselected"
Regards
Klaus


From: ColinEber [mailto:notifications@codeplex.com]
Sent: Freitag, 28. November 2008 15:34
To: Klaus Wiesel
Subject: Re: Datagrid dirty? [wpf:40956]

From: ColinEber

Hi,

As I mentioned, the solution lies with committing the changes to your row as you move from cell to cell. Try this example:

<Window.Resources>
<!-- create an instance of our DataProvider class -->
<ObjectDataProvider x:Key="CustomerDataProvider" ObjectType="{x:Type local:CustomerDataProvider}"/>
<!-- define the method which is invoked to obtain our data -->
<ObjectDataProvider x:Key="Customers" ObjectInstance="{StaticResource CustomerDataProvider}" MethodName="GetCustomers"/>
</Window.Resources>

<StackPanel DataContext="{Binding Source={StaticResource Customers}}">
<dg:DataGrid Height="200" ItemsSource="{Binding}" Name="dataGrid" IsSynchronizedWithCurrentItem="true"
CellEditEnding="dataGrid_CellEditEnding" CurrentCellChanged="dataGrid_CurrentCellChanged"/>
<TextBox Text="{Binding Path=CompanyName}"/>
</StackPanel>

i.e. a DataGrid - bound in code behind to a DataTable (Customers from the Northwind database). I also have a TextBox bound to the current item displaying the CompanyName.

In code-behind ... when the cell edit is ending, I record the current row:

private DataRowView rowBeingEdited = null;

private void dataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
DataRowView rowView = e.Row.Item as DataRowView;
rowBeingEdited = rowView;
}

The key thing to realise is that this event is fired when editing is ENDING and not ENDED - this throws me all the time! i.e. the cell data has not been written to your bound object yet!!!

When I move to another cell I commit changes then re-start editing:

private void dataGrid_CurrentCellChanged(object sender, EventArgs e)
{
if (rowBeingEdited != null)
{
rowBeingEdited.EndEdit();
rowBeingEdited.BeginEdit();
}
}

With this code in place you should see that the textbox which displays company name is updated before your move to the next row.

Regards,
Colin E.
--
http://wpfadventures.wordpress.com/ - my WPF blog
http://www.codeproject.com/KB/WPF/WPFDataGridExamples.aspx - WPF DataGrid Practical Examples





Dec 11, 2008 at 4:32 PM
Colin

I am getting back to this issue because I want to know if there is an other way to solve this:

Let me just explain my scenario:

I have a number of usergroups and a number of modules.
Each user group can use any number of modules, so I'd like to present a matrix containing checkboxes in my datagrid.

As I do not know how many usergroups there are, and also the number of modules is not known, I decided to create a datatable on the fly and bind this to my datagrid.

Is there any alternative over this? Is there a chance to create a class (or at least new properties of a predefined class) "on the fly"?

Regards
Klaus
Dec 12, 2008 at 7:36 AM
Could you perhaps use an array of bool values (to store the modules selections for each user group), and generate the XAML for the column specifications on the fly depending on how many modules there are?
Jan 21, 2009 at 5:28 PM
Hi

could you provide an example for this?

Regards
Klaus