Search code examples
c#wpfstylespropertychangedconverters

Converter not firing after collection update


I have ran into a issue with the converters... They are not triggering once the bound collection is updated although they trigger when the collection is first populated. I would like to have them fire whenever there is a change in the collection.

So far I have built a simple converter:

public class TableConverter : IValueConverter
{

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {

        VM.Measurement t = ((VM.Measurement)((TextBlock)value).DataContext);
        if (t.Delta != null)
        {
            if (Math.Abs((double)t.Delta) < t.Tol)
                return "Green";
            else
                return "Red";
        }
        else
            return "Red";
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

which is linked to a style

<conv:TableConverter x:Key="styleConvStr"/>

<Style x:Key="CellStyleSelectorTol" TargetType="syncfusion:GridCell">
    <Setter Property="Background" Value="{Binding   RelativeSource={RelativeSource Self}, Path=Content, Converter={StaticResource styleConvStr}}" />
</Style>

Which is used in this DataGrid

 <syncfusion:SfDataGrid x:Name="CheckGrid" BorderBrush="White" Grid.Row="1" Grid.Column="1" AllowEditing="True"  ItemsSource="{Binding ChecksList, Mode=TwoWay}"  Background="White"   SnapsToDevicePixels="False"
                            ColumnSizer="None"  AllowResizingColumns="False" AllowTriStateSorting="True" AllowDraggingColumns="False" CurrentCellEndEdit="CheckGrid_CurrentCellEndEdit" AutoGenerateColumns="False"
                            NavigationMode="Cell" HeaderRowHeight="30" RowHeight="21"   GridPasteOption="None" Margin="20 10 10 10" AllowGrouping="True" SelectedItem="{Binding SelectedLine, Mode=TwoWay}"
                           SelectionUnit="Row"  SelectionMode="Single" RowSelectionBrush="#CBACCB"  VirtualizingPanel.IsVirtualizing="True"  Visibility="Visible">

                                <syncfusion:GridTextColumn Width="100" ColumnSizer="SizeToCells" AllowEditing="True"  MappingName="Measured" CellStyle="{StaticResource CellStyleSelectorTol}" HeaderText="Measured" TextAlignment="Center"   AllowFiltering="False" FilterBehavior="StringTyped"/>

The VM contains an Observable Collection which implements NotifyPropertyChanged all the way down to the Measurement Class. The properties fire up nicely so it is not a binding issue.

 private ObservableCollection<Measurement> _checkList = new ObservableCollection<Measurement>();
    public ObservableCollection<Measurement> ChecksList
    {
        get
        {
            return _checkList;
        }
        set
        {
            _checkList = value;
            NotifyPropertyChanged();
        }
    }

Any help with this would be greatly appreciated.

Thanks

EDIT: Here is the code that updates the collection. Apologies for it being quite messy. Lineitem is the selected line for which Measured and Delta are updated. These are properly displayed in the grid once modified.

public void NewMeasurement(VM.Measurement measurementShell)
{
    using (VMEntity DB = new VMEntity())
    {
        var Check = CheckSets.Where(x => x.ID == SelectedLine.ID).First();
        if (Check.Measurement == null)
        {
            Check.Measurement = measurementShell.Index;
            var Lineitem = ChecksList.Where(x => x.ID == SelectedLine.ID).First();
            var measurement = DB.Measurements.Where(x => x.Index == Check.Measurement).First();
            Lineitem.Measured = (double)measurement.measurement1;
            Lineitem.Delta = Lineitem.Measured - Lineitem.Target;

Solution

  • OK, it looks like the problem is that you are changing properties of the cell content item (LineItem, in the NewMeasurement() method), but it's still the same object, so the cell's content doesn't change. The cell's Content is the source for the binding. If that doesn't change, the binding won't wake up and update the target. You're raising PropertyChanged, but this particular binding has no way of knowing you want it to listen to this object for those property changes. Easy enough fix: We'll start telling it exactly what to listen for.

    Fortunately the solution means simplifying some of your code. Passing a UI control into a value converter is exotic and not necessary.

    What you care about in the converter is Measurement.Delta and Measurement.Tol. When either one changes, the Binding should update its target. You don't want to do that in a clever way. You just want a Binding for each one. That's a Binding's job.

    So tell the Binding that you care about those properties, and rewrite the converter to accept both of them as parameters.

    <Style x:Key="CellStyleSelectorTol" TargetType="syncfusion:GridCell">
        <Setter 
            Property="Background" 
            >
            <Setter.Value>
                <MultiBinding Converter="{StaticResource styleConvStr}">
                    <Binding Path="Delta" />
                    <Binding Path="Tol" />
                </MultiBinding>
            </Setter.Value>
        </Setter>
    </Style>
    

    Converter:

    public class TableConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            //  I'm inferring that Measurement.Delta is Nullable<double>; if that's 
            //  not the case, change accordingly. Is it Object instead? 
            double? delta = (double?)values[0];
            double tol = (double)values[1];
    
            if (delta.HasValue && Math.Abs(delta.Value) < tol)
            {
                return "Green";
            }
            return "Red";
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }