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

堆叠几何画笔工厂

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (63投票s)

2009年2月10日

CPOL

13分钟阅读

viewsIcon

83151

downloadIcon

2599

一款具有插件架构和自定义XAML的几何视觉画笔生成器。

WPF text iam

引言

本文介绍了如何使用形状创建效果,此外,还介绍了如何为WPF构建插件架构以及通过属性控制XAML的序列化。它最初是为了创建一个发光的霓虹星作为图标而开始的,最终却成为一个多功能的视觉画笔工厂。很快就显而易见,该解决方案可以得到扩展。我决定创建一个具有以下特征的图形效果创建应用程序:

  • 能够以混合搭配的方式使用多种不同的几何图形源(Geometry Sources)和效果(Effect Factories)。
  • 几何源和效果工厂应该能够在以后添加,而无需我修改现有代码。
  • 这不仅仅是为了生成漂亮的图像。它需要生成相当简洁的XAML,我可以将其粘贴到另一个项目中并获得相同的图像。

大多数时候,我们在Illustrator或Photoshop中通过复制图像并将其堆叠在彼此之上来创建效果。然后,每个图层都会以某种方式进行转换。通过这种方式可以相当容易地创建一些引人注目的效果。以下是生成堆叠形状的基本计划:

  • 从Geometry Source类创建几何图形。
  • 从Effect Factory创建效果集合。
  • 使用几何图形创建Path集合。
  • 将效果应用于Path。
  • 按顺序将Path作为Canvas的Children添加。
  • 从此Canvas创建Visual Brush,我们就完成了。

背景

访问TutVid.com等互联网图形教程网站并观看一些视频可能会很有用,也可能很有趣。特别是,Comic Effect Factory受到他们一个视频的启发。如果您在YouTube上搜索“Illustrator tutorial”,您还会找到很多很棒的信息。

值得一提的是,本文最初是为了改进PolyStars的外观而产生的。如果您想更详细地了解polystar几何图形,可以在那里找到。

创建几何图形

效果将通过制作形状的相同副本然后修改它们来创建。以下方法将复制大多数形状,即使它被拒绝了,也值得一看。唯一的限制是不要直接暴露泛型类型,因为这会导致XamlWriter失败。

private ObservableCollection<shape> CopyShapes(Shape shape, int copyCount)
{ 
  ObservableCollection<shape> shapeStack = new ObservableCollection<shape>();
  string pString = XamlWriter.Save(shape);
  for (int i = 0; i < copyCount; i++)
  {
      StringReader sr = new StringReader(pString);
      XmlTextReader xmlr = new XmlTextReader(sr);
      Shape cShape = (Shape)XamlReader.Load(xmlr);
      shapeStack.Add(cShape);
  }
  return shapeStack;
}

最初,此解决方案中使用了与此方法非常类似的方法。它有效,但不令人满意。在这种情况下,使用序列化似乎有点过头了。

在框架提供的继承自Shape类的类中,Path类值得一提,因为它允许我们插入其几何图形。幸运的是,多个Path实例共享相同的几何图形没有问题。问题在于我们如何创建几何图形,其中有三种主要情况:

  • 我们可以控制该类,因此可以公开受保护的几何图形。
  • 几何图形的源是形状。
  • 几何图形的源是某些文本。

IGeometrySource

创建IGeometrySource接口是为了提供ExternalGeometryShape实例的Geometry源。

[CustomXaml]
public interface IGeometrySource:IFactory 
{
   //Main Method analogus to CacheDefiningGeometry
   Geometry CreateGeometry();
   // Supplies geometry Analogus to GetDefiningGeometry
   Geometry Geometry { get; set; }
}

CreateGeometry方法足以确保实现IGeometrySource接口的类可以用于提供Geometry对象,而Geometry属性由StackedGeometry用于缓存。我有权选择修改类以支持此接口,就像我为PolyStar所做的那样,或者创建一个实现该接口的包装类,就像为TextBlockShape所做的那样。IFactory接口用于插件系统以及更新显示。

Shapes

所有继承自Shape的类都实现了受保护的DefiningGeometry方法。我们的第一反应可能是继承自Shape然后公开该方法。不幸的是,继承自Shape的所有框架类都是sealed的。如果我们真的想,无论它们如何暴露,我们都可以始终通过反射来调用任何类。

