带有标志的数据网格中复选框的奇怪行为

Strange behaviour of checkboxes in datagrid with flags

我注意到我的数据网格中有奇怪的行为,我在其中显示带有标志的对象 属性。

当我单击第 1 行第 1 列中的复选框和第 2 行第 2 列中的复选框时: row2,column1 中的复选框也已更改。

这就像第一行记住更改并将其也应用到下一行:/ 抱歉英文错误。

代码: XAML:

<Window x:Class="DataGridFlags.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:DataGridFlags"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Window.Resources>
    <local:FlagConverter x:Key="FlagsConverter" />
    <local:EnumConverter x:Key="EnumsConverter" />
</Window.Resources>
<StackPanel>
<DataGrid Margin="20" x:Name="list" AutoGenerateColumns="False">
        <DataGrid.Columns>
    <DataGridTextColumn Width="100" Header="File type" Binding="{Binding FileType, Converter={StaticResource EnumsConverter}}" />
    <DataGridTemplateColumn Width="100" Header="Read">
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <CheckBox Content="Read" IsChecked="{Binding Path=Operations, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource FlagsConverter}, ConverterParameter={x:Static local:AllowedOperations.Read}}" />
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>
    <DataGridTemplateColumn Width="100" Header="Edit">
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <CheckBox Content="Edit" IsChecked="{Binding Path=Operations, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource FlagsConverter}, ConverterParameter={x:Static local:AllowedOperations.Edit}}" />
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>
    <DataGridTemplateColumn Header="Delete">
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <CheckBox  Content="Delete" IsChecked="{Binding Path=Operations, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource FlagsConverter}, ConverterParameter={x:Static local:AllowedOperations.Delete}}" />
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>
            <DataGridTextColumn Header="Summary" Binding="{Binding Operations}" />
        </DataGrid.Columns>
    </DataGrid>
</StackPanel>

Permit.cs

public class Permit : BindableBase
{
    private AllowedOperations _operations;

    public string Name { get; set; }
    public FileType FileType { get; set; }
    public AllowedOperations Operations
    {
        get { return _operations; }
        set { SetProperty(ref _operations, value); }
    }

}

FlagConverter.cs

class FlagConverter : IValueConverter
{
    private int targetValue;

    public FlagConverter()
    {
    }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        int mask = (int)parameter;
        this.targetValue = (int)value;

        return ((mask & this.targetValue) != 0);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        this.targetValue ^= (int)parameter;
        return Enum.Parse(targetType, this.targetValue.ToString());
    }
}

AllowedOperations.cs

  [Flags]
public enum AllowedOperations
{
    Read = 1,
    Edit = 2,
    Delete = 4
}

Github: https://github.com/kaczanpiotr/DataGridFlags

Application

问题是 targetValue 是一个字段。如果您在绑定到不同对象的不同属性之间共享转换器的静态实例,则 Convert()ConvertBack() 必须是 pure functions。你的不是。

相反,您有一个 targetValue 在 DataGrid 中的所有绑定之间共享。 Convert() 设置 targetValue,然后对 ConvertBack() 的下一次调用使用 targetValue 的值——但现在它是不同的行。

我已将您的转换器重写为 MarkupExtension,因此您可以方便地为每行中的每个单元格创建一个新实例。不要担心制造一堆这些东西的成本。与我们为每个单元格创建的所有其他内容的成本相比,这无关紧要。

我还将标志掩码设为构造函数参数。您可以通过使构造函数参数类型为 AllowedOperations 来简化此东西的使用,但是您编写了一个适用于任何标志枚举类型的转换器,我认为这种灵活性值得保留。

public class FlagConverter : MarkupExtension, IValueConverter
{
    private int _mask;
    private int _targetValue;

    public FlagConverter(object enumValue)
    {
        _mask = (int)enumValue;
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        _targetValue = (int)value;

        return ((_mask & _targetValue) != 0);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        _targetValue ^= _mask;
        return Enum.Parse(targetType, _targetValue.ToString());
    }
}

下面是您现在将如何使用它。它不再在 Window.Resources.

中创建
<DataGridTemplateColumn Width="100" Header="Read">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <CheckBox 
                Content="Read" 
                IsChecked="{Binding Operations, Converter={local:FlagConverter {x:Static local:AllowedOperations.Read}}, UpdateSourceTrigger=PropertyChanged}" 
                />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="100" Header="Edit">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <CheckBox 
                Content="Edit" 
                IsChecked="{Binding Operations, Converter={local:FlagConverter {x:Static local:AllowedOperations.Edit}}, UpdateSourceTrigger=PropertyChanged}" 
                />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Delete">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <CheckBox  
                Content="Delete" 
                IsChecked="{Binding Operations, Converter={local:FlagConverter {x:Static local:AllowedOperations.Delete}}, UpdateSourceTrigger=PropertyChanged}" 
                />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

我不是特别喜欢这个实现,因为在WPF中有一个普遍的假设Convert()ConvertBack()是纯函数,而它违反了假设。不要轻视相信该假设的倾向:您是通过随意相信自己来到这里的。

但是,ConvertBack 需要两个输入,我不知道还有其他方法可以获取第二个输入。

感谢您的帮助,@Ed Plunkett。 多亏了你,我找到了很多简单的解决方案。我只添加了 x:Shared 属性 到 converter

<local:FlagConverter x:Key="FlagsConverter" x:Shared="False" />

我认为目前它正在运行:)