【问题标题】:WPF Trigger on Button.LostFocus with binding带有绑定的 Button.LostFocus 上的 WPF 触发器
【发布时间】:2020-06-24 16:01:39
【问题描述】:

我正在设计一个 WPF 应用程序,我想在其中为窗口的默认按钮提供一个标记(例如红色边框)。标记的行为应该是

  • 当没有按钮获得焦点时,标记应该在默认按钮上可见
  • 如果默认按钮获得焦点,那么标记也应该可见
  • 如果任何其他按钮获得焦点,则应隐藏标记

由于我使用的是materialDesign,所以我不得不扩展“MaterialDesignRaisedButton”样式。 我编写了一个转换器,它将检查窗口中存在的所有按钮,并根据我的要求在默认按钮上设置标记。

internal class ButtonDefaultPropertyConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        bool nonDefaultFocus = false;
        foreach (Button button in FindVisualChildren<Button>((DependencyObject)value))
        {                
            if (button.IsFocused && !button.IsDefault)
            {
                nonDefaultFocus = true;
                break;
            }
        }
        foreach (Button button in FindVisualChildren<Button>((DependencyObject)value))
        {
            if (button.IsDefault)
            {
                if (!nonDefaultFocus)
                    button.BorderBrush = Brushes.Red;
                else
                {
                    button.BorderBrush = (Brush)Application.Current.Resources["PrimaryHueMidBrush"];
                }
            }
            else
            {
                button.BorderBrush = (Brush)Application.Current.Resources["PrimaryHueMidBrush"];
            }
        }
        return 1;
    }

我在不同的属性上编写了触发器,只是为了调用转换器。我在 APP.xaml 中有它,这样我就可以在我的应用程序中使用它。 xaml 看起来像这样

<Style x:Key="MyButton" TargetType="{x:Type Button}">
        <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
        <Setter Property="Background" Value="{StaticResource PrimaryHueMidBrush}"/>
        <Setter Property="BorderBrush" Value="{StaticResource PrimaryHueMidBrush}"/>
        <Setter Property="Foreground" Value="{StaticResource PrimaryHueLightForegroundBrush}"/>
        <Setter Property="materialDesign:ButtonProgressAssist.IndicatorForeground" Value="{DynamicResource PrimaryHueMidForegroundBrush}" />
        <Setter Property="materialDesign:ButtonProgressAssist.IndicatorBackground" Value="{StaticResource PrimaryHueMidBrush}" />
        <Setter Property="materialDesign:RippleAssist.Feedback" Value="White" />
        <Setter Property="Cursor" Value="Hand"/>
        <Setter Property="materialDesign:ShadowAssist.ShadowDepth" Value="Depth1" />
        <Setter Property="TextBlock.FontWeight" Value="Medium"/>
        <Setter Property="TextBlock.FontSize" Value="14"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="HorizontalContentAlignment" Value="Center"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="Padding" Value="16 4 16 4"/>
        <Setter Property="Height" Value="32" />
        <Setter Property="materialDesign:ButtonProgressAssist.IsIndicatorVisible" Value="False" />
        <Setter Property="materialDesign:ButtonProgressAssist.Opacity" Value=".4" />
        <Setter Property="materialDesign:ButtonAssist.CornerRadius" Value="2" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Button}">
                    <Grid>
                        <AdornerDecorator CacheMode="{Binding RelativeSource={RelativeSource Self}, Path=(materialDesign:ShadowAssist.CacheMode)}">
                            <Grid>
                                <Border Background="{TemplateBinding Background}" 
                                    CornerRadius="{Binding Path=(materialDesign:ButtonAssist.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}"
                                    BorderThickness="{TemplateBinding BorderThickness}"
                                    BorderBrush="{TemplateBinding BorderBrush}"
                                    x:Name="border" 
                                    Effect="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ShadowAssist.ShadowDepth), Converter={StaticResource ShadowConverter}}"/>
                                <ProgressBar x:Name="ProgressBar"
                                         Style="{DynamicResource MaterialDesignLinearProgressBar}"
                                         Minimum="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ButtonProgressAssist.Minimum)}"
                                         Maximum="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ButtonProgressAssist.Maximum)}"
                                         Foreground="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ButtonProgressAssist.IndicatorForeground)}"
                                         Background="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ButtonProgressAssist.IndicatorBackground)}"
                                         Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ButtonProgressAssist.Value)}"
                                         IsIndeterminate="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ButtonProgressAssist.IsIndeterminate)}"
                                         Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ButtonProgressAssist.IsIndicatorVisible), Converter={StaticResource BooleanToVisibilityConverter}}"
                                         Height="{TemplateBinding Height}"
                                         Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ButtonBase}}, Path=ActualWidth}"
                                         Opacity="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ButtonProgressAssist.Opacity)}"
                                         HorizontalAlignment="Left"
                                         VerticalAlignment="Center">
                                </ProgressBar>
                            </Grid>
                        </AdornerDecorator>
                        <materialDesign:Ripple Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" Focusable="False"
                                ContentStringFormat="{TemplateBinding ContentStringFormat}"
                                HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                                VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                                Padding="{TemplateBinding Padding}"
                                SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
                            <materialDesign:Ripple.Clip>
                                <MultiBinding Converter="{StaticResource BorderClipConverter}">
                                    <Binding ElementName="border" Path="ActualWidth" />
                                    <Binding ElementName="border" Path="ActualHeight" />
                                    <Binding ElementName="border" Path="CornerRadius" />
                                    <Binding ElementName="border" Path="BorderThickness" />
                                </MultiBinding>
                            </materialDesign:Ripple.Clip>
                        </materialDesign:Ripple>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsDefault" Value="True">
                            <Setter Property="BorderThickness" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}},Converter={StaticResource ButtonDefaultPropertyConverter}}" />
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter TargetName="border" Property="materialDesign:ShadowAssist.Darken" Value="True" />
                        </Trigger>
                        <Trigger Property="IsKeyboardFocused" Value="true">
                            <Setter TargetName="border" Property="materialDesign:ShadowAssist.Darken" Value="True" />
                            <Setter Property="BorderThickness" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Converter={StaticResource ButtonDefaultPropertyConverter}, UpdateSourceTrigger=Explicit}" />
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Opacity" Value="0.23"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