private Geometry GetHiddenDefiningGeometry(Shape shape)
{
    Type shptype = shape.GetType();
     MethodInfo mi = shapeType.GetMethod("CacheDefiningGeometry",
         BindingFlags.NonPublic | BindingFlags.Instance);
    PropertyInfo pi = shapeType.GetProperty("DefiningGeometry",
         BindingFlags.NonPublic | BindingFlags.Instance);
     mi.Invoke(Shape, null);
    return (Geometry)pi.GetValue(mShape, null);
}

让我们看一下Polygon类的方法(感谢Reflector)。

WPF text iam

几乎所有工作都在CacheDefiningGeometry方法中完成。如果所讨论的形状嵌入在XAML中,那么它很有可能在我们尝试调用其几何图形之前已经被渲染了,但先调用CacheDefiningGeometry方法更安全。

private void btValidate_Click(object sender, RoutedEventArgs e)
{
  ValidationButton but = (ValidationButton)sender;
  ShapeSource ss = (ShapeSource)but.DataObject;
  TextBox tb = (TextBox)but.ObjectToValidate;
  try
  {
     StringReader sr = new StringReader(tb.Text);
     XmlReader xmlReader = XmlReader.Create(sr);
     Shape shp = XamlReader.Load(xmlReader) as Shape;
     if (shp != null)
     {
         ss.Shape = shp;
     }
  }
  catch { }
}

这是用于获取用户输入并从中创建形状的代码。值得注意的是,如果我们在不调用CacheDefiningGeometry方法的情况下调用DefiningGeometry属性,它将始终为null。这种错误很容易犯。上面的代码可以工作;但是,它的速度不是很快,因为GetPropertyGetMethod方法很慢。我们通常听说反射的用途是允许使用在编译时未知的程序集和成员,但在这种情况下,我们已经知道要调用的成员。它们只是碰巧不是public,所以让我们用statics来完成所有工作。考虑这段代码:

[GeometrySource]
public class ShapeSource:IGeometrySource 
{
   // ...
   private static PropertyInfo sPolygonDefiningGeometry;
   //...
   static ShapeSource()
   {
       //...
       Type  shptype = typeof(Polygon);
       sPolygonDefiningGeometry = shptype.GetProperty("DefiningGeometry",
            BindingFlags.NonPublic | BindingFlags.Instance);
       //...
   }
  
   public   Geometry CreateGeometry()
   {
       Type shapeType = mShape.GetType();
       if (shapeType ==typeof(Polygon))
       {
           return (Geometry)sPolygonDefiningGeometry.GetValue(mShape, null);
       }
       else if  //...
       else
       {
           Type shptype = mShape.GetType();
           PropertyInfo pi = shptype.GetProperty("DefiningGeometry",
               BindingFlags.NonPublic | BindingFlags.Instance);
           return (Geometry)pi.GetValue(mShape, null);
       }
   }
   //..
}

这段代码应该以与普通方法相当的速度运行,因为慢方法只需要调用一次。此时,我感到有义务说明方法经常被标记为非public是有原因的。通常,不应忽视作者的意图,但有时我们就是必须去做。

PolyStar

如果您可以控制形状的源代码,我们只需要公开我们创建的定义几何图形。

public Geometry CreateGeometry()
{
    return DefiningGeometry;
}

正如您所见,这几乎和它能得到的一样简单。StackedGeometry类负责缓存,因此它不是很大的损失,因为它不在PolyStar中。

文本

现在,除了它的含义之外,一段文本就是一组曲线。幸运的是,框架使我们可以轻松访问底层几何图形。文本信息存储在TextBlock中以方便使用。

public System.Windows.Media.Geometry CreateGeometry()
{
   ICollection<typeface> faces = mTextBlock.FontFamily.GetTypefaces();
    FormattedText fText 
            = new FormattedText(mTextBlock.Text, CultureInfo.CurrentCulture, 
              FlowDirection.LeftToRight, faces.First(),
              mTextBlock.FontSize, Brushes.Black);
    mTextGeometry = fText.BuildGeometry(new Point(0, 0));
    return mTextGeometry;
}

我在这里选择了简单的。这个应用程序远非制作出严肃的文本效果。PhotoshopRoadmap上有一些非常好的效果。Chrome和Radioactive文本效果很棒,但这些需要自定义着色器效果和一些花哨的几何计算。

现在,我们可能会注意到这里存在一个潜在的问题。假设您将此样式化文本的一部分用作应用程序品牌推广的一部分。毕竟,这就是它的目的。我们不希望每个客户端都必须安装适当的字体。这就是为什么IFactory接口(IGeometrySource实现)具有IsFrozen属性的原因。当StackedGeometry类使用Geometry Source时,它会检查是否应使用CreateGeometry

