带有标志的数据网格中复选框的奇怪行为
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" />
我认为目前它正在运行:)
我注意到我的数据网格中有奇怪的行为,我在其中显示带有标志的对象 属性。
当我单击第 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" />
我认为目前它正在运行:)