Silverlight 2.0 的 DrawingBrush






4.38/5 (5投票s)
本文介绍如何为 Silverlight 构建缺失的 DrawingBrush 对象。此外,我们还将学习如何深度克隆 Silverlight 对象。
引言
在 WPF 中,我们有很多不同的画刷可用作 XAML 控件的背景和填充。不幸的是,在 Silverlight 中,只有 WPF 画刷的一个有限子集:一些颜色画刷、ImageBrush
和 VideoBrush
。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);
然后,递归遍历所有DependencyProperty
和DependencyObject
,并创建它们的浅拷贝。
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));
}
}
我们完成了DependencyProperty
和DependencyObject
,下一步是获取 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
的新对象,它具有一些属性:Pattern
、Tile
(继承自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
派生类(例如,Line
、Polygon
、Ellipse
、Path
等)。
有关此控件的更多信息和详细说明,请访问我的博客。 此外,欢迎你增强此控件并贡献它,以便为其他开发人员的需求提供高质量的解决方案。