按钮看起来像这样

<Button Style="{StaticResource MyButton}" Grid.Row="0" x:Name="btn1" Height="40" Width="100" Content="Button1" IsDefault="True" />
<Button Style="{StaticResource MyButton}" Grid.Row="1" x:Name="btn2" Height="40" Width="100" Content="Button2" />

虽然这几乎符合我的目的。但唯一的问题是当按钮失去焦点时,转换器不会被触发。 我用谷歌搜索了它,发现我可以将 EventTriggers 用于 LostFocus 事件。 EventTrigger 的问题是我不能在 EventTriggers 中使用绑定。

所以现在我被卡住了。

如果有人能帮忙就太好了……

提前致谢

【问题讨论】:

  • 为什么不简单地将EventSetter 添加到Style 并以编程方式处理LostFocus 事件?
  • 谢谢@mm8。我使用了 EventSetter,它解决了我的问题...:) :)

标签: wpf ivalueconverter eventtrigger


【解决方案1】:

您可以简单地将EventSetter 添加到Style 并以编程方式处理LostFocus 事件。

如果Style 定义在ResourceDictionary 中,则应向其添加code-behind class 并在其中定义事件处理程序。

【讨论】:

    【解决方案2】:

    转换器ButtonDefaultPropertyConverter只能正确处理前两个条件。 他并不总是能够应付第三种情况。 只有当窗口焦点从一个按钮移动到另一个按钮并且没有其他具有焦点的元素时,这才是正确的。

    对于正常操作,转换器必须至少传输所有其他按钮的焦点值。 在我看来,使用附加属性会更容易实现。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-02-02
      • 2021-07-29
      • 1970-01-01
      • 1970-01-01
      • 2012-09-18
      • 1970-01-01
      • 2010-12-20
      相关资源
      最近更新 更多