0
votes

Today I found something strange (for me). If I want to update a DataGrid through a property in my ViewModel the Binding wont get notified. The special think here is (I think) the Binding is bound to another object (part of an Collection) not directly the property i change.

I have prepared some sample code for you. But first a little (Depper) explanation.

Of course it is just a sample but here I have a ViewModel with two public Properties (Items and CurrentItem). Items is a ObservableCollection and serves as the ItemsSource of my DataGrid. CurrentItem is a String which serves as indicator for a converter to set the background colour (for the grid).

I add two instances o String to my Collection and after the program is started the behaviour is as expected. The first line is green the second is white (set through the converter and the properties).

But if I change the value of CurrentItem after the program was loaded (lets say through the button) the colours wont update on my Datagrid.

If I create a breakpoint at the beginning of the converter I can see (after the loading process) the converter wont execute again so it has to be a Problem with the Binding. I think the problem is my property which is not part of the items in my Collection. The OnPropertyChange method seems to not trigger the update for the RowStyle properly.

In real life the model class of the collections is not a string and the model class implements INotifyPropertyChange (but I don´t think this is the problem cause i just don´t update anything in the model).

I need this kind of behaviour to visible highlight more rows based on a dynamic indicator (similar to the example). If no one knows a better way I think I will implement some kind of Property in my models and update the property with a method from the ViewModel.

ViewModel:

public class MainWindowViewModel : INotifyPropertyChanged
{
    public static MainWindowViewModel instance = null;

    private string _CurrentItem;

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string Property)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(Property));
        }
    }

    public string CurrentItem
    {
        get
        {
            return _CurrentItem;
        }
        set
        {
            if (value != _CurrentItem)
            {
                _CurrentItem = value;
                OnPropertyChanged("CurrentItem");
                OnPropertyChanged("Items");
            }
        }
    }
    public ObservableCollection<String> Items { get; set; }

    public MainWindowViewModel()
    {
        instance = this;
        Items = new ObservableCollection<string>();

        CurrentItem = "First";

        Items.Add("First");
        Items.Add("Second");
        Items.Add("First");

    }

View XAML

<Window.DataContext>
    <local:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
    <local:StringToColorConverter x:Key="StringToColorConverter" />
</Window.Resources>
<DockPanel Margin="30">
    <Button DockPanel.Dock="bottom" Content="From First to Second" Click="Button_Click" />
    <DataGrid IsReadOnly="True" ItemsSource="{Binding Items}" ColumnWidth="*" AutoGenerateColumns="False">
        <DataGrid.RowStyle>
            <Style TargetType="DataGridRow">
                <Setter Property="Background" 
                        Value="{Binding Converter={StaticResource StringToColorConverter}}" />
            </Style>
        </DataGrid.RowStyle>
        <DataGrid.Columns>
            <DataGridTextColumn Header="Text" Binding="{Binding}" />
        </DataGrid.Columns>
    </DataGrid>
</DockPanel>

Converter

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    var item = value as string;

    if (item == MainWindowViewModel.instance?.CurrentItem)
        return "Green";
    return "White";
}

So sorry for the long post I hope you can comprehend my problem and of course maybe help me :)

2
In the XAML you included, CurrentItem isn't bound to anything. How is the XAML supposed to guess that it even exists, much less care when it changes?15ee8f99-57ff-4f92-890c-b56153
I agree with Ed, I was editing my post while Ed's comment came. @Bado, the is another solution that is possible if CurrentItem is the line currently selected in the Datagrid. Is CurrentItem The currenlty selected line ?Emmanuel DURIN
Yes I thought something like that. So there is no way to (lets say) force the ObservableCollection to update its Bindings without changing an item? As you can see in my example I have tried it with INotifyPropertyChanged. The name CurrentItem is maybe a little bit confusing. It is not the currently selected item it is just a indicator which serves as base to let the converter know which rows should be green and which rows should be white. Through this indicator it could also be that more than one rows will be marked green (However not in my example :)).Bado
I have edited my post. Now after the program is loaded there should be two lines green and one white. After the click on the button there should be just one line green (The one with the text second). I hope this is helpful :) If no one came up with a better solution then I think I will implement something like Emmanuel DURIN mentioned in his answer. I will wait until tomorow then I mark you answer as solution. And thank you for you fast responses (both of you) :)Bado

2 Answers

2
votes

You can involve CurrentItem using an IMultiValueConverter

<DataGrid.RowStyle>
    <Style TargetType="DataGridRow">
        <Setter Property="Background">
            <Setter.Value>
                <MultiBinding Converter="{StaticResource MultiStringToColorConverter}">
                    <Binding />
                    <Binding Path="DataContext.CurrentItem" 
                             RelativeSource="{RelativeSource FindAncestor, 
                                              AncestorType={x:Type Window}}"/>
                </MultiBinding>
            </Setter.Value>
        </Setter>
    </Style>
</DataGrid.RowStyle>

The Converter

public class MultiStringToColorConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter,
                  System.Globalization.CultureInfo culture)
    {
        var item = values[0] as string;
        var current = values[1] as string;

        if (item == current)
            return new SolidColorBrush(Colors.Green);
        return new SolidColorBrush(Colors.White);
    }

    public object[] ConvertBack(object values, Type[] targetType, object parameter,
                    System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
2
votes

Everything is normal !

It is by design. Your background is bound to the n-th object of the DataGrid. It is not bound to CurrentItem, so there is no reason the binding updates the n-th line background.

Because you have an ObservableCollection, you could put a IsSelected property in MyItem class And you should make MyItem ràise a PropertyChanged event on IsSelected property.

Of course MyItem would implement INotifyPropertyChanged

Last, you should change the binding :

<DataGrid.RowStyle>
  <Style TargetType="DataGridRow">
    <Setter Property="Background" 
                    Value="{Binding Path=IsSelected,
                                    Converter={StaticResource BooleanToColorConverter}}" />
  </Style>
</DataGrid.RowStyle>

Of course changing the StringToColorConverter into BooleanToColorConvert wouldbe trivial.

Regards