Странное поведение флажков в сетке данных с флагами - PullRequest
0 голосов
/ 04 июня 2018

Я заметил странное поведение в моей сетке данных, когда я показываю объект со свойством flags.

Когда я нажимаю флажок в строке1, столбце1 и флажок в строке2, столбец2: флажок в строке2, столбец1 также изменяется.

Это как в первом ряду запомнить изменения и применить их также к следующему ряду: / Извините за ошибки на английском языке.

Коды: 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

Применение

Ответы [ 2 ]

0 голосов
/ 04 июня 2018

Спасибо за помощь, @Ed Plunkett.Благодаря вам я нашел гораздо простое решение.Я только добавил свойство x: Shared в конвертер

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

Пока оно работает, я думаю:)

0 голосов
/ 04 июня 2018

Проблема в том, что targetValue - это поле.Если вы разделяете статический экземпляр преобразователя среди привязок к различным свойствам разных объектов, Convert() и ConvertBack() должны быть чистыми функциями .Твой нет.

Вместо этого у вас есть один 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 нужны два входа, и я не знаю другого способа получить второй.

...