具有绑定图像状态和悬停图像的WPF无铬按钮
本文关键字:图像 WPF 按钮 绑定 状态 悬停 | 更新日期: 2023-09-27 18:11:12
我在WPF应用程序中有一个使用以下XAML样式的无色按钮:但是,我想用它做两件事。
- 我希望显示的图像是两个图像之一,这取决于按钮所在模板的数据上下文的绑定属性。该属性是一个布尔值。
- 我想显示的图像改变,当鼠标悬停在按钮上,是图像,将显示当以上的边界属性为True。
我试过几种方法,但似乎都不奏效。我尝试了一个具有如下绑定值的转换器:
<x:ArrayExtension x:Key="ThumbsDown" Type="BitmapImage">
<BitmapImage UriSource="/Elpis;component/Images/thumbDown.png" />
<BitmapImage UriSource="/Elpis;component/Images/thumbBan.png" />
</x:ArrayExtension>
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var data = (object[])parameter;
if (data.Length != 2 || value == null || value.GetType() != typeof(bool))
return data[0];
if(!((bool)value))
return data[0];
else
return data[1];
}
<Button Name="btnThumbDown" Grid.Column="1" Style="{StaticResource NoChromeButton}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Background="Transparent"
Click="btnThumbDown_Click">
<Image Width="32" Height="32" Margin="2"
Source="{Binding Banned,
Converter={StaticResource binaryChooser},
ConverterParameter={StaticResource ThumbsDown}}"/>
</Button>
但是这会导致两个问题。我对悬停图像所做的一切都不起作用了,设计师抛出了一个异常。
下面的不透明度触发器可以去掉,因为我现在只想要悬停效果。
关于如何使其正常工作的任何想法?
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="NoChromeButton" TargetType="{x:Type ButtonBase}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ButtonBase}">
<Grid x:Name="Chrome" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}" RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="0.75"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="Opacity" Value="1.0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
你需要做几件事:
- 为您想要显示的每个图像设置
ImageBrush
资源。 - 为具有内置
Style
的按钮创建ControlTemplate
,鼠标移到Trigger
上。 - 通过将
Background
属性绑定到点1的ImageBrush
资源之一来设置Button
上的图像。 - 将鼠标移到
Trigger
上,将Background
属性更改为另一个ImageBrush
资源。
请参阅下面的代码以获得一个简单的示例(您必须使用自己的图像来代替"up"answers"down")。
<Window x:Class="ButtonHover.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:ButtonHover"
Title="MainWindow" Height="227" Width="280">
<Window.Resources>
<ImageBrush x:Key="imageABrush"
ImageSource="/ButtonHover;component/Resources/up.png"/>
<ImageBrush x:Key="imageBBrush"
ImageSource="/ButtonHover;component/Resources/down.png"/>
<ControlTemplate x:Key="buttonTemplate" TargetType="Button">
<Border BorderThickness="3" BorderBrush="Black">
<Border.Style>
<Style>
<Style.Setters>
<Setter Property="Border.Background"
Value="{StaticResource imageABrush}"/>
</Style.Setters>
<Style.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Border.Background"
Value="{StaticResource imageBBrush}"/>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</ControlTemplate>
</Window.Resources>
<Grid>
<Button Height="75"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="75"
Template="{StaticResource buttonTemplate}" />
</Grid>
</Window>
这里的关键是在Border
的Style
上实现触发器,而不是直接在Button
上实现触发器。当试图从应用于Button
的Style
设置Border
的属性时,我无法让它工作。您也可以将Border
替换为Rectangle
或任何其他具有BackGround
属性的ContentControl
。
我将通过向按钮的ControlTemplate
添加Image
控件来解决这个问题,然后创建一个MultiDataTrigger
,其中IsMouseOver
有一个条件,要绑定到的布尔属性有一个条件。触发器然后在活动时设置图像的来源。
下面是完成这个的样式。我已经假设按钮有一个包含布尔属性的DataContext,并且这个布尔属性叫做MyBoolean。
<Style x:Key="NoChromeButton" TargetType="{x:Type ButtonBase}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<!-- I assume that the button has a DataContext with a boolean property called MyBoolean -->
<ControlTemplate TargetType="{x:Type ButtonBase}">
<Grid x:Name="Chrome" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<!-- Not sure about what the button should look like, so I made it an image to the left
and the button's content to the right -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image x:Name="ButtonImage" Grid.Column="0" Source="/Elpis;component/Images/thumbBan.png" />
<ContentPresenter Grid.Column="1"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}" RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
<ControlTemplate.Triggers>
<!-- As I understand it, ThumbBan should be shown when IsMouseOver == True OR the bound boolean is true,
so if you invert that you could say that the ThumbDown image should be shown
when IsMouseOver == false AND the bound boolean is false, which is what this trigger does -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsMouseOver, ElementName=Chrome}" Value="False" />
<Condition Binding="{Binding MyBoolean}" Value="False" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter TargetName="ButtonImage" Property="Source" Value="/Elpis;component/Images/thumbDown.png" />
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="0.75"/>
</Trigger>
<!-- The trigger that sets opacity to 1 for IsMouseOver false is not needed, since 1 is the
default and will be the opacity as long as the trigger above is not active -->
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
如果我误解了需求,您可能必须切换触发器激活时应该显示的图像。
这里的技巧是使用MultiDataTrigger
,以便您提到的两个条件可以组合在一起。通常,当绑定到控件的IsMouseOver
时,您可能会使用Trigger
而不是DataTrigger
。但是,由于布尔属性需要DataTrigger
,因此可以使用该绑定的ElementName
属性将IsMouseOver
绑定编写为DataTrigger
。通过这样做,您可以使用MultiDataTrigger来组合两者。
更新:
为了添加对自定义所使用的图像的支持,以及要绑定到的属性,对于每个按钮实例,我将继承Button
类并添加一对DependencyProperties
。
public class ImageButton : Button
{
public static readonly DependencyProperty ActiveImageUriProperty =
DependencyProperty.RegisterAttached("ActiveImageUri", typeof(Uri), typeof(ImageButton),
new PropertyMetadata(null));
public static readonly DependencyProperty InactiveImageUriProperty =
DependencyProperty.RegisterAttached("InactiveImageUri", typeof(Uri), typeof(ImageButton),
new PropertyMetadata(null));
public static readonly DependencyProperty IsActiveProperty =
DependencyProperty.RegisterAttached("IsActive", typeof(bool), typeof(ImageButton),
new PropertyMetadata(false));
public Uri ActiveImageUri
{
get { return (Uri)GetValue(ActiveImageUriProperty); }
set { SetValue(ActiveImageUriProperty, value); }
}
public Uri InactiveImageUri
{
get { return (Uri)GetValue(InactiveImageUriProperty); }
set { SetValue(InactiveImageUriProperty, value); }
}
public bool IsActive
{
get { return (bool)GetValue(IsActiveProperty); }
set { SetValue(IsActiveProperty, value); }
}
}
这个类可以按以下方式使用:
<SomeNamespace:ImageButton Height="23" Width="100" Content="Button 1"
ActiveImageUri="/Elpis;component/Images/thumbBan.png"
InactiveImageUri="/Elpis;component/Images/thumbDown.png"
IsActive="{Binding MyBoolean}" />
<SomeNamespace:ImageButton Height="23" Width="100" Content="Button 2"
ActiveImageUri="/Elpis;component/Images/someOtherImage.png"
InactiveImageUri="/Elpis;component/Images/yetAnotherImage.png"
IsActive="{Binding SomeOtherBooleanProperty}" />
然后可以将控件模板修改如下:
<Style TargetType="SomeNamespace:ImageButton">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Padding" Value="1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type SomeNamespace:ImageButton}">
<Grid x:Name="Chrome" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image x:Name="ButtonImage" Grid.Column="0"
Source="{Binding ActiveImageUri, RelativeSource={RelativeSource TemplatedParent}}" />
<ContentPresenter Grid.Column="1"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}" RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Grid>
<ControlTemplate.Triggers>
<!-- The "active" image should be shown when IsMouseOver == True OR the bound boolean is true,
so if you invert that you could say that the "inactive" image should be shown
when IsMouseOver == false AND the bound boolean is false, which is what this trigger does -->
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="False" />
<Condition Property="IsActive" Value="False" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter TargetName="ButtonImage" Property="Source"
Value="{Binding InactiveImageUri, RelativeSource={RelativeSource TemplatedParent}}" />
</MultiTrigger.Setters>
</MultiTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="0.75" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
这里的主要变化是,图像的源被设置为依赖项属性的值,而不是硬编码的uri,并且MultiDataTrigger
被更改为绑定到依赖项属性的MultiTrigger
。以前布尔属性的路径也是硬编码的,但现在可以通过在创建按钮时更改IsActive
属性的绑定来配置,如上面的示例所示。