Search code examples
c#wpfxamldatagrid

WPF DataGrid updating cell style after editing


In XAML I have a column in a DataGrid that is defined like this:

<DataGridTextColumn Header="Name" Binding="{Binding Name}">
    <DataGridTextColumn.CellStyle>
        <Style TargetType="DataGridCell" BasedOn="{StaticResource
               {x:Type DataGridCell}}" >
            <Setter Property="Background" Value="{Binding
                    Converter={StaticResource NameToBrushConverter}}"/>
        </Style>
    </DataGridTextColumn.CellStyle>
</DataGridTextColumn>

The NameToBrushConverter returns a color when the Name column and the FirstName column have the same content. Otherwise it returns DependencyProperty.UnsetValue.

The problem I'm facing is that if I edit a cell and end editing, the style is not updated. Only if the newly entered value moves to a different line (because of sorting) the conditional coloring of the background is applied. But if after editing the object is displayed in the same row of the DataGrid the background color get's not updated until I click on sort. As soon as the cell moves to a different row the background will be updated according to the Converter value.

Implementing INotifyPropertyChanged for the object doesn't help.

Is there a way to tell the GridView that it has to reevaluate the styling after editing a cell?

dataGrid.Items.Refresh();

Calling refresh helps, but which is the right event to trigger a refresh? I tried it in CellEditEnding, but got an exception Refresh is not allowed in AddNew- or EditItem transactions.


Solution

    1. You need to set UpdateSourceTrigger to PropertyChanged because it is by default set to LostFocus within a DataGrid.

    2. I'm guessing that you do not have a CellEditTemplate.

    3. This is the biggest issue of all : You would have to use multi-binding on your properties with a relevant Converter. The only reason this is working now is because when lost focus occurs the binding in the current cell refresh and gets your item (i.e. binding or binding to Path=), passing it to the converter and outputting some color.

    Edit:

    I know see that I put the UpdateSourceTrigger on the wrong binding. Place it on the Name above and in your cell style also bind to Name.

    XAML:

     <DataGrid>
        <DataGrid.Columns>
            <DataGridTextColumn Header="Name" Binding="{Binding Name,
                                UpdateSourceTrigger=PropertyChanged}">
                <DataGridTextColumn.CellStyle>
                    <Style TargetType="DataGridCell"
                           BasedOn="{StaticResource {x:Type DataGridCell}}" >
                        <Setter Property="Background" Value="{Binding Name,
                                Converter={StaticResource
                                NameToBrushConverter}}"/>
                    </Style>
                </DataGridTextColumn.CellStyle>
            </DataGridTextColumn>
        </DataGrid.Columns>
    </DataGrid> 
    

    Further more, as for your question below. The only reason it was working in partially when moving between rows is because of the default UpdateSourceTrigger for every binding nested in a DataGrid, which is LostFocus.

    When Binding to the current DataContext using :

    <SomeElement Tag={Binding}/>
    

    Or:

    <SomeElement Tag={Binding Path=.}/>
    

    You are not binding to a property. The Binding is evaluated when :

    1. The DependencyObject initializes and all it's DP's are evaluated. It gets it's value for the first time.

    2. UpdateSourceTrigger=LostFocus (The default inside the DataGrid) it is eventuated on LostFocus. This is why your Binding is evaluated when you pass between rows.

    3. UpdateSourceTrigger=PropertyChanged. If you want it on your DataContext you would have to explicitly set a property which would return itself and call it when the name property changes.

    Something like this :

    C#:

    public class Entity : INotifyPropertyChanged
    {
        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                PropertyChanged(this,
                    new PropertyChangedEventArgs("Name"));
                PropertyChanged(this,
                    new PropertyChangedEventArgs("Self"));
            }
        }
        private string _firstName;
        public string FirstName
        {
            get { return _firstName; }
            set
            {
                _firstName = value;
                PropertyChanged(this,
                    new PropertyChangedEventArgs("FirstName"));
                PropertyChanged(this,
                    new PropertyChangedEventArgs("Self"));
            }
        }
        public Entity Self { get { return this; } }
        public event PropertyChangedEventHandler PropertyChanged =
            delegate { };
    }
    

    XAML:

    <Style TargetType="DataGridCell" BasedOn="{StaticResource
           {x:Type DataGridCell}}" >
        <Setter Property="Background" Value="{Binding
                Self,Converter={StaticResource NameToBrushConverter}}"/>
    </Style>
    

    But this won't evaluate on LostFocus, but you wouldn't need it to any ways since it will evaluate the first time and then on any changes to name.