• WPF图像按钮100%在XAML


    介绍 本文的目标是创建满足以下条件的图像按钮: 按钮是扁平的,没有文字图像是完全定义在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

  • 相关阅读:
    转:Windows Socket五种I/O模型
    C++线程池的实现(二)
    C++ 简单 Hash容器的实现
    C++ TrieTree(字典树)容器的实现
    转载:C++线程池的一个实现
    C++用数组实现的静态队列
    C++ 类成员函数作为参数
    C++位操作符总结
    C++简单单例模式
    C++控制程序只运行一个实例
  • 原文地址:https://www.cnblogs.com/Dincat/p/13456216.html
Copyright © 2020-2023  润新知