Я не нашел по-настоящему элегантного способа сделать это, но поговорил с разработчиками в Mindscape, и вот кое-что грубое, но функциональное, которое работает с Mindscape PropertyGrid.
Сначала мы создаем шаблон для самого редактора flag-enum. Это ItemsControl, заполняемый с помощью EnumValuesConverter из библиотеки WPF Property Grid:
<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. Вы можете предпочесть создать присоединенное свойство с более значимым именем.
Теперь элемент управления будет отображать проверки установленных флагов, но еще не будет обновлять значение, если вы установите или отключите флажок. К сожалению, я нашел единственный способ выполнить эту работу - обработать события Checked и Unchecked и вручную установить значение контекста. Для этого нам нужно поместить значение контекста в место, где его можно будет обновить с помощью обработчиков событий флажка. Это означает двустороннюю привязку свойства флажка к значению контекста. Я снова воспользуюсь тегом, хотя вам может понадобиться что-нибудь более чистое; Кроме того, я собираюсь использовать прямую обработку событий, хотя в зависимости от вашего дизайна вы можете заключить это в прикрепленное поведение (это будет особенно хорошо работать, если вы создавали присоединенные свойства для переноса значения контекста).
<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>
Обратите внимание на двустороннюю привязку тега: это так, что когда мы устанавливаем тег из нашего кода обработки событий, он распространяется обратно на 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 внутренне не может преобразовать значение в тип перечисления при попытке передать его обратно в источник. Здесь Curses - это мой тип перечисления. Если вам нужен полностью гибкий, не зависящий от типа редактор, вы захотите предоставить его извне, например, как присоединенное свойство в поле для флажка. Вы можете сделать это с помощью конвертера или распространить его из редактора EditContext.
Наконец, нам нужно подключить это к сетке. Вы можете сделать это для каждого отдельного объекта недвижимости:
<ms:PropertyGrid>
<ms:PropertyGrid.Editors>
<ms:PropertyEditor PropertyName="Curses" EditorTemplate="{StaticResource FlagEditorTemplate}" />
</ms:PropertyGrid.Editors>
</ms:PropertyGrid>
или с помощью объявления интеллектуального редактора для подключения всех свойств, типы которых имеют атрибут FlagsAttribute. Для получения информации о создании и использовании интеллектуальных редакторов см. http://www.mindscape.co.nz/blog/index.php/2008/04/30/smart-editor-declarations-in-the-wpf.-property-grid/.
Если вы хотите сэкономить место, вы можете изменить ItemsControl на ComboBox, хотя вам потребуется проделать дополнительную работу для обработки свернутого дисплея; Я не исследовал это подробно.
person
holsee
schedule
19.10.2009