if (!GeometrySource.IsFrozen | GeometrySource.DesignMode)
{
   mGeometry = GeometrySource.CreateGeometry();
   GeometrySource.Geometry = mGeometry;
}
else
{
   mGeometry = GeometrySource.Geometry;
}

我们允许使用存储在XAML中的几何图形。请注意,当表示为XAML时,此几何图形可能会很大。

IEffectFactory

既然我们有了几何图形,我们需要对它们做些事情。IEffectFactory接口与IGeometrySource接口非常相似。

[CustomXaml]
public interface IEffectFactory:IFactory 
{
    ShapeVisualPropsCollection CreateLayerCollection();
    ShapeVisualPropsCollection ShapeVisualProps { get; set; }
}

Effect Factories通过CreateLayerCollection生成视觉属性集合,并通过ShapeVisualProps属性进行缓存。创建ShapeVisualPropsCollection类是为了让XamlWriter不会因为泛型而崩溃。

public class ShapeVisualPropsCollection:ObservableCollection<shapevisualprops>{}

Shape视觉ShapeVisualProps类包含Shape类中的所有视觉属性。由于我希望该类像Shape一样运行,所以我只是从Reflector中复制了代码并做了一些小的调整。我强烈推荐这样做。如果您想模仿框架中的某些内容,值得考虑。ShapeVisualProps有一个重要的方法Apply,它将其属性应用于所讨论的形状。

public void Apply(Shape shape)
{
    shape.Fill = Fill;
    shape.StrokeDashArray = StrokeDashArray;
    shape.StrokeDashCap = StrokeDashCap;
    shape.StrokeDashOffset = StrokeDashOffset;
    shape.StrokeEndLineCap = StrokeEndLineCap;
    shape.StrokeLineJoin = StrokeLineJoin;
    shape.StrokeMiterLimit = StrokeMiterLimit;
    shape.Stroke = Stroke;
    shape.StrokeStartLineCap = StrokeStartLineCap;
    shape.StrokeThickness = StrokeThickness;

   shape.Effect = mEffect;
   shape.RenderTransform = mTransformGroup;
}

到目前为止,此解决方案中已创建了两个工厂:NeonFactoryComicFactory

霓虹灯工厂

霓虹灯由充有氖气或其他惰性气体的玻璃管组成。光线有三个主要区域:内部发光的辉光柱,没有气体的玻璃边缘,以及周围的发光。

public ShapeVisualPropsCollection CreateLayerCollection()
{
    ShapeVisualPropsCollection svps = new ShapeVisualPropsCollection();
    ShapeVisualProps svp = new ShapeVisualProps();
    double currentSaturation = StartingSaturation;
    double currentBrightness = StartingBrightness;

    //Glow
    svp.Stroke = new SolidColorBrush(MediaColor(
        DevCorpColor.ColorSpaceHelper.HSBtoColor(Hue,
        currentSaturation, currentBrightness)));
    svp.StrokeThickness = StartingThickness * 4 * GlowMultiplier;
    svp.StrokeLineJoin = PenLineJoin.Round;
    System.Windows.Media.Effects.BlurEffect blur =
        new System.Windows.Media.Effects.BlurEffect();
    blur.Radius = 12;
    svp.Effect=blur ;
    svps.Add(svp);

    //glass edge
    svp = new ShapeVisualProps();
    currentSaturation *= SaurationMultiplier[0];
    currentBrightness *= BrightnessMultiplier[0];
    svp.Stroke = new SolidColorBrush(MediaColor(
        DevCorpColor.ColorSpaceHelper.HSBtoColor(Hue, currentSaturation,
        currentBrightness)));
    svp.StrokeThickness = StartingThickness * 2;
    svp.StrokeLineJoin = PenLineJoin.Round;
    svps.Add(svp);

     // Gas  Column
    svp = new ShapeVisualProps(); 
    currentSaturation *= SaurationMultiplier[1];
    currentBrightness *= BrightnessMultiplier[1];
    svp.Stroke = new SolidColorBrush(MediaColor(
         DevCorpColor.ColorSpaceHelper.HSBtoColor(Hue, currentSaturation,
         currentBrightness)));
    svp.StrokeThickness = StartingThickness ;
    svp.StrokeLineJoin = PenLineJoin.Round;
    svps.Add(svp);

    return svps;
}

