如何在WPF中实现自定义画刷?

15

我在哪里可以找到关于画刷(Brushes)如何工作的足够信息,以实现自己的System.Windows.Media.Brush?我可以处理所有freezable(可冻结对象)相关内容,但是不是很清楚需要覆盖哪些部分才能使其正常工作。


是的,我并不是想使用预定义的画刷(brush),而是想扩展System.Windows.Media.Brush这个抽象类。这完全是为了我的自我提高,即使我不确定我可以制作什么样的画刷。我只是想学习一下画刷是如何工作的。

public AwesomeBrush : Brush
{

    protected override Freezable CreateInstanceCore()
    {
        return new AwesomeBrush();
    }

    ... // concrete brush stuff

}

提供更多关于您尝试做什么的信息可能会有所帮助。不确定创建画笔需要什么,但这里有一个很好的列表,其中包含许多内置于wpf中的画笔: http://msdn.microsoft.com/en-us/library/aa970904.aspx - Mark Synowiec
4个回答

10

我使用Reflector快速查看了现有的刷子。它们的实现似乎非常封闭,并且依赖于许多内部架构。虽然可能可以实现自己的刷子,但它似乎不是受支持的选项。甚至可能WPF控件紧密绑定在现有的刷子上,无法与自定义刷子一起使用。

最有可能实现类似自定义刷子的方法是使用DrawingBrush和一些复杂的绘图逻辑。您可以使用其他刷子来组成由复杂形状构成的绘画刷子,因此这应该可以实现所需的目标。

编辑后更新

由于这是用于教育,您最好下载Reflector并使用它来查看刷子的工作原理。它们不是自行实现的,并且由于它们依赖于某些程序员通常无法访问的internal类,因此很难这样做。

尽管有趣的是,Brush documentation有一个针对继承者的备注,以引导正确继承Brush的方法。

更多更新

在探索时,我在一篇中文博客中发现了一个非常好的实现类似功能的方法。那里的技巧是使用标记扩展,因此它看起来只是一个刷子。实际上,它基于其属性创建了新的图像刷子。他似乎得出了与我们相同的结论:有一些内部类阻止了简单的实现。


我也怀疑是这样。我无法想象有什么无法使用框架中提供的画笔实现完成的事情,只是好奇是否有乐趣的方式将其组件化,以便于重复使用。 - MojoFilter
添加了一个更新,可以实现类似的功能,而无需扩展Brush。 - Mikko Rantanen
1
我想知道刷子文档中对继承者的注释是否只针对微软内部,并且不应该出现在最终文档中。通过反编译工具查看,似乎Brush和GradientBrush中许多关键字段都被标记为internal,因此我们无法访问它们。 - Josh G
感谢您提供有用的答案,可惜这不是被采纳的答案。我希望有一种更灵活的方式来实现自定义的Brush派生类。使用DrawingBrush的想法对于某些情况是可以的,但我不知道如何在其他情况下使用它。例如,如果我想要实现一种包含某种伽马控制(即停止之间的插值不是严格线性的)的LinearGradientBrush变体,该怎么办? - Peter Duniho

5
要回答为什么您可能想要拥有自定义画笔的问题,请考虑一个复杂的几何形状,您想用与形状匹配的渐变来填充它,同时从形状的中心(或任意起始点)径向改变颜色。没有办法使用静态填充来做到这一点(这几乎消除了所有现有的画笔)。除非重复复制对象并嵌套它们(并计算外壳使其看起来正确),填充每个对象的不同颜色,否则唯一实际的方法是拥有一个可以查看正在填充的对象的画笔。这实际上在Forms中是可行的。但在WPF中似乎没有办法做到这一点,这很不幸。实际上,WPF的画笔既强大又受限制。它们显然是设计成某种概念模型,但我并不确定它是否有用于某些相当琐碎的视觉效果的概念模型,并且通常情况下,它会因为奇怪(文档不全的)参数而变得更加复杂,使得进行非常简单的事情变得相当复杂。

4
我尝试了各种可能的方法来解决自定义画笔的问题,从使用MarkupExtensions到混合使用TypeConverter属性,然后我想到了一个简单的方法:只需创建一个基于DependencyObject的包装类,创建一个类型为Brush的DependencyProperty,并实现定制化,然后绑定到Brush DependencyProperty即可。
之后,将自定义画笔放入资源中。
  <l:CustomBrush x:Key="CustomBrush" Brush="Magenta" />

然后绑定它:
  <Line X1="0" Y1="-5" X2="200" Y2="-5" 
        Stroke="{Binding Source={StaticResource CustomBrush}, Path=Brush}" 
        StrokeThickness="12"/>

我认为这种方式比MarkupExtension方案更好,因为你不能将其放入资源中,因此每次使用它时都必须重新定义自定义设置。

无论如何,这似乎是从任何Wpf对象继承的简单解决方案,并且借助Bindings的灵活性,您还可以绑定到包装的Wpf对象本身的成员。

以下是简单的类:

public class CustomBrush : DependencyObject
{
   public CustomBrush()
   {
      this.Brush = Brushes.Azure;      
   }

   #region Brush DependencyProperty

   [BindableAttribute(true)]
   public Brush Brush
   {
     get { return (Brush)GetValue(BrushProperty); }
     set { SetValue(BrushProperty, value); }
   }
   public static readonly DependencyProperty BrushProperty =
      DependencyProperty.Register(
         "Brush",
         typeof(Brush),
         typeof(CustomBrush),
         new UIPropertyMetadata(null));

   #endregion         
}

3

您无法通过继承System.Windows.Media.Brush类来创建自定义的WPF画刷,因为该类包含了一些内部的抽象成员。

如果您使用反编译工具查看System.Windows.Media.Brush类的源代码,您将会看到以下内部抽象方法:

internal abstract DUCE.ResourceHandle AddRefOnChannelCore(DUCE.Channel channel);
internal abstract DUCE.Channel GetChannelCore(int index);
internal abstract int GetChannelCountCore();
internal abstract DUCE.ResourceHandle GetHandleCore(DUCE.Channel channel);
internal abstract void ReleaseOnChannelCore(DUCE.Channel channel);

这些必须被覆盖,但由于它们是内部的,所以无法进行覆盖。

注意:编辑了我的答案,因为之前的答案是关于System.Drawing.Brush的,与此问题无关。特别感谢Mikko Rantanen的评论。


4
你可以继承Brush类 - 就像你说的那样,它是一个抽象类,必须被继承。 - Mikko Rantanen
1
该死的内部构造函数!我以前就被你们坑过! - MojoFilter
3
@M. Jahedbozorgan:您在这里陈述的是System.Drawing.Brush类的正确性... WPF版本可以从中派生。请参阅System.Windows.Media.Brush。 - Josh G
2
构造函数不是内部的。protected Brush(); / 声明类型: System.Windows.Media.Brush / 程序集: PresentationCore,Version=3.0.0.0 - 不过你接近问题了。刷子确实有内部抽象方法,只是没有构造函数。其中最糟糕的之一是 internal abstract DUCE.ResourceHandle AddRefOnChannelCore(DUCE.Channel channel);,其参数也是内部类型。这些必须被覆盖,但由于它们是内部的,所以无法覆盖。 - Mikko Rantanen
1
@Josh。那是相当错误的。没有任何阻止您从System.Drawing.Brush类派生。但是从WPF Brush派生是不可能的。这不是因为构造函数,而是因为其他抽象内部方法。 - Mikko Rantanen
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接