介绍 本文的目标是创建满足以下条件的图像按钮: 按钮是扁平的,没有文字图像是完全定义在xaml图像可以轻松和动态设置资源键(可绑定)图像有可绑定的属性,如前景,背景,不透明度,填充颜色等。按钮在按下或IsMouseOver="True"时的外观可以不需要太多额外的代码就进行样式化 笔记 本例中使用的图标:https://commons.wikimedia.org/wiki/File:Speaker_Icon.svg(许可:公共领域) 在这个解决方案中,nuget包的属性发生了变化。Fody使用。 它使用Visual Studio 2015 Community, . net Framework 4.5.2开发。也在Visual Studio 2010 . net Framework 4.0上测试过。 使用Inkscape 0.91编辑SVG文件。 微软XPS文档编写器用于转换SVG文件。 这个想法 用一个向量图像设置画布作为VisualBrush的视觉,然后设置该画笔作为按钮的背景,并使用附加属性使所有它可绑定。 实现细节,使用的代码 1. 按钮是扁平的,没有文字 这个很简单。只需重写默认样式: 隐藏,复制Code
<Stylex:Key="StyleButtonTransparent"TargetType="{x:Type Button}"> <SetterProperty="FocusVisualStyle"Value="{x:Null}"/> <SetterProperty="Template"> <Setter.Value> <ControlTemplateTargetType="Button"> <BorderCornerRadius="5"Background="{TemplateBinding Background}"/> </ControlTemplate> </Setter.Value> </Setter> </Style>
虽然这是基本的想法,但它还需要进一步设计。注意,这里没有ContentPresenter,因为按钮的外观只使用它的Background属性来设置。 2. 图像完全在XAML中定义 首先,在WPF中,可以使用VisualBrush绘制。这样的画笔可以设置为控件的背景。VisualBrush的视觉可以是很多对象。在我们的例子中,最简单的解决方案是使用Canvas和一些将在XAML中定义的向量图像。 获得这样的定义比您想象的要容易,尽管它需要一两个解决方案。 如果您已经有一个矢量图像,将其保存为SVG。如果你有一个光栅图像,它可以转换为SVG使用一些在线转换器(例如,谷歌“转换png到SVG”)或者你可以做它使用Inkscape的路径/跟踪位图。 在本例中,我们将使用一个已经是SVG格式的图标,尽管我在Inkscape中对其进行了一些修改,以标准化大小并添加一些背景(附加文件)。基本上,我添加了一个正方形的白色背景,将对象分组,将文档属性中的单位改为英寸,然后将文档的宽度和高度设置为1,并调整对象组的大小以适应文档。这样,我的图像是1x1英寸,这将导致产生的图像是96x96 px(因为我的屏幕dpi)后,打印(Inkscape/文件/打印)在Microsoft XPS文档Writer,没有恼人的百分数数字。 为什么打印出来?事实证明,“常规”SVG格式与XAML中使用的语法略有不同。不用手动转换,我们可以使用Microsoft XPS Document Writer来打印它。然后,将生成的XPS文件重命名为ZIP,并将文件解压缩到Documents1Pages1.fpage中。在此之后,将FPAGE文件的扩展名更改为XML(或使用文本编辑器打开它)。在内部,您将拥有一个漂亮的、兼容xforms的映像定义。(几乎)您必须做的最后一件事是将FixedPage标记替换为Canvas。 因为我们的画布和几何图形将有绑定属性,它们无论如何都不会被冻结,所以设置PresentationOptions: freeze ="True"不会有任何作用。另一方面,我们必须设置x:Shared="False",这样我们就可以在应用程序中多次使用画布。 由此产生的XAML: 隐藏,收缩,复制Code
<Canvasx:Key="Canvas_Speaker"x:Shared="False"Width="96"Height="96"> <PathData="F1 M 0,0 L 96,0 96,96 0,96 z"Fill="#ffffffff"/> <PathData="F1 M 7.68,60.96 L 28,60.96 50.4,80.32 50.4,17.6 28.32,36.48 7.68,36.48 z"Fill="#ff111111"/> <PathData="F1 M 7.68,60.96 L 28,60.96 50.4,80.32 50.4,17.6 28.32,36.48 7.68,36.48 z"Stroke="#ff111111"StrokeThickness="6.4"StrokeLineJoin="Round"/> <PathData="F1 M 61.6,62.72 C 64,58.72 65.44,54.08 65.44,49.12 65.44,44 64,39.2 61.44,35.2"Stroke="#ff111111"StrokeThickness="6.4"StrokeLineJoin="Miter"StrokeStartLineCap="Round"StrokeEndLineCap="Round"StrokeMiterLimit="4"/> <PathData="F1 M 70.4,26.24 C 75.2,32.64 77.92,40.48 77.92, 49.12 77.92,57.44 75.2,65.28 70.56,71.68"Stroke="#ff111111"StrokeThickness="6.4"StrokeLineJoin="Miter"StrokeStartLineCap="Round"StrokeEndLineCap="Round"StrokeMiterLimit="4"/> <PathData="F1 M 78.88,80 C 85.6,71.52 89.76,60.8 89.76,49.12 89.76, 37.28 85.6,26.4 78.72,17.92"Stroke="#ff111111"StrokeThickness="6.4"StrokeLineJoin="Miter"StrokeStartLineCap="Round"StrokeEndLineCap="Round"StrokeMiterLimit="4"Clip="M 65.28,4.48 L 96,4.48 96,93.6 65.28,93.6 z"/> </Canvas>
3.图像可通过按键进行轻松动态设置。4. 图像具有可绑定属性,如前景、背景、不透明度、填充颜色等。 这可以使用附加属性来完成。让我们定义一个类VisBg: 隐藏,复制Code
public class VisBg: DependencyObject
它将有5个属性用于设置图像的视觉属性:ResourceKey,前景,背景,不透明度和填充。它还将具有一个属性来公开生成的VisualBrush,该属性称为BrushValue。然后,将有一个私有属性来保存我们的图像(画布)将要绑定到的数据:BrushData。 让我们从最后一个开始。 隐藏,复制Code
private static readonly DependencyProperty BrushDataProperty = DependencyProperty.RegisterAttached( "BrushData", typeof(VisualBackgroundData), typeof(VisBg), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.NotDataBindable | FrameworkPropertyMetadataOptions.Inherits)); private static VisualBackgroundData GetBrushData(DependencyObject d) { return (VisualBackgroundData)d.GetValue(BrushDataProperty); } private static void SetBrushData(DependencyObject d, VisualBackgroundData value) { d.SetValue(BrushDataProperty, value); }
VisualBackgroundData是一个用于获取正确的资源并设置Canvas的DataContext的类。为了提高资源键的可读性和避免重复,画布在ResourceDictionaries中声明,键以“Canvas_”开头,在搜索资源时,默认添加这个头。 VisualBackgroundData类还保存了对实例化它的源框架元素的引用。它用于查找为该元素定义的资源。也可以在没有源引用的情况下使用application . tryfindresource (key),但这将迫使您始终在应用程序级别导入App.xaml文件中的所有资源。 在VisualBackgroundData类中,当键发生变化时,应用程序会搜索适当的画布,以及它是否为found,它被设置为VisualBrush的可视化,稍后将用于管理控件的背景。 隐藏,收缩,复制Code
private void OnKeyChanged() { if (string.IsNullOrEmpty(this.Key)) { this.Value = Brushes.Transparent; return; } string key = this.Key; object res = this.GetResource(key); if (res == null || !(res is Canvas)) { key = cHeader + key; res = this.GetResource(key); if (res == null || !(res is Canvas)) { this.Value = Brushes.Transparent; return; } } if (!(res is Canvas)) { this.Value = Brushes.Transparent; return; } Canvas c = (Canvas)res; c.DataContext = this; c.SnapsToDevicePixels = true; c.UseLayoutRounding = true; if (this.Value == null || !(this.Value is VisualBrush)) { VisualBrush b = new VisualBrush(c); b.TileMode = TileMode.None; b.Stretch = Stretch.Fill; this.Value = b; } else { ((VisualBrush)this.Value).Visual = c; } }
其余附加的属性(ResourceKey,前景,背景,不透明度,填充和画笔值)都定义了PropertyChangedCallback方法。在这些方法中,必要时实例化包含数据的私有附加属性BrushData,并设置该实例中的适当属性。 现在,我们回到图像的XAML 定义。画布上使用的画笔及其不透明度属性: 隐藏,收缩,复制Code
<Canvasx:Key="Canvas_Speaker"x:Shared="False"Width="96"Height="96"Opacity="{Binding Opacity}"> <PathData="F1 M 0,0 L 96,0 96,96 0,96 z"Fill="{Binding Background}"/> <PathData="F1 M 7.68,60.96 L 28,60.96 50.4,80.32 50.4,17.6 28.32,36.48 7.68,36.48 z"Fill="{Binding FillBrush}"/> <PathData="F1 M 7.68,60.96 L 28,60.96 50.4,80.32 50.4,17.6 28.32,36.48 7.68,36.48 z"Stroke="{Binding Foreground}"StrokeThickness="6.4"StrokeLineJoin="Round"/> <PathData="F1 M 61.6,62.72 C 64,58.72 65.44,54.08 65.44,49.12 65.44,44 64,39.2 61.44,35.2"Stroke="{Binding Foreground}"StrokeThickness="6.4"StrokeLineJoin="Miter"StrokeStartLineCap="Round"StrokeEndLineCap="Round"StrokeMiterLimit="4"/> <PathData="F1 M 70.4,26.24 C 75.2,32.64 77.92,40.48 77.92, 49.12 77.92,57.44 75.2,65.28 70.56,71.68"Stroke="{Binding Foreground}"StrokeThickness="6.4"StrokeLineJoin="Miter"StrokeStartLineCap="Round"StrokeEndLineCap="Round"StrokeMiterLimit="4"/> <PathData="F1 M 78.88,80 C 85.6,71.52 89.76,60.8 89.76,49.12 89.76, 37.28 85.6,26.4 78.72,17.92"Stroke="{Binding Foreground}"StrokeThickness="6.4"StrokeLineJoin="Miter"StrokeStartLineCap="Round"StrokeEndLineCap="Round"StrokeMiterLimit="4"Clip="M 65.28,4.48 L 96,4.48 96,93.6 65.28,93.6 z"/> </Canvas>
5. 按钮在按下或IsMouseOver="true"时的外观可以不需要太多额外的代码就进行样式化 以上述方式定义的附加属性是可绑定的,并且可以以多种方式使用。 (我们的类是在库项目中定义的,因此我们必须定义名称空间xmlns:lib="clr-namespace:VisExtLib;assembly=VisExtLib",测试项目是VisExtTest)。 例如,让我们定义一个按钮,图像有: 黄色背景黑色填充红色前景 隐藏,复制Code
<ButtonBackground="{Binding Path=(lib:VisBg.BrushValue), Mode=OneWay, RelativeSource={RelativeSource Self}}"Style="{StaticResource StyleButtonTransparent}"Margin="10"Width="{Binding Path=ActualHeight, RelativeSource={RelativeSource Self}}"VerticalAlignment="Stretch"lib:VisBg.ResourceKey="Speaker"lib:VisBg.Background="Yellow"lib:VisBg.Foreground="Red"lib:VisBg.Fill="Black"/>
(注意在绑定中围绕着附加属性路径的括号。) 现在,让我们定义一个样式,当按下按钮时,图像的前景和填充颜色(不是按钮本身!)会发生变化。因为我们不使用按钮的实际前景和边界刷属性,我们也可以使用它们(按钮)。pressedCanvasImage前景。填满,和按钮。BorderBrush pressedCanvasImage.Foreground)。这样,我们的黄-黑-红按钮可以在按下时变成黄-紫罗兰-白: 隐藏,收缩,复制Code
<Stylex:Key="StyleButtonTransparentPressed"TargetType="{x:Type Button}"> <SetterProperty="FocusVisualStyle"Value="{x:Null}"/> <SetterProperty="Template"> <Setter.Value> <ControlTemplateTargetType="Button"> <BorderCornerRadius="5"BorderBrush="{TemplateBinding BorderBrush}"BorderThickness="{TemplateBinding BorderThickness}"Background="{Binding Path=(local:VisBg.BrushValue), Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"> <Border.Style> <StyleTargetType="Border"> <SetterProperty="local:VisBg.Foreground"Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:VisBg.Foreground)}"/> <SetterProperty="local:VisBg.Fill"Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:VisBg.Fill)}"/> <SetterProperty="local:VisBg.Opacity"Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:VisBg.Opacity)}"/> <SetterProperty="local:VisBg.Background"Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:VisBg.Background)}"/> <Style.Triggers> <DataTriggerBinding="{Binding Path=IsPressed, RelativeSource={RelativeSource TemplatedParent}}"Value="True"> <SetterProperty="local:VisBg.Foreground"Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=BorderBrush}"/> <SetterProperty="local:VisBg.Fill"Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Foreground}"/> </DataTrigger> </Style.Triggers> </Style> </Border.Style> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
隐藏,复制Code
<ButtonBackground="{Binding Path=(lib:VisBg.BrushValue), Mode=OneWay, RelativeSource={RelativeSource Self}}"Style="{StaticResource StyleButtonTransparentPressed}"Margin="10"Width="{Binding Path=ActualHeight, RelativeSource={RelativeSource Self}}"VerticalAlignment="Stretch"Foreground="White"BorderBrush="Violet"BorderThickness="0"lib:VisBg.ResourceKey="Speaker"lib:VisBg.Background="Yellow"lib:VisBg.Foreground="Red"lib:VisBg.Fill="Black"/>
现在,让我们也这样做,当鼠标在按钮上时,图像的背景将改变为它的填充颜色。当我们这样做的时候,为什么不添加一个不透明度动画开始时按钮被按下? 隐藏,收缩,复制Code
<ButtonBackground="{Binding Path=(lib:VisBg.BrushValue), Mode=OneWay, RelativeSource={RelativeSource Self}}"Width="{Binding Path=ActualHeight, RelativeSource={RelativeSource Self}}"VerticalAlignment="Stretch"Foreground="White"BorderBrush="Violet"BorderThickness="0"Margin="10"lib:VisBg.ResourceKey="Speaker"lib:VisBg.Foreground="Red"lib:VisBg.Fill="Black"> <Button.Style> <StyleTargetType="Button"BasedOn="{StaticResource StyleButtonTransparentPressed}"> <SetterProperty="lib:VisBg.Background"Value="Yellow"/> <Style.Triggers> <TriggerProperty="IsMouseOver"Value="True"> <SetterProperty="lib:VisBg.Background"Value="{Binding Path=(lib:VisBg.Fill), RelativeSource={RelativeSource Self}}"/> </Trigger> </Style.Triggers> </Style> </Button.Style> <Button.Triggers> <EventTriggerRoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <DoubleAnimationStoryboard.TargetProperty="(lib:VisBg.Opacity)"From="1"To="0.3"Duration="0:0:2"AutoReverse="True"RepeatBehavior="2x"/> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button>
…我们可以修改画布,只有图像适当地改变不透明度,而不是它的背景: 隐藏,收缩,复制Code
<Canvasx:Key="Canvas_Speaker"x:Shared="False"Width="96"Height="96"> <PathData="F1 M 0,0 L 96,0 96,96 0,96 z"Fill="{Binding Background}"/> <PathData="F1 M 7.68,60.96 L 28,60.96 50.4,80.32 50.4,17.6 28.32,36.48 7.68,36.48 z"Fill="{Binding FillBrush}"Opacity="{Binding Opacity}"/> <PathData="F1 M 7.68,60.96 L 28,60.96 50.4,80.32 50.4,17.6 28.32,36.48 7.68,36.48 z"Opacity="{Binding Opacity}"Stroke="{Binding Foreground}"StrokeThickness="6.4"StrokeLineJoin="Round"/> <PathData="F1 M 61.6,62.72 C 64,58.72 65.44,54.08 65.44,49.12 65.44,44 64,39.2 61.44,35.2"Stroke="{Binding Foreground}"StrokeThickness="6.4"StrokeLineJoin="Miter"Opacity="{Binding Opacity}"StrokeStartLineCap="Round"StrokeEndLineCap="Round"StrokeMiterLimit="4"/> <PathData="F1 M 70.4,26.24 C 75.2,32.64 77.92,40.48 77.92,49.12 77.92, 57.44 75.2,65.28 70.56,71.68"Opacity="{Binding Opacity}"Stroke="{Binding Foreground}"StrokeThickness="6.4"StrokeLineJoin="Miter"StrokeStartLineCap="Round"StrokeEndLineCap="Round"StrokeMiterLimit="4"/> <PathData="F1 M 78.88,80 C 85.6,71.52 89.76,60.8 89.76,49.12 89.76, 37.28 85.6,26.4 78.72,17.92"Opacity="{Binding Opacity}"Stroke="{Binding Foreground}"StrokeThickness="6.4"StrokeLineJoin="Miter"StrokeStartLineCap="Round"StrokeEndLineCap="Round"StrokeMiterLimit="4"Clip="M 65.28,4.48 L 96,4.48 96,93.6 65.28,93.6 z"/> </Canvas>
的兴趣点 你可以设置SnapsToDevicePixels和uselayoutzing for Canvas为“True”,然后图像会更清晰,但有时会导致几何部分移动奇怪,当调整大小。这对于较小的尺寸来说尤其明显,在图像中,一条路径从另一条路径移动1像素会产生明显的差异。我猜这取决于图像如果你想让它在运行时可调整大小。这取决于你能接受的是什么:模糊还是不完全准确。 虽然在Visual Studio 2010 . net Framework 4.0的设计器中按照上述方式定义和样式的边框和按钮可以显示出来,但在Visual Studio 2015 Community . net Framework 4.5.2中却没有。应用程序运行没有任何问题,也没有任何警告。如果有人知道为什么会这样,并且愿意分享,我会很感激。 本文转载于:http://www.diyabc.com/frontweb/news1066.html