气体层最亮,然后是玻璃层,最后是漫射的光辉。特别感谢Guillaume Leparmentier的项目:在.NET中操作颜色 - 第1部分。他的一些颜色处理代码为我节省了大量时间。霓虹灯的饱和度较低,并且当我们从光柱到辉光时,色相和饱和度保持不变,但亮度会变化。只需很少的努力,我们就可以制作出看起来像这样的东西。

WPF text iam

漫画工厂

我在网上发现了这种风格,觉得它很有趣。概念很简单。

  • 三色渐变
  • 渐变周围有深色边框
  • 底部渐变色的附加边框
  • 中间渐变色的最终边框

创建此效果的代码非常简单。

public ShapeVisualPropsCollection CreateLayerCollection()
{
    ShapeVisualPropsCollection svps = new ShapeVisualPropsCollection();
    ShapeVisualProps svp;
    //Third Outline
    svp = new ShapeVisualProps();
    svp.Stroke = new SolidColorBrush(MiddleColor);
    svp.StrokeThickness = 11;
    svps.Add(svp);
    //Second Outline
    svp = new ShapeVisualProps();
    svp.Stroke = new SolidColorBrush(BottomColor);
    svp.StrokeThickness = 7;
    svps.Add(svp);
    //First Outline
    svp = new ShapeVisualProps();
    svp.Stroke = new SolidColorBrush(OutlineColor);
    svp.StrokeThickness = 3 ;
    svps.Add(svp);
    //Gradient Layer
    svp = new ShapeVisualProps();
    LinearGradientBrush lgb = new LinearGradientBrush();
    lgb.GradientStops.Add(new GradientStop(BottomColor, 0));
    lgb.GradientStops.Add(new GradientStop(MiddleColor, .5));
    lgb.GradientStops.Add(new GradientStop(TopColor, 1));
    lgb.StartPoint = new System.Windows.Point(0, 1);
    lgb.EndPoint = new System.Windows.Point(0, 0);
    svp.Fill = lgb;
    svps.Add(svp);

    return svps;
}

尽管它最初是为文本设计的,但我对它在形状上的表现感到惊讶。

WPF text iam

特别感谢Microsoft提供的ColorPicker Custom Control Sample,该控件用于为漫画工厂选择颜色。

整合它们

StackedGeometry类管理IEffectFactoryIGeometrySource。此类的主要方法是PrepareShapeStack方法。

private void PrepareShapeStack()
{
    if (GeometrySource != null )  
    {
        if (!GeometrySource.IsFrozen | GeometrySource.DesignMode)
        {
            mGeometry = GeometrySource.CreateGeometry();
            GeometrySource.Geometry = mGeometry;
        }
        else
        {
             mGeometry = GeometrySource.Geometry;
        }
    }

    if (EffectFactory != null)  
    {
        if (!EffectFactory.IsFrozen | EffectFactory.DesignMode)
        {
            mShapeVisualPropsCollection =
                EffectFactory.CreateLayerCollection();
            EffectFactory.ShapeVisualProps = mShapeVisualPropsCollection;
        }
        else
        {
            mShapeVisualPropsCollection = EffectFactory.ShapeVisualProps;
        }
       
    }
    if (mGeometry != null && mShapeVisualPropsCollection != null)
    {
        foreach (ShapeVisualProps layer in mShapeVisualPropsCollection)
        {
            Path ext = new Path() { Data = mGeometry };
            layer.Apply(ext);
            this.Children.Add(ext);
        }
    }
}

这就是外观组合的地方。DesignMode属性由应用程序使用,以便我们可以使应用程序冻结并生成属性以生成所需的XAML。StackedGeometryBrushFactory公开一个VisualBrush,其视觉效果是StackedGeometry

插件架构

该解决方案包含八个程序集:StackedGeometryDesignStackedGeometryGeometrySourcesEffectFactoriesPolygonImageLibPointTransformationsColorPickerDevcorpColor。依赖关系图可能很有帮助。(箭头指向依赖方向。)

WPF text iam

除支持库外,所有内容都依赖于StackedGeometry,其中包含所有使用的接口以及自定义属性。StackedGeometryDesignStackedGeometryAssemblies对其他程序集一无所知。它们依赖于StackedGeometry中定义的属性和接口。定义了以下属性:

  • ContainsEffectFactoriesAttribute - 用于将程序集标记为包含IEffectFactory类型。
  • ContainsGeometrySourcesAttribute - 用于将程序集标记为包含IGeometrySource类型。
  • EffectFactoryAttribute - 用于将类标记为IEffectFactory
  • GeometrySourceAttribute - 用于将类标记为IGeometrySource
  • CustomXamlAttribute - 用于将类或接口标记为具有自定义XAML。
  • XamlIgnoreAttribute - 用于将属性标记为在XAML目的上始终被忽略或有条件地被忽略。

