Привязка данных WPF к перечислению флагов (в PropertyGrid)

StackOverflow https://stackoverflow.com/questions/1471151

Вопрос

Мне нужно иметь возможность выбирать несколько значений, как это свойственно перечислению флагов из представления WPF (все, что угодно, в PropertyGrid).

Свойства, о которых идет речь, являются динамичный и никакие заранее определенные DataTemplates не могут быть использованы в качестве тип свойств будет обнаружен во время выполнения.(DataTemplate, который может определить, является ли перечисление флагом, может оказаться полезным, но, насколько я понимаю, мне нужно было бы заранее знать типы перечислений флагов, чтобы достичь этого, а этого не будет).

Я опробовал ряд проприетарных сеток свойств с открытым исходным кодом для WPF, и, похоже, ни одна из них не поддерживает типы перечисления, приписываемые 'Flags', из коробки.

Решением этой проблемы было бы все, что позволило бы мне привязать данные к + выбрать несколько значений для указанного перечисления флагов для любого коммерческого или с открытым исходным кодом WPF PropertyGrid.

Код:

Пример PropertyType:

public class PropertyTypeOne
{
    public PropertyTypeOne()
    {
        IntProp = 1;
        InProp2 = 2;
        BoolProp = true;
        Boolprop2 = false;
        StringProp = "string1";
        DoubleProp = 2.3;
        EnumProp = FlagEnumDataTYpe.MarketDepth;
    }

    public int IntProp { get; set; }

    public int InProp2 { get; set; }

    public bool BoolProp { get; set; }

    public bool BoolProp2 { get; set; }

    public string StringProp { get; set; }

    public double DoubleProp { get; set; }

    //This is the property in question
    public FlagEnumDataType EnumProp { get; set; }
}

Пример Типа Перечисления Флагов:

[Flags]
public enum FlagEnumDataType : byte
{
    None = 0,
    Trade = 1,
    Quote = 2,
    MarketDepth = 4,
    All = 255
}

Примечание:

Если решение использует WPF PropertyGrid с открытым исходным кодом (http://www.codeplex.com/wpg) Я внесу изменения / дополнения обратно в элемент управления.

Спасибо.

Это было полезно?

Решение

Я не нашел по-настоящему элегантного способа сделать это, но из разговора с разработчиками в Mindscape и вот что-то грубое, но функциональное, что работает с Mindscape PropertyGrid.

Сначала мы создаем шаблон для самого редактора перечислений флагов.Это элемент управления ItemsControl, заполняемый с использованием EnumValuesConverter из библиотеки сетки свойств WPF:

<ms:EnumValuesConverter x:Key="evc" />
<local:FlaggyConverter x:Key="fc" />

<DataTemplate x:Key="FlagEditorTemplate">
  <ItemsControl Name="ic" ItemsSource="{Binding Value, Converter={StaticResource evc}}">
    <ItemsControl.ItemTemplate>
      <DataTemplate>
        <CheckBox Content="{Binding}">
        </CheckBox>
      </DataTemplate>
    </ItemsControl.ItemTemplate>
  </ItemsControl>
</DataTemplate>

Теперь нам нужно отобразить флажки как установленные в зависимости от того, включен флаг или выключен.Для этого требуются две вещи:во-первых, IMultiValueConverter, позволяющий учитывать как имеющийся флаг, так и значение контекста, и, во-вторых, способ для отдельных флажков считывать значение контекста.(Под контекстным значением я подразумеваю фактическое значение свойства.Например.контекстное значение может быть Flag1 | Flag4 | Flag32.) Вот конвертер:

public class FlaggyConverter : IMultiValueConverter
{
  public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    int flagValue = (int)values[0];
    int propertyValue = (int)values[1];

    return (flagValue & propertyValue) == flagValue;
  }

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

Для распространения значения контекста я собираюсь воспользоваться ярлыком и использовать Tag .Возможно, вы предпочтете создать вложенное свойство с более осмысленным именем.

Теперь элемент управления будет отображать проверки установленных флагов, но пока не будет обновлять значение при включении или выключении флажка.К сожалению, единственный способ, который я нашел, чтобы заставить это работать, - это обработать отмеченные и Непроверенные события и вручную задать значение контекста.Чтобы сделать это, нам нужно поместить значение контекста в место, где оно может быть обновлено с помощью обработчиков событий checkbox.Это означает двустороннюю привязку свойства флажка к контекстному значению.Опять же, я буду использовать Tag, хотя вы, возможно, захотите что-то немного более чистое;кроме того, я собираюсь использовать прямую обработку событий, хотя в зависимости от вашего дизайна вы можете захотеть преобразовать это в прикрепленное поведение (это работало бы особенно хорошо, если бы вы создавали прикрепленные свойства для переноса значения контекста).

<DataTemplate x:Key="FlagEditorTemplate">
  <ItemsControl Name="ic" ItemsSource="{Binding Value, Converter={StaticResource evc}}" Tag="{Binding Value, Mode=TwoWay}">
    <ItemsControl.ItemTemplate>
      <DataTemplate>
        <CheckBox Content="{Binding}" Tag="{Binding Tag, ElementName=ic, Mode=TwoWay}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked">
          <CheckBox.IsChecked>
            <MultiBinding Converter="{StaticResource fc}" Mode="OneWay">
              <Binding />
              <Binding Path="Tag" ElementName="ic" />
            </MultiBinding>
          </CheckBox.IsChecked>
        </CheckBox>
      </DataTemplate>
    </ItemsControl.ItemTemplate>
  </ItemsControl>
</DataTemplate>

Обратите внимание на двустороннюю привязку тега:это делается для того, чтобы, когда мы устанавливаем Tag из нашего кода обработки событий, он распространялся обратно на ic.Tag, а оттуда на значение свойства.

Обработчики событий в основном очевидны, но с одним недостатком:

<DataTemplate x:Key="FlagEditorTemplate">
  <ItemsControl Name="ic" ItemsSource="{Binding Value, Converter={StaticResource evc}}" Tag="{Binding Value, Mode=TwoWay}">
    <ItemsControl.ItemTemplate>
      <DataTemplate>
        <CheckBox Content="{Binding}" Tag="{Binding Tag, ElementName=ic, Mode=TwoWay}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked">
          <CheckBox.IsChecked>
            <MultiBinding Converter="{StaticResource fc}" Mode="OneWay">
              <Binding />
              <Binding Path="Tag" ElementName="ic" />
            </MultiBinding>
          </CheckBox.IsChecked>
        </CheckBox>
      </DataTemplate>
    </ItemsControl.ItemTemplate>
  </ItemsControl>
</DataTemplate>

Обработчики событий:

private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
  CheckBox cb = (CheckBox)sender;
  int val = (int)(cb.Tag);
  int flag = (int)(cb.Content);
  val = val | flag;
  cb.Tag = (Curses)val;
}

