WPF按钮鼠标悬停时修改DrawingImage画笔颜色的问题
我在WPF视图中使用如下按钮:
MyView.xaml:
<Button Grid.Column="1" Style="{StaticResource HoverOnCloseButton}" Background="Transparent" BorderBrush="Transparent" Command="{Binding CancelBtnCommand}"> <Button.Content> <Image Width="10" Height="10" MinWidth="10" MinHeight="10" Stretch="Uniform" UseLayoutRounding="True" SnapsToDevicePixels="True" RenderOptions.BitmapScalingMode="HighQuality" Source="{Binding MySettings.CrossIcon, UpdateSourceTrigger=PropertyChanged}"/> </Button.Content> </Button>
按钮内容的图片加载自实现INotifyPropertyChanged接口的MySettings类的CrossIcon属性:
public class MySettings: INotifyPropertyChanged { public ImageSource CrossIcon { get; private set; } public ResourceDictionary ResourceDict { get; set; } public MySettings() { InitializeDictionary(); this.CrossIcon = ResourceDict["crossDrawingImage"] as DrawingImage; } private void InitializeDictionary() { String url = String.Format("pack://application:,,,/myApp;component/Resources/Themes/Dark/MyDictionary.xaml"); // Load resource dictionary ResourceDict = new ResourceDictionary { Source = new Uri(url, UriKind.RelativeOrAbsolute) }; } }
crossDrawingImage在MyDictionary.xaml中定义如下:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:core="clr-namespace:System;assembly=mscorlib"> <DrawingImage x:Key="crossDrawingImage"> <DrawingImage.Drawing> <DrawingGroup ClipGeometry="M0,0 V10 H10 V0 H0 Z"> <GeometryDrawing Brush="#FF000000" Geometry="F0 M10,10z M0,0z M4.33959,5L0,0.660409 0.660409,0 5,4.33959 9.33959,0 10,0.660409 5.66041,5 10,9.33959 9.33959,10 5,5.66041 0.660409,10 0,9.33959 4.33959,5z" /> </DrawingGroup> </DrawingImage.Drawing> </DrawingImage> </ResourceDictionary>
我想通过按钮绑定的HoverOnCloseButton样式,利用IsMouseOver触发器修改DrawingImage的画笔颜色(原本是#FF000000),于是写了如下样式:
<Style x:Key="HoverOnCloseButton" TargetType="{x:Type Button}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ButtonBase}"> <Border Background="{TemplateBinding Panel.Background}" BorderBrush="Transparent" Name="border" SnapsToDevicePixels="True"> <ContentPresenter Name="contentPresenter" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Border> <ControlTemplate.Triggers> <Trigger Property="UIElement.IsMouseOver" Value="True"> <Setter Property="Panel.Background" TargetName="border" Value="Red"/> <Setter Property="TextElement.Foreground" TargetName="contentPresenter"> <Setter.Value> <SolidColorBrush>#FFFFFF</SolidColorBrush> </Setter.Value> </Setter> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
但设置TextElement.Foreground的Setter完全没效果。我还试过替换整个DrawingImage:在资源字典里创建用白色画笔的crossOnMouseHoverDrawingImage,在MySettings里加对应属性,修改样式触发器设置ContentSource,但还是没生效。请问我哪里错了?怎么实现按钮鼠标悬停时修改DrawingImage的画笔颜色?
为什么之前的方法失效?
TextElement.Foreground不作用于DrawingImage:TextElement.Foreground是针对文本元素的属性,你的按钮内容是Image控件,绑定的DrawingImage里的Brush是硬编码在资源中的静态值,不会继承Foreground属性,所以这个Setter根本影响不到画笔颜色。- 替换
DrawingImage的方案错误:你修改样式触发器设置ContentSource的方式不对,ContentSource不是用来直接切换Content的属性,而且如果Image的Source是绑定到ViewModel的属性,触发器无法直接修改这个绑定值。
可行实现方法
方法一:将画笔提取为资源,通过触发器修改画笔
- 先修改资源字典,把画笔提取为可复用的资源:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <!-- 定义默认和悬停的画笔 --> <SolidColorBrush x:Key="CrossIconDefaultBrush" Color="#FF000000"/> <SolidColorBrush x:Key="CrossIconHoverBrush" Color="#FFFFFF"/> <DrawingImage x:Key="crossDrawingImage"> <DrawingImage.Drawing> <DrawingGroup ClipGeometry="M0,0 V10 H10 V0 H0 Z"> <!-- 绑定画笔到动态资源 --> <GeometryDrawing Brush="{DynamicResource CrossIconDefaultBrush}" Geometry="F0 M10,10z M0,0z M4.33959,5L0,0.660409 0.660409,0 5,4.33959 9.33959,0 10,0.660409 5.66041,5 10,9.33959 9.33959,10 5,5.66041 0.660409,10 0,9.33959 4.33959,5z" /> </DrawingGroup> </DrawingImage.Drawing> </DrawingImage> </ResourceDictionary>
- 修改按钮样式,在
IsMouseOver触发器中替换动态资源的画笔:
<Style x:Key="HoverOnCloseButton" TargetType="{x:Type Button}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ButtonBase}"> <Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="Transparent" SnapsToDevicePixels="True"> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="border" Property="Background" Value="Red"/> <!-- 在Border级别覆盖动态资源,影响内部DrawingImage的画笔 --> <Setter Property="Resources" TargetName="border"> <Setter.Value> <ResourceDictionary> <SolidColorBrush x:Key="CrossIconDefaultBrush" Color="#FFFFFF"/> </ResourceDictionary> </Setter.Value> </Setter> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
这种方法利用DynamicResource的动态替换特性,在按钮的Border级别覆盖资源,从而影响内部DrawingImage的画笔。
方法二:在按钮模板中直接定义两个Image,通过触发器切换可见性
这种方法不需要修改ViewModel和资源字典,直接在样式里处理:
<Style x:Key="HoverOnCloseButton" TargetType="{x:Type Button}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ButtonBase}"> <Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="Transparent" SnapsToDevicePixels="True"> <!-- 默认图标 --> <Image x:Name="defaultIcon" Width="10" Height="10" Stretch="Uniform" Source="{DynamicResource crossDrawingImage}"/> <!-- 悬停图标 --> <Image x:Name="hoverIcon" Width="10" Height="10" Stretch="Uniform" Visibility="Collapsed"> <Image.Source> <DrawingImage> <DrawingImage.Drawing> <DrawingGroup ClipGeometry="M0,0 V10 H10 V0 H0 Z"> <GeometryDrawing Brush="#FFFFFF" Geometry="F0 M10,10z M0,0z M4.33959,5L0,0.660409 0.660409,0 5,4.33959 9.33959,0 10,0.660409 5.66041,5 10,9.33959 9.33959,10 5,5.66041 0.660409,10 0,9.33959 4.33959,5z" /> </DrawingGroup> </DrawingImage.Drawing> </DrawingImage> </Image.Source> </Image> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="border" Property="Background" Value="Red"/> <Setter TargetName="defaultIcon" Property="Visibility" Value="Collapsed"/> <Setter TargetName="hoverIcon" Property="Visibility" Value="Visible"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
注意这里要把原来Button里的Image内容去掉,因为模板里已经包含了图标,按钮直接用这个样式即可。
方法三:通过ViewModel绑定切换ImageSource
如果坚持要通过ViewModel控制,可以给MySettings添加CrossHoverIcon属性,然后在按钮样式里通过触发器修改Image的Source绑定:
- 修改
MySettings:
public class MySettings: INotifyPropertyChanged { public ImageSource CrossIcon { get; private set; } public ImageSource CrossHoverIcon { get; private set; } public ResourceDictionary ResourceDict { get; set; } public MySettings() { InitializeDictionary(); this.CrossIcon = ResourceDict["crossDrawingImage"] as DrawingImage; this.CrossHoverIcon = ResourceDict["crossOnMouseHoverDrawingImage"] as DrawingImage; } // INotifyPropertyChanged接口实现代码省略 }
- 修改按钮样式,用触发器修改
Image的Source:
<Style x:Key="HoverOnCloseButton" TargetType="{x:Type Button}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ButtonBase}"> <Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="Transparent" SnapsToDevicePixels="True"> <Image x:Name="iconImage" Width="10" Height="10" Stretch="Uniform" Source="{Binding MySettings.CrossIcon}"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="border" Property="Background" Value="Red"/> <Setter TargetName="iconImage" Property="Source" Value="{Binding MySettings.CrossHoverIcon}"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
同样,按钮里不需要再定义Content,模板已经包含了Image。
内容的提问来源于stack exchange,提问作者Willy

