WPF MVVM - Datagrid does not update changed proper

2019-08-31 11:10发布

I've got an editable datagrid on a WPF form inside an MVVM application.

There are two possible actions a user can take on this page that cause some data inside one of the rows to change. One of the editable fields - Requested - can cause another property to change, and there's a style trigger dependent on its value:

public bool OverRequested
{
    get
    {
        if(this.Requested > this.Volume)
        {
            return true;
        }
        return false;
    }
}

And in the XAML:

<DataGridTextColumn Header="Requested" Binding="{Binding Requested}">
    <DataGridTextColumn.CellStyle>
        <Style TargetType="DataGridCell">
            <Style.Triggers>
                <DataTrigger Binding="{Binding OverRequested}" Value="true">
                    <Setter Property="Foreground" Value="Red"/>
                    <Setter Property="ToolTip" Value="The requested volume is greater than the available volume" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </DataGridTextColumn.CellStyle>
</DataGridTextColumn>

The other is a button that updates the data item behind the row and changes a value on the item.

Although the datagrid itself is responsive to changes - if you add or remove rows from the underlying ObservableCollection it updates accordingly in the UI - changes to these properties on the items underneath the rows do not update.

I understand that this is by design, in the sense that changes to items inside an observableCollection do not bubble up, and have to be watched for specifically, but I'm struggling to find out how to achieve what I want.

I've tried - as suggested elsewhere - overriding the event handler for CollectionChanged on the ObservableCollection to add a notification alert to the items inside:

private void ObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.NewItems != null)
    {
        foreach (var item in e.NewItems)
        {
            (item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
        }
    }
}

But items inside do not implement the INotifyPropertyChanged interface - they're Entity Framework objects and not ViewModels. So the cast fails.

Is there a way I can implement this? With only two properties to watch, I'd happily do it manually - but even calling OnPropertyChanged("") to update all the properties in the ViewModel doesn't cause those inside the datagrid to refresh.

1条回答
小情绪 Triste *
2楼-- · 2019-08-31 11:43

Maybe you can try it another way, using converters. I have an app that does something like that. I usually need a test app to get this stuff just right, but try this:

<DataGridTextColumn Header="Requested" Binding="{Binding Requested}">
<DataGridTextColumn.CellStyle>
    <Style TargetType="DataGridCell">
         <Setter Property="Foreground">
              <Setter.Value>
                <MultiBinding Converter="{StaticResource OverRequestedForegroundMultiConverter}">
                    <Binding Path="Requested" />
                    <Binding Path="Volume" />
                </MultiBinding>
              </Setter.Value>
         </Setter>
         <Setter Property="ToolTip">
              <Setter.Value>
                <MultiBinding Converter="{StaticResource OverRequestedTooltipMultiConverter}">
                    <Binding Path="Requested" />
                    <Binding Path="Volume" />
                </MultiBinding>
              </Setter.Value>
         </Setter>
    </Style>
</DataGridTextColumn.CellStyle>

The converters would look something like this:

public class OverRequestedForegroundMultiConverter : IMultiValueConverter
{
    #region IValueConverter Members

    public object Convert(object[] value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value != null && value.Length == 2)
        {
            if (value[0] is int && value[1] is int)
            {
                int requested = (int)value[0];
                int volume = (int)value[1];
                if (requested > volume)
                    return Colors.Red;
            }
        }
        return Colors.Gray; // Or whatever color you want
    }

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

    #endregion
}

    public class OverRequestedTooltipMultiConverter : IMultiValueConverter
    {
        #region IValueConverter Members

        public object Convert(object[] value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value != null && value.Length == 2)
            {
                if (value[0] is int && value[1] is int)
                {
                    int requested = (int)value[0];
                    int volume = (int)value[1];
                    if (requested > volume)
                        return "The requested volume is greater than the available volume";
                }
            }
            return null;
        }

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

        #endregion
    }

Don't forget to add the converters to your app.xaml:

<app:OverRequestedForegroundMultiConverter x:Key="OverRequestedForegroundMultiConverter" />
<app:OverRequestedTooltipMultiConverter x:Key="OverRequestedTooltipMultiConverter" />
查看更多
登录 后发表回答