65.9K
CodeProject 正在变化。 阅读更多。
Home

Silverlight 2.0 的 DrawingBrush

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.38/5 (5投票s)

2008年5月13日

CPOL

3分钟阅读

viewsIcon

58656

downloadIcon

463

本文介绍如何为 Silverlight 构建缺失的 DrawingBrush 对象。此外,我们还将学习如何深度克隆 Silverlight 对象。

引言

在 WPF 中,我们有很多不同的画刷可用作 XAML 控件的背景和填充。不幸的是,在 Silverlight 中,只有 WPF 画刷的一个有限子集:一些颜色画刷、ImageBrushVideoBrush。Silverlight 中没有 DrawingBrush。而且,看起来微软没有计划在未来的 Silverlight 版本中添加这种类型的画刷。

我们真的需要 Silverlight 中的 DrawingBrush 吗?如果我们想用形状绘制控件或创建阴影背景,答案是“是的”。今天,我们将构建 Silverlight DrawingBrush 的基本版本。

背景

DrawingBrush 究竟是什么?它是页面中现有控件的深度副本,用于绘制不同的区域。因此,为了在 Silverlight 中构建自定义 DrawingBrush,我们首先需要学习如何创建 Silverlight 对象的深度克隆。

Silverlight 中的对象深度克隆

基本上,为了克隆 WPF 对象,我们应该使用XamlReader/XamlWriter。问题是,XamlWriter 在 Silverlight 中是一个protected 类,因此我们无法使用这种方法。那么,我们该怎么做呢?答案是使用反射来深度克隆对象。

首先,我们应该创建目标类的另一个实例。

