Strange behaviour of checkboxes in datagrid with flags

时间:2018-06-04 17:13:09

标签: c# wpf datagrid flags

I noticed strange behaviour in my datagrid where I show object with flags property.

When I click checkbox in row1,column1 and checkbox in row2,column2: checkbox in row2,column1 is changed too.

It's like first row remember changes and apply it to next row too :/ Sorry for mistakes in English.

Codes: 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

2 个答案:

答案 0 :(得分:0)

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

相反,您在DataGrid中的所有绑定中共享一个targetValueConvert()设置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需要两个输入,我不知道另一种方法来获得它的第二个。

答案 1 :(得分:0)

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

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

现在它正在工作,我认为:)