具有绑定图像状态和悬停图像的WPF无铬按钮

本文关键字:图像 WPF 按钮 绑定 状态 悬停 | 更新日期: 2023-09-27 18:11:12

我在WPF应用程序中有一个使用以下XAML样式的无色按钮:但是,我想用它做两件事。

  1. 我希望显示的图像是两个图像之一,这取决于按钮所在模板的数据上下文的绑定属性。该属性是一个布尔值。
  2. 我想显示的图像改变,当鼠标悬停在按钮上,是图像,将显示当以上的边界属性为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>

具有绑定图像状态和悬停图像的WPF无铬按钮

你需要做几件事:

  1. 为您想要显示的每个图像设置ImageBrush资源。
  2. 为具有内置Style的按钮创建ControlTemplate,鼠标移到Trigger上。
  3. 通过将Background属性绑定到点1的ImageBrush资源之一来设置Button上的图像。
  4. 将鼠标移到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>
这里的关键是在BorderStyle上实现触发器,而不是直接在Button上实现触发器。当试图从应用于ButtonStyle设置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属性的绑定来配置,如上面的示例所示。