1

I have a WPF datagrid bound to a EF6 Dbcontext. I have many things functioning such as manual edits etc.

PROBLEM Editing the EF objects bound to the grid does not update on the grid. BUT if i manually edit a cell after one of these background edits, it displays the correct value immediately.

SCENARIO / SETUP

ViewSources Involved

<Window.Resources>
 <CollectionViewSource x:Key="equipmentViewSource" d:DesignSource="{d:DesignInstance {x:Type HAI_Job_EF_Model:Equipment}, CreateList=True}"/>
 <CollectionViewSource x:Key="equipmentAssociatedDevicesViewSource" 
                              Source="{Binding AssociatedDevices, Source={StaticResource equipmentViewSource}}"/>    
 </Window.Resources>

Note that Associated devices are an ObservableCollection inside an Equipment object.

WPF Datagrid (example parts only)

<DataGrid x:Name="associatedDevicesDataGrid" Grid.Row="1" AutoGenerateColumns="False" 
                              MaxWidth="1200" EnableRowVirtualization="True" 
                              RowDetailsVisibilityMode="VisibleWhenSelected" CanUserDeleteRows="False"
                              DataContext="{StaticResource equipmentAssociatedDevicesViewSource}" ItemsSource="{Binding}" 
                              CellEditEnding="associatedDevicesDataGrid_CellEditEnding" 
                              SelectionChanged="associatedDevicesDataGrid_SelectionChanged" 
                              PreviewKeyDown="associatedDevicesDataGrid_PreviewKeyDown"
                              LostFocus="associatedDevicesDataGrid_LostFocus">
                <DataGrid.Resources>
                    <!-- DATGRID STYLE CELL: Gives padding space inside cells -->
                    <Style TargetType="DataGridCell">
                        <Setter Property="Padding" Value="5,5"/>
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="{x:Type DataGridCell}">
                                    <Border Padding="{TemplateBinding Padding}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
                                        <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                                    </Border>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </DataGrid.Resources>
                <DataGrid.Columns>
                    <DataGridTextColumn x:Name="quantityColumn" Header="Qty" MaxWidth="50" 
                                        Binding="{Binding Quantity, StringFormat={}\{0:N0\}, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
                    <DataGridComboBoxColumn x:Name="typeColumn" Header="Type" MaxWidth="150" 
                                            ItemsSource="{Binding Source={StaticResource assDevTypeFilteredViewSource}}"
                                            SelectedItemBinding="{Binding Path=Type, UpdateSourceTrigger=PropertyChanged}" 
                                            TextBinding="{Binding Path=Type, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                        <DataGridComboBoxColumn.EditingElementStyle>
                            <Style TargetType="ComboBox">
                                <Setter Property="IsEditable" Value="True"/>
                                <Setter Property="Text" Value="{Binding Path=Type}"/>
                                <Setter Property="IsSynchronizedWithCurrentItem" Value="True" />
                                <Setter Property="IsTextSearchEnabled" Value="True" />
                                <Setter Property="IsTextSearchCaseSensitive" Value="False" />
                            </Style>
                        </DataGridComboBoxColumn.EditingElementStyle>
                    </DataGridComboBoxColumn>

                    <DataGridTemplateColumn x:Name="certificateNumberColumn" Header="Certificate Number" Width="Auto" MaxWidth="200">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <ComboBox Name="cbxAssDevCertComboBox" 
                                          IsEditable="True" IsTextSearchEnabled="True" IsTextSearchCaseSensitive="False"
                                          ItemsSource="{Binding GenericFilterResults}"
                                          Text="{Binding Path=CertificateNumber, UpdateSourceTrigger=PropertyChanged}"
                                          SelectionChanged="cbxAssDevCertComboBox_SelectionChanged"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTextColumn x:Name="manufacturerColumn" Header="Manufacturer" Width="Auto" MaxWidth="150" 
                                        Binding="{Binding Manufacturer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

Background Change The combobox "cbxAssDevCertComboBox" selectionchanged event then updates all the values for the other cells source objects. This is the update that does not refresh.

