Как обрабатывать прикрепленные свойства событий?

Я создал стиль экспандера, который содержит флажок в своем заголовке. Состояние флажка привязано к присоединенному свойству:

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

  1. создать модель представления со свойствами: IsChecked(привязан к CheckBox), SelectedItem(привязан к ComboBox) и Source(ItemsSource for ComboBox) - абстракция моего реального представления без каких-либо ссылок на элементы управления;
  2. написать логику в модели представления, которая устанавливает или отменяет SelectedItem в зависимости от свойства IsChecked;
  3. покрыть эту логику модульным тестом (да, вы можете даже начать с этой точки, если вам нравится тест с первого подхода).
Другие вопросы по тегам