Странный конфликт XAML IsMouseOver и IsEnabled, из-за которого кнопка отображается активной при отключении

В моем приложении у меня есть собственный стиль и шаблон кнопки, который имеет несколько состояний на основе триггеров XAML:

<Style x:Key="UniversalButton" TargetType="{x:Type Button}">
    <Setter Property="Cursor" Value="Hand" />
    <Setter Property="OverridesDefaultStyle" Value="True" />
    <Setter Property="FontWeight" Value="Bold" />
    <Setter Property="Background" Value="#FFD7D7D7" />
    <Setter Property="BorderBrush" Value="#FF999999" />
    <Setter Property="Foreground" Value="#FF666666" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <ControlTemplate.Resources>
                    <Style x:Key="ButtonBorder" TargetType="{x:Type Border}">
                        <Setter Property="CornerRadius" Value="3" />
                        <Setter Property="BorderThickness" Value="0,0,0,2" />
                        <Setter Property="SnapsToDevicePixels" Value="True" />
                    </Style>

                    <Style x:Key="ButtonContent" TargetType="{x:Type ContentPresenter}">
                        <Setter Property="HorizontalAlignment" Value="Center" />
                        <Setter Property="VerticalAlignment" Value="Center" />
                        <Setter Property="TextBlock.TextAlignment" Value="Center" />
                        <Setter Property="Margin" Value="8,7,8,8" />
                        <Setter Property="RecognizesAccessKey" Value="True" />
                        <Setter Property="SnapsToDevicePixels" Value="True" />
                    </Style>
                </ControlTemplate.Resources>
                <ControlTemplate.Triggers>
                    <Trigger Property="ContextMenu.IsOpen" Value="True">
                        <Setter Property="Opacity" Value="0.8" />
                    </Trigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsMouseOver" Value="True" />
                            <Condition Property="IsEnabled" Value="True" />
                        </MultiTrigger.Conditions>
                        <MultiTrigger.EnterActions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetProperty="Opacity" To="0.8" Duration="0:0:0.1" />
                                </Storyboard>
                            </BeginStoryboard>
                        </MultiTrigger.EnterActions>
                        <MultiTrigger.ExitActions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.1" />
                                </Storyboard>
                            </BeginStoryboard>
                        </MultiTrigger.ExitActions>
                    </MultiTrigger>
                    <Trigger Property="IsEnabled" Value="False">
                        <Setter Property="Opacity" Value="0.6" />
                    </Trigger>
                </ControlTemplate.Triggers>
                <Border Style="{DynamicResource ButtonBorder}"
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        Padding="{TemplateBinding Padding}"
                        Opacity="{TemplateBinding Opacity}">
                    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                        <ContentPresenter Style="{DynamicResource ButtonContent}"
                                          TextBlock.FontWeight="{TemplateBinding FontWeight}"
                                          TextBlock.Foreground="{TemplateBinding Foreground}"/>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

В конкретном экземпляре рассматриваемой кнопки есть команда с CanExecute на основе выбора ItemsControl в представлении. Когда я переключаю свой выбор, в том числе те, которые предназначены для отключения кнопки, все работает нормально. Кнопка становится полупрозрачной, как я призвал в триггере XAML для IsEnabled.

Проблема начинается, когда я нахожу указатель мыши на кнопку, активируя раскадровки выше. После этого изменение выбора на состояние, в котором кнопка отключена, оставляет кнопку полностью непрозрачной, независимо от состояния _5 _ / _ 6_.

Путем тщательной отладки я определил, что проблема не вызвана CanExecute, как считалось ранее, и что она должна быть связана с триггером IsMouseOver. Я исчерпал все свои знания XAML, пытаясь разобраться в этом конфликте, и буду очень благодарен за помощь.

Заранее спасибо!


person The Tourer    schedule 23.09.2014    source источник


Ответы (1)


Я считаю, что проблема вызвана приоритетом значения свойства зависимости в WPF. В этом случае анимация (шкала времени) имеет значение по умолчанию FillBehavior из FillBehavior.HoldEnd. После завершения анимации все попытки изменить значение (свойство Opacity) должны иметь более высокий приоритет. Из ссылки вы можете увидеть, что только система, приводящая значение, может переопределить значение на основе анимации (даже локальная настройка, например, путем присвоения непосредственно в выделенном коде, не может переопределить это).