public static T Clone<T>(this T source)  where T : DependencyObject      
{       
    Type t = source.GetType();
    T no = (T)Activator.CreateInstance(t);

然后,递归遍历所有DependencyPropertyDependencyObject,并创建它们的浅拷贝。

Type wt = t;
while (wt.BaseType != typeof(DependencyObject))
{       
    FieldInfo[] fi = wt.GetFields(BindingFlags.Static | BindingFlags.Public);
    for (int i = 0; i < fi.Length; i++)       
    {       
        {       
            DependencyProperty dp = fi[i].GetValue(source) as DependencyProperty;

当我们拥有所有DependencyProperty及其值时,我们可以在对象的新的实例中设置它们。

if (dp != null && fi[i].Name != "NameProperty")      
{       
    DependencyObject obj = source.GetValue(dp) as DependencyObject;
    if (obj != null)       
    {       
        object o = obj.Clone();
        no.SetValue(dp, o);
    }
    else
    {

有一些只读的DependencyProperty。在使用反射时,我们没有关于它们的信息,因此避免设置它们值的唯一方法是使用硬编码表达式。

{       
    if(fi[i].Name != "CountProperty" &&
        fi[i].Name != "GeometryTransformProperty" &&
        fi[i].Name != "ActualWidthProperty" &&       
        fi[i].Name != "ActualHeightProperty" &&
        fi[i].Name != "MaxWidthProperty" &&
        fi[i].Name != "MaxHeightProperty" &&
        fi[i].Name != "StyleProperty")
    {
        no.SetValue(dp, source.GetValue(dp));
    }
}

我们完成了DependencyPropertyDependencyObject,下一步是获取 CLR 属性。

PropertyInfo[] pis = t.GetProperties();      
for (int i = 0; i < pis.Length; i++)       
{ 
    if (pis[i].Name != "Name" &&
        pis[i].Name != "Parent" &&
        pis[i].CanRead && pis[i].CanWrite &&
        !pis[i].PropertyType.IsArray &&
        !pis[i].PropertyType.IsSubclassOf(typeof(DependencyObject)) &&
        pis[i].GetIndexParameters().Length == 0 &&
        pis[i].GetValue(source, null) != null &&
        pis[i].GetValue(source,null) == (object)default(int) &&
        pis[i].GetValue(source, null) == (object)default(double) &&
        pis[i].GetValue(source, null) == (object)default(float)
        )
          pis[i].SetValue(no, pis[i].GetValue(source, null), null);

这对于常规属性有效,但是数组呢?有一些方法可以设置数组值。

else if (pis[i].PropertyType.GetInterface("IList", true) != null)
{       
    int cnt = (int)pis[i].PropertyType.InvokeMember("get_Count",
               BindingFlags.InvokeMethod, null, pis[i].GetValue(source, null), null); 
    for (int c = 0; c < cnt; c++)
    {       
        object val = pis[i].PropertyType.InvokeMember("get_Item",
                     BindingFlags.InvokeMethod, null, pis[i].GetValue(source, null), 
                     new object[] { c }); 
        object nVal = val;      
        DependencyObject v = val as DependencyObject;       
        if(v != null)       
            nVal = v.Clone();
        pis[i].PropertyType.InvokeMember("Add", 
               BindingFlags.InvokeMethod, null, 
               pis[i].GetValue(no, null), new object[] { nVal }); 
    }       
}

到目前为止,我们已经完成了克隆;我们的下一步是使用克隆的对象来创建 DrawingBrush。

使用克隆的对象实现 DrawingBrush

我们所要做的就是将新对象添加到应用程序的可视化树和逻辑树中。为此,我们将创建一个名为DrawingBrush的新对象,它具有一些属性:PatternTile(继承自TileBrush)和一个将容纳我们克隆对象的Panel。我使用WrapPanel(Silverlight 中也缺少)来容纳新对象并将它们平铺在画刷上。你可以从我的博客获取源代码和关于如何实现它的描述

void SetPatternImpl(double width, double height)
{       
    Pattern = new WrapPanel();
    Pattern.Width = width;
    Pattern.Height = height;
    Pattern.HorizontalAlignment = HorizontalAlignment.Stretch;
    Pattern.VerticalAlignment = VerticalAlignment.Stretch;
    double xObj = (1 / this.Viewport.Width);
    double yObj = (1 / this.Viewport.Height);
    for (int i = 0; i < Math.Ceiling(xObj*yObj); i++)
    {       
        Shape ns = this.Drawing.Clone();
        ns.Stretch = this.TileMode == TileMode.None?Stretch.None:Stretch.Fill;
        ns.Width = Pattern.Width / xObj;
        ns.Height = Pattern.Height / yObj;
        ScaleTransform st = new ScaleTransform();
        st.ScaleX = this.TileMode == TileMode.FlipX | 
                    this.TileMode == TileMode.FlipXY ? -1 : 1;
        st.ScaleY = this.TileMode == TileMode.FlipY | 
                    this.TileMode == TileMode.FlipXY ? -1 : 1;
        ns.RenderTransform = st;       
        Pattern.Children.Add(ns);       
    }
}

我们完成了;现在我们唯一要做的就是在我们的 XAML 代码中使用它。我尽量使语法尽可能简单。

<Grid x:Name="LayoutRoot" Width="300" Height="300">      
    <Grid.Background>       
        <l:DrawingBrush Viewport="0,0,0.25,0.25" TileMode="Tile">
            <l:DrawingBrush.Drawing>
                <Path Stroke="Black" Fill="Red" StrokeThickness="3">
                    <Path.Data>
                        <GeometryGroup>
                            <EllipseGeometry RadiusX="20" RadiusY="45" Center="50,50" />
                            <EllipseGeometry RadiusX="45" RadiusY="20" Center="50,50" />
                        </GeometryGroup>
                    </Path.Data>
                </Path>
            </l:DrawingBrush.Drawing>
        </l:DrawingBrush>
    </Grid.Background>
    <Canvas Width="150" Height="150" x:Name="canvas">
        <Canvas.Background>
            <l:DrawingBrush Viewport="0,0,0.1,0.1" TileMode="FlipX">
                <l:DrawingBrush.Drawing>
                    <Polygon Fill="Blue" Points="0,0 1,1 1,0 0,1"/>
                </l:DrawingBrush.Drawing>
            </l:DrawingBrush>
        </Canvas.Background>
        <TextBox Foreground="Yellow" Background="#AA000000" 
                 Text="Hello, World!" Height="30"/>
    </Canvas>
</Grid>

关注点

此 DrawingBrush 实现有一些限制。

  • 它仅适用于Panel
  • 它不进行布局(因此例如,你不能将其与StackPanel一起使用)。
  • 你应该命名托管控件(我已经解释了原因)。
  • 对于DrawingBrush内的绘图,你只能使用Shape派生类(例如,LinePolygonEllipsePath等)。

有关此控件的更多信息和详细说明,请访问我的博客 此外,欢迎你增强此控件并贡献它,以便为其他开发人员的需求提供高质量的解决方案。

© . All rights reserved.