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

时间:2011-10-12 01:30:00

标签: c# wpf xaml

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

  1. 我希望显示的图像是两个图像中的一个,具体取决于按钮所在模板的datacontext的绑定属性。该属性是一个布尔值。如果为False则显示一个图像,如果为True则显示另一个图像。
  2. 我希望当鼠标悬停在按钮上时,显示的图像会发生变化,并且当上面的绑定属性为True时,图像会显示。
  3. 我尝试了几件事,但似乎没有什么工作。我尝试了一个带有绑定值的转换器:

            <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>
    

    但这会导致2个问题。我没有为悬停图像做任何事情,设计师抛出异常。

    来自下方的不透明度触发器可以继续,因为我现在只想悬停。

    有关如何使其正常运作的任何想法?

    <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>
    

2 个答案:

答案 0 :(得分:3)

你将不得不做几件事:

  1. 为要显示的每个图片设置ImageBrush个资源。
  2. 为具有内置ControlTemplate且鼠标悬停Style的按钮创建Trigger
  3. 通过将Button属性绑定到第1点的Background资源之一,在ImageBrush上设置图像。
  4. 使用鼠标悬停TriggerBackground属性更改为其他ImageBrush资源。
  5. 请参阅下面的代码以获取一个简单示例(您必须使用自己的图像代替“向上”和“向下”)。

        <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>
    

    此处的关键是在Style的{​​{1}}而不是Border直接实施触发器。尝试将Button的属性设置为应用于Border的{​​{1}}时,我无法使其工作。您还可以将Style替换为Button或任何其他Border Rectangle属性。

    从此处开始,根据您的应用进行自定义。

答案 1 :(得分:1)

我会通过向按钮的Image添加ControlTemplate控件来解决此问题,然后为MultiDataTrigger创建一个IsMouseOver条件,并为其创建一个条件要绑定的布尔属性。然后触发器在激活时设置图像源。

以下是完成此操作的样式。我假设按钮有一个包含boolean属性的DataContext,并且boolean属性被称为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,这样你提到的两个条件就可以合并。通常,在绑定到控件的Trigger时,您可能会使用DataTrigger而不是IsMouseOver。但是,由于布尔属性需要DataTrigger,因此IsMouseOver绑定可以使用绑定的DataTrigger属性写为ElementName。通过这样做,您可以使用MultiDataTrigger来组合这两者。

UPDATE:

为了添加对自定义所用图像的支持,以及要绑定到哪个属性,对于每个按钮实例,我将继承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的{​​{1}}依赖属性。以前,布尔属性的路径也是硬编码的,但现在可以通过在创建按钮时更改IsActive属性的绑定来配置,如上例所示。