WPF 在 Button.LostFocus 上触发绑定
WPF Trigger on Button.LostFocus with binding
我正在设计一个 WPF 应用程序,我想在其中为 window 的默认按钮提供一个标记(例如红色边框)。标记的行为应该是
- 当没有按钮获得焦点时,标记应该在默认按钮上可见
- 如果默认按钮获得焦点,那么标记也应该可见
- 如果任何其他按钮获得焦点,则应隐藏标记
由于我使用的是materialDesign,所以我不得不扩展“MaterialDesignRaisedButton”样式。
我写了一个转换器,它将检查 window 中存在的所有按钮,并根据我的要求在默认按钮上设置标记。
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 中使用绑定。
所以现在我卡住了。
如果有人能提供帮助,那就太好了...
提前致谢
转换器ButtonDefault属性转换器只能正确处理前两个条件。
第三种情况,他不一定总能应付得来。
仅当 window 焦点从一个按钮移动到另一个按钮并且没有其他元素具有焦点时,这是正确的。
为了正常操作,转换器必须至少传输所有其他按钮的焦点值。
在我看来,使用 Attached 属性.
会更容易实现
您只需将 EventSetter
添加到 Style
并以编程方式处理 LostFocus
事件。
如果Style
定义在ResourceDictionary
中,您应该添加一个code-behind class并在其中定义事件处理程序。
我正在设计一个 WPF 应用程序,我想在其中为 window 的默认按钮提供一个标记(例如红色边框)。标记的行为应该是
- 当没有按钮获得焦点时,标记应该在默认按钮上可见
- 如果默认按钮获得焦点,那么标记也应该可见
- 如果任何其他按钮获得焦点,则应隐藏标记
由于我使用的是materialDesign,所以我不得不扩展“MaterialDesignRaisedButton”样式。 我写了一个转换器,它将检查 window 中存在的所有按钮,并根据我的要求在默认按钮上设置标记。
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 中使用绑定。
所以现在我卡住了。
如果有人能提供帮助,那就太好了...
提前致谢
转换器ButtonDefault属性转换器只能正确处理前两个条件。 第三种情况,他不一定总能应付得来。 仅当 window 焦点从一个按钮移动到另一个按钮并且没有其他元素具有焦点时,这是正确的。
为了正常操作,转换器必须至少传输所有其他按钮的焦点值。 在我看来,使用 Attached 属性.
会更容易实现您只需将 EventSetter
添加到 Style
并以编程方式处理 LostFocus
事件。
如果Style
定义在ResourceDictionary
中,您应该添加一个code-behind class并在其中定义事件处理程序。