Как обрабатывать прикрепленные свойства событий?
Я создал стиль экспандера, который содержит флажок в своем заголовке. Состояние флажка привязано к присоединенному свойству:
<Style TargetType="{x:Type Expander}" x:Key="MyCheckboxExpander">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
(...)
<CheckBox x:Name="ExpanderHeaderChk" VerticalAlignment="Center" Margin="4,0,0,2"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(my:AP.IsChecked)}" />
(...)
На мой взгляд, внутри экспандера у меня есть стекпанель с ComboBox.
Всякий раз, когда пользователь проверяет флажок экспандера, я не хочу, чтобы в комбинированном ящике был выбран первый элемент, а с другой стороны, когда пользователь снимает его, я не хочу, чтобы выбранный элемент комбинированного списка был нулевым.
Как я могу сделать это? Я следую шаблону MVVM, но так как это больше вопрос вида, я открыт для предложений кода.
3 ответа
Я последовал предложению @Baboon и создал собственный элемент управления с перенаправленным событием с именем CheckedChanged, так что я могу получить к нему доступ через xaml и code-behind представления:
[TemplatePart(Name = "PART_Expander", Type = typeof(Expander))]
[TemplatePart(Name = "PART_CheckBox", Type = typeof(CheckBox))]
public class MyCustomExpander : Expander
{
static MyCustomExpander()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomExpander), new FrameworkPropertyMetadata(typeof(MyCustomExpander)));
}
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool), typeof(MyCustomExpander),
new UIPropertyMetadata(false));
#region Events
private CheckBox chkExpander = new CheckBox();
public CheckBox ChkExpander { get { return chkExpander; } private set { chkExpander = value; } }
public static readonly RoutedEvent CheckedChangedEvent = EventManager.RegisterRoutedEvent("ExtraButtonClick",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(MyCustomExpander));
public event RoutedEventHandler CheckedChanged
{
add { AddHandler(CheckedChangedEvent, value); }
remove { RemoveHandler(CheckedChangedEvent, value); }
}
void OnCheckedChanged(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(CheckedChangedEvent, this));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
CheckBox chk = base.GetTemplateChild("PART_CheckBox") as CheckBox;
if (chk != null)
{
chk.Checked += new RoutedEventHandler(OnCheckedChanged);
chk.Unchecked += new RoutedEventHandler(OnCheckedChanged);
}
}
#endregion
}
Я хочу поблагодарить @Baboon и @Vlad за их помощь.
Ну, я думаю, что ваш дизайн не является оптимальным. Вы видите, вы пытаетесь изменить семантику Expander
, Настоящий расширитель не имеет семантики с дополнительным флажком, поэтому создаваемый вами элемент управления больше не является расширителем.
Я бы посоветовал вам переключиться на пользовательский элемент управления (или, может быть, на собственный элемент управления, посмотреть на свою семантику) и выставить необходимое событие в классе вашего элемента управления. Возможно, XAML для пользовательского элемента управления должен быть расширителем с флажком.
Изменить: пример с UserControl (не проверено)
(XAML)
<UserControl x:Class="namespace:MyCheckboxExpander">
<Expander>
...
<Checkbox x:Name="cb"/>
...
</Expander>
</UserControl>
(Код-сзади)
public class MyCheckboxExpander : UserControl
{
MyCheckboxExpander()
{
InitializeComponent();
cb.Check += OnCheck;
}
void OnCheck(object sender, whatever2 args)
{
if (CheckboxTriggered != null)
CheckboxTriggered(new EventArgs<whatever>);
}
public event EventArgs<whatever> CheckboxTriggered;
}
WPF является настолько мощным фреймворком, что вы можете решить свою проблему, просто используя следующий стиль для Expander:
<Style x:Key="myExpanderStyle" TargetType="{x:Type Expander}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<StackPanel>
<CheckBox x:Name="PART_CheckBox" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
<ComboBox x:Name="PART_ComboBox" ItemsSource="{TemplateBinding Content}" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter TargetName="PART_ComboBox" Property="SelectedIndex" Value="0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
ОБРАЗЕЦ:
<Expander Style="{StaticResource myExpanderStyle}">
<x:Array Type="sys:String">
<sys:String>1</sys:String>
<sys:String>2</sys:String>
<sys:String>3</sys:String>
</x:Array>
</Expander>
Просто XAML! Мне нравится декларативность XAML.
Но с точки зрения MVVM у этого подхода есть один недостаток - я не могу охватить этот случай модульными тестами. Итак, я бы предпочел:
- создать модель представления со свойствами: IsChecked(привязан к CheckBox), SelectedItem(привязан к ComboBox) и Source(ItemsSource for ComboBox) - абстракция моего реального представления без каких-либо ссылок на элементы управления;
- написать логику в модели представления, которая устанавливает или отменяет SelectedItem в зависимости от свойства IsChecked;
- покрыть эту логику модульным тестом (да, вы можете даже начать с этой точки, если вам нравится тест с первого подхода).