加载中

StackedGeometryDesign应用程序搜索其当前目录和子目录中的类库,然后检查它们是否具有ContainsEffectFactoriesAttributeContainsGeometrySourcesAttribute属性。如果是,则检查类型是否具有EffectFactoryAttributeGeometrySourceAttribute属性。然后实例化找到的类。代码编写完毕后,又以更优化的方式重写。最初,代码看起来像这样:

string[] files = Directory.GetFiles(currentAssemblyDirectoryName, 
                 "*.dll", SearchOption.AllDirectories);
foreach (string str in files)
{
    Assembly asm = Assembly.LoadFile(str);
    ContainsGeometrySourcesAttribute containsGeom = 
      (ContainsGeometrySourcesAttribute)Attribute.GetCustomAttribute(asm,
       typeof(ContainsGeometrySourcesAttribute));

    f (containsGeom != null) //Attribute null if not present
    {
        foreach (Type t in asm.GetTypes())
        {
            GeometrySourceAttribute gsa = (GeometrySourceAttribute)
              Attribute.GetCustomAttribute(t, typeof(GeometrySourceAttribute));
            if (gsa != null)
            //Attribute null if not present
            {
                IGeometrySource gs = 
                  (IGeometrySource)Activator.CreateInstance(t, null);
                geometrySources.Add(gs);
            }
        }
    }

此方法有效,但当程序集同时具有这两个属性时效率不高。为这种情况创建了一个更通用的解决方案,由AttributeReflectionItemAttributeReflector组成。AttributeReflectionItem仅包含属性搜索的三个数据项。

class AttributeReflectionItem
{
    public Type AssemblyAttribute { get; set; }
    public Type ClassAttribute { get; set; }
    public IList List { get; set; }
}

AttributeReflector逐个遍历程序集并填充相应的列表。

public void Reflect(Assembly assembly)
{
    List<attributereflectionitem> assemblyReflectionItems = 
                               new List<attributereflectionitem>();
    foreach (AttributeReflectionItem ri in mReflectionItems)
    {
        if (Attribute.GetCustomAttribute(assembly,
            ri.AssemblyAttribute) != null)
        {
            assemblyReflectionItems.Add(ri);
        }
    }
    if (mReflectionItems.Count > 0)
    {
        foreach (Type t in assembly.GetTypes()) // only Called once
        {
            foreach (AttributeReflectionItem ri in mReflectionItems)
            {
                if (Attribute.GetCustomAttribute(t, ri.ClassAttribute) != null)
                {
                    ri.List.Add(Activator.CreateInstance(t, null));
                }
            }
        }
    }
}

由于反射调用被限制在最低限度,因此此方法将效率更高,但代价是更高的抽象。此外,添加其他搜索属性也变得微不足道。

ar.ReflectionItems.Add(new AttributeReflectionItem()
{
    AssemblyAttribute = typeof(ContainsGeometrySourcesAttribute),
    ClassAttribute = typeof(GeometrySourceAttribute),
    List = geometrySources
});

交互

已创建的类通过它们的接口或通过UI进行交互。WPF提供了多种方式使类在用户界面中可见。我们可以使用DataTemplate或制作自定义控件。我选择了一种略有不同的方式。类具有作为属性的DataTemplate

public DataTemplate DataTemplate
{
    get
    { 
          ResourceDictionary rd = new ResourceDictionary();
          rd.Source = new 
            Uri("EffectFactories;component/NeonResources.xaml", 
                UriKind.Relative);
          DataTemplate dt = (DataTemplate)rd["NeonFactoryTemplate"];
          return dt;
    }
}

这些DataTemplate从类所在的程序集中的资源字典加载。然后,可以将类放置在ContentControl中,并将ContentTemplate设置为IEffectFactoryIGeometrySourceDataTemplate属性。

<ContentControl Name="cEffects" VerticalAlignment="Top" 
                Content="{Binding Source ={
                          StaticResource StackedGeometry} ,
                          Path=EffectFactory}"
                          ContentTemplate="{Binding Source ={
                          StaticResource StackedGeometry} , 
                          Path=EffectFactory.DataTemplate }"/>

