Asvaldr
Asvaldr

Reputation: 197

WPF EF6 Datagrid Cell doesn't update when changed programatically unless user edits the cell

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.

Upvotes: 1

Views: 5393

Answers (2)

Asvaldr
Asvaldr

Reputation: 197

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.

Upvotes: 2

MBender
MBender

Reputation: 5650

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.

Upvotes: 2

Related Questions