private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
  CheckBox cb = (CheckBox)sender;
  int val = (int)(cb.Tag);
  int flag = (int)(cb.Content);
  val = val & ~flag;
  cb.Tag = (Curses)val;
}

Обратите внимание на приведение при настройке cb.Tag.Без этого WPF внутренне не может преобразовать значение в тип enum при попытке распространить его обратно в источник.Здесь Curses - это мой тип перечисления.Если вам нужен полностью гибкий редактор, не зависящий от типа, вы захотите предоставить это извне, например, в виде прикрепленного свойства в поле checkbox.Вы могли бы либо вывести это с помощью конвертера, либо распространить его из редактора EditContext.

Наконец, нам нужно подключить это к сети.Вы можете сделать это либо на индивидуальной основе:

<ms:PropertyGrid>
  <ms:PropertyGrid.Editors>
    <ms:PropertyEditor PropertyName="Curses" EditorTemplate="{StaticResource FlagEditorTemplate}" />
  </ms:PropertyGrid.Editors>
</ms:PropertyGrid>

или с помощью объявления smart editor для подключения всех свойств, типы которых имеют атрибут FlagsAttribute.Дополнительные сведения о создании и использовании интеллектуальных редакторов см. http://www.mindscape.co.nz/blog/index.php/2008/04/30/smart-editor-declarations-in-the-wpf-property-grid/.

Если вы хотите сэкономить место, вы можете изменить ItemsControl на поле со списком, хотя вам нужно будет выполнить некоторую дополнительную работу для обработки свернутого отображения;Я не изучал это подробно.

Другие советы

Нашел в Интернете, немного улучшил, но еще не успел протестировать.

/// <summary>
/// Two-way conversion from flags to bool and back using parameter as mask
/// Warning: The trick is in storing value locally between calls to Convert and ConvertBack
/// You must have a single instance of this converter per flags property per object
/// Do not share this converter between different objects or properties
/// Typical usage:
/// [Flags] enum FlagType { None = 0, Trade = 1, Quote = 2, Report = 4, All = 255 }
/// <local:EditableFlagsToBooleanConverter x:Key="FlagsToBooleanConverter" />
/// <CheckBox IsChecked="{Binding Prop1, Converter={StaticResource FlagsToBooleanConverter}, Mode=TwoWay, 
///     ConverterParameter={x:Static local:FlagType.Trade}}" >Trade</CheckBox>
/// <CheckBox IsChecked="{Binding Prop1, Converter={StaticResource FlagsToBooleanConverter}, Mode=TwoWay, 
///     ConverterParameter={x:Static local:FlagType.Quote}}" >Quote</CheckBox>
/// <CheckBox IsChecked="{Binding Prop1, Converter={StaticResource FlagsToBooleanConverter}, Mode=TwoWay, 
///     ConverterParameter={x:Static local:FlagType.Report}}" >Report</CheckBox>
/// </summary>
public class EditableFlagsToBooleanConverter : IValueConverter
{
    private ulong _target;

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (parameter is Enum && value is Enum)
        {
            var mask = (ulong) parameter;
            _target = (ulong) value;
            return ((mask & _target) != 0);
        }

        return Binding.DoNothing;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool && parameter is Enum)
        {
            var mask = (ulong)parameter;
            if ((bool)value)
            {
                _target |= mask;
            }
            else
            {
                _target &= ~mask;
            }
            return _target;
        }

        return Binding.DoNothing;
    }
} 
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top