Вот почему ваш шаблон Trigger (с приоритетом 7) не может переопределить текущее анимированное значение. Для этого есть несколько решений, но я думаю, вам следует использовать всю раскадровку в своих триггерах (вместо использования установщика). Если вам не нужна анимация, просто установите Duration в ноль:

<Trigger Property="ContextMenu.IsOpen" Value="True">        
    <Trigger.EnterActions>
        <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation Storyboard.TargetProperty="Opacity" 
                                To=".8" Duration="0:0:0"/>
            </Storyboard>
        </BeginStoryboard>
    </Trigger.EnterActions>
    <!-- similar for ExitActions --> 

</Trigger>

<!--  -->

<Trigger Property="IsEnabled" Value="False">
    <Trigger.EnterActions>
        <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation Storyboard.TargetProperty="Opacity" 
                                To=".6" Duration="0:0:0"/>
            </Storyboard>
        </BeginStoryboard>
    </Trigger.EnterActions>
    <!-- similar for ExitActions --> 

</Trigger>

Использование Storyboard таким образом также полезно для обслуживания. Вот когда вы хотите добавить анимацию к изменению значения свойства, тогда у вас уже есть раскадровка, и вы можете просто добавить дополнительные параметры или анимации ...

PS: Не бойтесь длинного кода XAML. Это природа и характеристика кода XAML. Однако в качестве компромисса у нас есть больший контроль над поведением и эффектами.

person King King    schedule 23.09.2014
comment
Вау, отличный ответ, спасибо! Никогда не исследовал такого рода приоритеты, поэтому я ценю знания. Кроме того, я полностью согласен с написанием длинного XAML ... помогает адекватно определить пользовательский интерфейс! Я обновил свой код триггера, чтобы использовать Storyboards вместо прямых сеттеров, и это устранило мою проблему с отключенным состоянием, но теперь состояние наведения IsMouseOver не работает. Любые идеи? - person The Tourer; 23.09.2014
comment
Далее, только что определил, что если я поменяю порядок запуска, проблема IsEnabled вернется, но проблема IsMouseOver будет решена! - person The Tourer; 23.09.2014
comment
@TheTourer В вашем коде только один IsMouseOver триггер (в состоянии), и я надеюсь, что вы имели в виду не это. Так каков фактический код для изменения на IsMouseOver? Если это все еще основано на обычном установщике триггеров, вам, конечно же, придется преобразовать его в использование раскадровки. - person King King; 23.09.2014
comment
@TheTourer, не могли бы вы рассказать больше о том, как IsMouseOver (в вашем первом комментарии) не работает? Я действительно не нахожу здесь никакого конфликта (я сомневаюсь, что ваша следующая проблема вызвана каким-то конфликтом между раскадровками). - person King King; 23.09.2014
comment
Спасибо за постоянную помощь King King! Итак, я провел дополнительное тестирование с различными сценариями, и вот мои выводы: если я оставлю все три набора триггеров в (ContextMenu.IsOpen, IsEnabled, IsMouseOver; в этом порядке, и каждый с одинаковыми EnterAction и ExitAction Storyboards), проблема в том, что наведение над кнопкой прерывается состояние IsEnabled Opacity сохраняется. Если я удалю корпус IsMouseOver Trigger, проблема исчезнет, ​​и изменение IsEnabled Opacity будет работать все время, даже после наведения курсора на кнопку. Мысли? Заранее спасибо! - person The Tourer; 24.09.2014
comment
@TheTourer Я могу выпустить ваш следующий номер прямо сейчас, и он меня очень удивил. Я не мог поверить, что анимация не может заменить анимацию. Похоже, что последняя объявленная раскадровка (в коде XAML) по-прежнему имеет более высокий приоритет (поэтому замена кода вызывает другие проблемы). Я до сих пор этого не понимаю (конечно, никогда раньше с этим не сталкивался). Я думаю, вам следует попробовать кое-что, чтобы решить эту проблему, например, используя некоторый код позади ... извините. - person King King; 24.09.2014
comment
Не нужно извиняться, я невероятно благодарен за помощь, и вы научили меня чему-то новому о XAML (материале приоритета стилей). Как я могу лучше всего отметить этот ответ, чтобы он был вам полезен? - person The Tourer; 24.09.2014
comment
@TheTourer нет, не принимайте никаких ответов, если они не решают вашу проблему (это хорошо для сообщества). Репутация для меня сейчас не особо важна (было раньше :) - person King King; 25.09.2014