这样,托管应用程序就不需要了解包含类和资源字典的程序集的任何信息。在开发过程中,这些DataTemplate可以保留在主程序集中以便于编辑,然后稍后移动。

XAML文档

StackedGeometryDesign应用程序旨在实用。我们需要能够轻松地将这些StackedGeometryBrushFactory对象放入他们的应用程序中。为此,该应用程序生成StackedGeometryBrushFactory的XAML。通过调用可以非常容易地获得XAML。

string xamlString = XamlWriter.Save(mUIElement);

但是,生成的XAML过于冗长,以至于无法使用。由于使用属性控制序列化似乎是Windows的传统,所以我决定走这条路。XamlGenerator类完成了大部分工作。基本策略如下:

  • 使用XamlWriter创建XAML。
  • 递归地遍历对象层次结构,查找具有CustomXamlAttribute的对象。
  • 在这些对象中,检查XamlIgnoreAttribute,并可能从XAML中删除该对象的序列化。

缓存

缓存对于提高基于反射的代码的性能非常有效。检查对象哪些属性可能需要从XAML中删除。

Dictionary<type,> mSpecialInfoCache = new Dictionary<type,>(); //For Caching
private List<propertyinfo> SpecialXamlInfos(Type t)
{
    //Caching speed improvement ~1000 times 
    if (mSpecialInfoCache.ContainsKey(t))
    {
        return mSpecialInfoCache[t];
    }
    else
    {
        PropertyInfo[] infos =t.GetProperties(BindingFlags.Public | 
                                              BindingFlags.Instance);
        List<propertyinfo> specialInfos = new List<propertyinfo>();
        foreach (PropertyInfo pi in infos)
        {
            if (HasCustomXaml(pi.PropertyType) | GetXamlIgnoreStatus(pi)!=
                eXamlIgnoreStatus.noAttribute)
            {
                specialInfos.Add(pi);
            }
        }
        mSpecialInfoCache.Add(t, specialInfos);
        return specialInfos;
    }
}

我的机器上,第一次运行后,此代码的运行速度大约是原来的千倍。部分原因是控件有70多个依赖项属性,但即便如此,这也是一个惊人的差异。类似以下的代码仅加速了约三十倍。

Dictionary<type,> mHasCustomXaml = new Dictionary<type,>();//For Caching
private bool HasCustomXaml(Type t )
{
    //Caching speed improvement ~30 times 
    if (mHasCustomXaml.ContainsKey(t))
    {
        return mHasCustomXaml[t];
    }
    else
    {
    CustomXamlAttribute cxa = (
        CustomXamlAttribute)Attribute.GetCustomAttribute(t,
        typeof(CustomXamlAttribute));
    mHasCustomXaml.Add(t, cxa != null);
    return (cxa != null);
    }
}

TypePropertyInfo类是全局唯一的(至少在AppDomain内),因此使用它们作为字典键应该没有困难。

生成代码

XamlGeneratorGenerateXaml方法完成了XAML创建的所有繁重工作。关于XamlWriter,有几件事需要牢记。首先,它会将所有可能需要的命名空间放在第一个元素中。这没什么错,但这意味着表示对象子元素的字符串默认情况下与XamlWriter从该元素生成的字符串不同。其次,元素可以以三种不同的方式表示。前两种是XML的标准方式。属性可以表示为属性或子元素。这些可以通过正则表达式轻松提取。但是,对象可以具有**ContentPropertyAttribute**。这使得它们看起来没有标签,因此更难提取。我们需要为对象创建XAML,删除命名空间,然后找到它以进行删除或自定义。最后,在此应用程序中,只有当IsFrozen属性为true时,才需要将GeometryShapeVisualProps写入XAML。如果您有兴趣,请查看代码。该方法很长,远不漂亮,但同样,框架在此情况下使用的一些代码也很有挑战性,例如System.Windows.Markup.Primitives.MarkupWriter.WriteItem

结论

感谢您耐心阅读所有这些内容。最初只是一个简单的霓虹灯星,最终发展成完全不同于最初设想的东西。使其易于扩展成为当务之急,因为我打算在不久的将来创建一些其他几何图形,并且不希望担心集成问题。特别是,PolyArc和一些分形几何图形即将到来。一旦我找到一种制作足够令人愉悦的浅银色效果的方法,我将看看有多少可以转移到Silverlight,敬请关注。

更新

  • 2010年7月15日 - 对演示应用程序进行了一些优化。
© . All rights reserved.