0

I have a DataGrid that binds to an ObservableCollection named Programs. On the window there's 2 buttons, one for changing a bit field in the selected row to Activate, the other to change it to Deactivate. Since we're using .EDMX files, so don't have access to the generated C# code for each model class, changing a value of the bit field in one of the rows of Programs doesn't change the value in the DataGrid. I understand that. I looked up here on SO how I might be able to do this. I found a post from almost 8 years ago, titled ObservableCollection not updating View. This looked very promising so I implemented the solution given by aqwert. However, it still is not working. I know the value is getting modified and following aqwert's solution I'm replacing Programs. Doesn't matter, it doesn't update the view.

We're using .NET Framework 4.5.2. We're also using MVVM Light. And we're using FirstFloor Software's ModernUI for WPF.

Here's the XAML for the DataGrid:

<DataGrid
Grid.Row="3"
Grid.Column="2"
AutoGenerateColumns="False"
BorderThickness="1"
CanUserAddRows="False"
ItemsSource="{Binding Programs}"
SelectedItem="{Binding SelectedProgram, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<DataGrid.Columns>
    <DataGridTemplateColumn
        Width="Auto"
        Header="ID"
        IsReadOnly="True">
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding ID}" />
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>
    <DataGridTemplateColumn
        Width="Auto"
        Header="Abbrev"
        IsReadOnly="True">
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding ProgramAbbrev}" />
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>
    <DataGridTemplateColumn
        Width="Auto"
        Header="Program Name"
        IsReadOnly="True">
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding ProgramName}" />
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>
    <DataGridTemplateColumn
        Width="Auto"
        Header="Inactive"
        IsReadOnly="True">
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Inactive, Converter={StaticResource BoolToYN}}" TextAlignment="Center" />
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>
</DataGrid.Columns>

Here's the definition of Programs from the VM:

private ObservableCollection<Program> programs;
public ObservableCollection<Program> Programs
{
    get { return programs; }
    set
    {
        if (programs != value)
        {
            programs = value;
            RaisePropertyChanged("Programs");
        }
    }
}

and lastly, here's the code that I've made, following aqwert's solution:

//make a copy of Programs
var programsCopy = new List<Program>();
foreach (var item in Programs)
{
    if (item.ID == SelectedProgram.ID)
    {
        item.Inactive = inactiveFlag;
        item.UpdatedBy = Environment.UserDomainName + "\\" + Environment.UserName;
        item.UpdatedOn = rightNow;
    }
    programsCopy.Add(item);
}
//copy over the top of Programs
Programs = new ObservableCollection<Program>(programsCopy);
Rod
  • 4,107
  • 12
  • 57
  • 81
  • If you only ever create a new Programs collection and "copy over", there is no need to use ObservableCollection at all. – Clemens Nov 19 '19 at 22:17
  • We do more than just copy over. A user may also add additional rows to the observable collection. Following the spec, once a user has selected a new program to add, it's added to the database and I then refresh the Programs collection from the database. In this case, when a user wants to change a program from Active or Deactivate (or vice-versa), it must only be done in memory, not in the database, according to the spec. According to the spec all such modifications require the user to click a Save button to persist those changes. – Rod Nov 19 '19 at 22:34
  • Since you don't have access to the model class containing the bit field property, there is no way to verify that it implements `iNotifyPropertyChanged`. The `ObservableCollection` can handle notification of new collection items, but not individual properties within an item in a collection. My guess is that the property you are binding too does't implement `iNotifyPropertyChanged` in which case it will never notify your `DataGrid` to update. You may need to refresh bindings manually if this is the case. – Tronald Nov 20 '19 at 00:27
  • 2
    As Tronald has mentioned, observablecollection will only notify if the whole collection changed. Not if some property of the element inside the collection is changed.. There are lot of libraries out there which can be of help. I have created my lightweight MVVM library for such tasks. https://www.nuget.org/packages/Haley.Flipper.MVVM/ Try to use this. I have a collection called FlipperObservableCollection which can trigger if the property is changed. It is present inside the namespace Haley.Flipper.MVVM.Models – Lingam Nov 20 '19 at 03:03
  • Thank you, Tronald and Lingam. I'll try it tomorrow when I'm at work. – Rod Nov 20 '19 at 04:56

2 Answers2

1

An ObservableCollection<T> doesn't raise change notifications to the UI when properties of individual items in the collection are modified. The way to do this is to implement the INotifyPropertyChanged event in your Program class.

If you can't modify Program for whatever reason, you could create a new view model class that wraps it and change the type of your source collection property from ObservableCollection<Programs> to IEnumerable<ProgramViewModel>. You don't need an ObservableCollection if you reset the Programs property to a new instance each time you want to change the collection.

You then implement the INotifyPropertyChanged in the ProgramViewModel class and bind to properties of this one.

mm8
  • 163,881
  • 10
  • 57
  • 88
0

I finally discovered my problem. Stupid error on my part. I have 2 routines, once each that are bound to two different ICommands. One for the Activate button, the other for the Deactivate button. I'll just should the Deactivate button's code:

private void ExecuteDeActivateCommand()
{
    SetInactiveFlag(true);
    GetProgramsSynchronously(SelectedRow.ID); //this call was what was causing me problems
}

Yesterday I showed you some code from the SetInactiveFlag(true) call. That worked fine and was setting the Inactive bit flag to true. However, I'd forgotten that I'd called the GetProgramsSynchronously(SelectedRow.ID) call would go against the database and refresh the in-memory Programs collection, with the Inactive flag set back to false.

Egg on my face. I want you all to learn from my mistake so hopefully you have avoid egg on your face.

Rod
  • 4,107
  • 12
  • 57
  • 81