MAJOR OPTIONS TRIED/CONSIDERED

  1. I'm not fully up with MVVM and i'm not following it. I'm too deep now to change for this project. I just need the f'n thing to update......!

  2. I can't wrap every single EF object in an Observable collection... nothing else has needed it so why this? Isn't that what the CollectionViewSource is essentially doing?!

  3. Updating the Datagrids ItemSource bindingexpression did not help.

  4. Using the Update

HOW CAN I UPDATE THE CELLS JUST LIKE IT DOES WHEN I MANUALLY TRY TO EDIT THEM AFTER THE PROGRAMITC EDIT?

I have now lost days trying to solve the this; anyhelp would be greatly appreciated.

hichris123
  • 10,145
  • 15
  • 56
  • 70
Asvaldr
  • 197
  • 3
  • 15
  • I think you need to implement `INotifyPropertyChanged` on the model, and invoke that when the values get changed. `ObservableCollection` will notify the UI when the collection is modified (new items added / items removed), but not when a property of an item changes - for that you need `INotifyPropertyChanged`. – MBender Sep 24 '13 at 06:37
  • Good to know the difference between observable collection and I notify. I thought that using a collection viewsource did the same? Obviously not I suppose. What about manually triggering a binding refresh? – Asvaldr Sep 24 '13 at 13:06
  • How are you going about triggering such a binding update? – MBender Sep 24 '13 at 14:11
  • i was trying to using the GetBindingExpression(....).Update() on the data grid's item source property but that didn't work. I couldn't find an intellisense path to get a binding expression of the cells/columns/rows themselves. I also was thinking of beginning an edit on thos e cells but couldn't figure out how exactly. – Asvaldr Sep 24 '13 at 21:59
  • Which is why implementing `INotifyPropertyChanged` is easier. ;) – MBender Sep 25 '13 at 07:44

2 Answers2

2

An ObservableCollection is great, but it only notifies the UI when items are added or removed. It doesn't notify the UI when an item is modified (that is, when an items properties get changed).

You need to implement the INotifyPropertyChanged interface on your model and fire that up.

Here's a sample model class implementing the interface:

public class ModelClass : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

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

    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
    {
        if (selectorExpression == null)
            throw new ArgumentNullException("selectorExpression");
        MemberExpression body = selectorExpression.Body as MemberExpression;
        if (body == null)
            throw new ArgumentException("The body must be a member expression");
        OnPropertyChanged(body.Member.Name);
    }

    string _myValue;
    public string MyValue
    {
        get
        {
            return _myValue;
        }
        set
        {
            _myValue = value;
            OnPropertyChanged(() => MyValue);
        }
    }
}

The nifty lambda trick is taken from this SO question...

Of course you can manually invoke OnPropertyChanged from other parts of your code (adjust accessors as needed or write extra public methods). Calling OnPropertyChanged should force the UI to invoke getters for the properties that are displayed.

Community
  • 1
  • 1
MBender
  • 5,395
  • 1
  • 42
  • 69
2

SOLVED FOR OTHERS INFO

I found an answer. I wondered why some of my other grids updated and this one didn't.

If you create a viewsource as i showed in the OP; calling a ViewSource.View.Refresh will refresh the grid. I didn't have this originally because it will cause a ComboboxSelectionChanged event when refreshed; which then creates in infinite loop. You also can't just add a toggle preventing a refresh every second time of the event, because this event can be setoff during setup many times aswell.

THE SOLUTION I used a bool as a lock to prevent processing of the logic in the selection changed event. This lock is set each time that event logic is processed. The lock is only removed by the Combobox drop open event; this ensures that the refresh only happens as intended for when a user has made a selection change.

Asvaldr
  • 197
  • 3
  • 15
  • I tried `ViewSource.View.Refresh`, in my `CellEditEnding` event but get an error message: `'Refresh' is not allowed during an AddNew or EditItem transaction.` How do I terminate the transaction before doing the `Refresh`? – E Mett Mar 27 '16 at 10:10