WPF通常用Xaml格式创建对象树。您还可以使用XamlWriter类进行反方向操作——将对象树写入Xaml。

对于XamlWriter来说,将对象转换成良好的Xaml表示形式通常很容易。但是,您不能总是通过查看对象的属性就知道如何将对象写入Xaml。当你创建一个新类时,你需要做一些事情,使你的类在XamlWriter中工作得更好。

XamlWriter 是什么?

XamlWriter是一个允许你从对象创建Xaml的类。对象的类型可以不是WPF特有的,您可以对任何托管类型使用XamlWriter和XamlReader,例如在c#、VB中创建的那些类型。

假设我们用这样的代码片段创建了一个Button对象:

Button button = new Button();

button.Width = 100.0;

button.Background = Brushes.Red;

想把它写成Xaml,可以用这样的代码:

string xamlString = XamlWriter.Save( button );

其结果xamlString 的内容是这样的:

<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="100" Background="Red"/>

长的xmlns Uri用于将标记映射到程序集。这很有用,但是有点分散注意力,所以我先不讲了。因此它只是:<Button Width="100" Background="Red"/>

非常简单——将对象的类型写成标记,将其属性写成特性。

本例中的红色是一个简单的SolidColorBrush,它很容易表示为一个特性。但是使用LinearGradientBrush要困难得多——这需要更多的XML。例如:

<Button Width="100">

  <Button.Background>

      <LinearGradientBrush>

          <GradientStop …="" />

        ...

      </LinearGradientBrush>

    </Button.Background>

</Button>

在本例中,我们使用Xaml的属性元素语法,Background标记表明该标记的内容实际上是Background属性的一个值,在本例中是一个LinearGradientBrush。

这简单地展示了XamlWriter—Save方法获取对象并将它们转换为Xaml。基本的算法是将对象的类型名写入标记,然后将对象的属性值写入属性(如果它们是简单的属性)或属性元素标记下的嵌套标记。

顺便说一下,如果您已经对Xaml有过很多了解,您可能已经知道有时属性元素标记会被省略。例如,对于Grid,子属性是它的内容属性,因此:

<Grid>

    <Grid.Children>

        <Button />

        <Button />

    </Grid.Children>

</Grid>

可以简写成:

<Grid>

     <Button />

     <Button />

</Grid>

这是基本的Xaml语法(基于[ContentProperty]属性),但这里的重点是XamlWriter理解它,并在可能的情况下编写更简单的语法。

XamlWriter的限制

不是所有的类都可以用Xaml表示。例如,Xaml不能(在大多数情况下)表示类型,除非它们有默认构造函数,因此XamlWriter也不会编写这样的类型。另一个例子是,Xaml不能直接表示对象的图形。

其他限制:

1,字段。只写属性,不写字段。

2,只读属性(有setter而没有getter的属性)。因为不可以从Xaml设置只读属性了。但是,可以使用下面描述的[DesignerSerializationVisibility]属性覆盖它。

3,内啊/私有属性。只有public属性才可以写。

4,内部/私有类。与前面类似,只编写pulice类。实际上,尝试编写非公共类会引发异常。

5,事件监听器:例如,button.Click+=OnClick(),您不会得到<Button Click="OnClick"/>,单击处理程序将被删除。

最后,如果您使用XamlReader读取Xaml,然后使用XamlWriter编写它,您将不会得到完全相同的XML。另外,一些标记扩展(如{x:Static})在加载时由XamlReader解析,标记扩展本身被丢弃,因此XamlWriter无法重新生成它。

什么样的properties可以写成Xaml

对象被写为标签,属性被写为属性或属性元素标签。在大多数情况下,你不需要做任何事情,就可以写出正确的属性。但也许不是,在这种情况下,您可以在类定义中做一些工作来获得更多的控制权。有几个不同的选择…

1,不写缺省值。一般来说,如果属性是默认值,那么将它写出来是多余的。类型作者(即定义类或结构的人)可以使用[DefalutValue]属性让XamlWriter知道属性的默认值。例如,在下面的例子中,如果MyClass对象的值为0,那么MyProperty就不会被写出来:

class MyClass

{

[DefaultValue(0)]

public int MyProperty

{

get … set …

}

}

但是,如果属性由DependencyProperty支持,则此规则略有不同。在这种情况下,属性只有在实际设置时才会被写入。例如,下面的按钮不是在按钮本身上获取其背景属性设置,而是从按钮的样式中获取:

<Window>

<Window.Resources>

<Style TargetType="Button">

<Setter Property="Background" Value="Red" />

</Style>

</Window.Resources>

<Button Name="button1">Click</Button>

</Window>

在这个例子中,代码button1.Background会得到一个红色的画笔,但是如果您使用XamlWriter编写它,您只会得到:

<Button Name="button1">Click</Button>

2,写只读属性。通常没有必要将只读属性的值写入Xaml,因为它不可能从Xaml加载回来,因为Xaml不能设置只读属性。但是,在一种情况下,Xaml可以设置为只读属性,这是集合类型属性。在这种情况下,它不是设置属性本身,而是添加到集合中。例如,Grid.Children是只读的,但是您仍然可以使用:

<Grid>

<Grid.Children>

<Button …/>

<Button …/>

</Grid.Children>

</Grid>

只读集合属性通常可以写入OK,但XamlWriter默认情况下不写入它们。要覆盖它,您可以使用[DesignerSerializationVisibility]属性在类定义中覆盖它:

class Grid

{

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]

public UIElementCollection Children

{

get { … }

}

...

}

3,不写可读写属性。相反的情况是,如果您有一个读/写属性,并且看起来可以写入Xaml,但是您不希望它是这样的。例如,在下面的类中,DoorChanges属性是一个运行时计数器,不需要持久化。这里我们使用[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]来阻止它被写入:

class Car

{

int _doorChanges = 0;

[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]

public int DoorChanges

{

get { return _doorChanges; }

set { _doorChanges = value; }

}

...

}

4,在运行时决定是否编写。DesignerSerializationVisibility属性是一种方法,用于声明性地确定某个属性是否由XamlWriter编写。XamlWriter还可以设定为在调用期间做出运行时决策。为此,需要在属性中添加“ShouldSerializeProperty”方法,其中“property”是属性的名称。例如:

class MyClass

{

public int Foo

{

get { return _foo; }

set { _foo = value; }

}

public bool ShouldSerializeFoo()

{

if( …  )

return false;

else

return true;

}

...

}

如果属性是由DependencyProperty支持的,那么在这个运行时检查中还有一个额外的选择。在这种情况下,您仍然可以使用这个ShouldSerializeProperty模式,或者可以覆盖DependencyObject.ShouldSerializeProperty虚方法。例如:

override bool ShouldSerializeProperty( DependencyProperty dp )

{

if( dp == FooProperty )

{

if( … )

return false;

}

return true;

}

使用类型转换器转换property编写入Xaml

最后一部分是关于是否应该将属性写入Xaml。一旦决定了应该这样做,下一个决定就是如何编写它——作为属性(如下面例子中的Width),或者标记(如下面的Background ):

<Button Width="100">

  Click

  <Button.Background>

    <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">

      <LinearGradientBrush.GradientStops>

        <GradientStop Color="Blue" Offset="0"/>

        <GradientStop Color="Red" Offset="1"/>

      </LinearGradientBrush.GradientStops>

    </LinearGradientBrush>

  </Button.Background>

</Button>

如何将宽度整数转换为字符串“100”?答案是类型转换器。类型转换器与类型相关联,用于与其他类型进行转换。在加载Xaml时,类型转换器用于从字符串进行转换,在编写Xaml时,类型转换器用于将其转换为字符串。

double类型有一个内置的类型转换器,上面使用它创建字符串“100”。类似地,string、float、int和所有其他基本类型都有内置的类型转换器。Enum还有一个内置的类型转换器,您可以在其中简单地使用字段名。这是所有标准类型转换器的功能;对于Xaml来说,这并不是什么新鲜事。

但是由于Xaml可以识别类型转换器,这意味着您可以为您的类创建自己的类型转换器:

[TypeConverter( typeof( FooConverter ))]

class Foo

{

...

}

这样,如果另一个类具有这种类型的属性:

class Bar

{

public Foo Foo

{

get ... set ...

}

}

它可以在Xaml中作为一个属性引用,使用类型转换器:

<Bar Foo="20" />

不要忘记,虽然在类型转换器中需要实现的只是ConvertFrom和CanConvertFrom,但是还需要实现ConvertTo和XamlWriter使用的CanConvertTo。

此外,除了向类注册类型转换器外,还可以在属性上注册它。如果在属性和属性的类型上都指定了类型转换器,则属性上的转换器优先。所以:

class Bar

{

    [TypeConverter( typeof( SpecialFooConverter ))]

    public Foo Foo

    {

        get … set ...

    }

}

Foo类有一个类型转换器,但是在Bar上,Foo用它自己的类型转换器。

用ValueSerializer转换property写入Xaml

XamlWriter还引入了一种写属性值的新方法,称为ValueSerializer,它类似于类型转换器。

在WPF中,将写属性最常见情况之一是画笔。这是因为每个人都希望能够使用一个简单的属性将按钮的Background 设置为“红色”,而不是冗长的 <Button.Background><SolidColorBrush …/></Button.Background>

但是Brush是一个很不简单的问题,因为您可以将Brush写入Xaml的方式是不明确的。这是因为Brush是SolidColorBrush的基类,它提供简单的颜色,以及更复杂的画笔,如LinearGradientBrush、ImageBrush、VisualBrush等。如果将类型转换器放在Brush上,XamlWriter调用CanConvertTo方法来查看是否可以将其转换为字符串,那么答案肯定是“也许”。这是因为如果值是一个SolidColorBrush,您可以将其转换为字符串(可以是像“Red”这样的简单字符串,也可以使用“##AARRGGBB”语法),但是其他画笔,如LinearGradientBrush,不能转换为属性字符串。问题是类型转换器没有给你做出这个决定的方法;当CanConvertTo被调用时,您不会得到对正在编写的实际Brush对象的引用。

XamlWriter的解决方案是ValueSerializer。值序列化器很像类型转换器,因为它有ConvertTo/From、CanConvertTo/From方法。您还可以使用[ValueSerializer]属性指向它,就像您使用[TypeConverter]属性指向类型转换器一样。值序列化器与类型转换器的一个不同之处在于,它只将对象转换为字符串,而不是将对象转换为任何类型。另一个关键区别是,它可以把字符串反序列化为对象。

在WPF中,画笔类是这样的:

[TypeConverter(typeof(BrushConverter))]

[ValueSerializer(typeof(BrushValueSerializer))]

public abstract class Brush : ...

{

...

}

BrushValueSerializer看起来是这样的:

class BrushSerializer : ValueSerializer

{

  ...

  public override bool CanConvertToString(object value, IValueSerializerContext context)

  {

      if( value is SolidColorBrush )

          return true;

      else

          return false;

  }

  ...

}

注意,Brush既有类型转换器,也有值序列化器。这是因为XamlReader不识别值序列化器;它只使用类型转换器。另一方面,当XamlWriter看到值序列化器和类型转换器都可用时,它会选择值序列化器。

最后:

您可以控制类上的哪些属性将由XamlWriter编写,并且可以控制它们是否作为属性或属性元素写入Xaml。大多数情况下,即使您不做任何事情,您创建的类也可以很好地与XamlWriter一起工作。到目前为止,您遇到的最可能的问题是默认情况下不会写入只读集合属性。所以你最可能要做的就是设置[DesignerSerializationVisibility(DesignerSerializationVisibility.Conten)

XamlWriter-将对象树写入Xaml的更多相关文章

  1. C语言的引用计数与对象树

    引用计数与对象树 cheungmine 2013-12-28 0 引言 我们经常在C语言中,用指针指向一个对象(Object)的结构,也称为句柄(Handle),利用不透明指针的技术把结构数据封装成对 ...

  2. Qt对象树

    Qt提供了一种机制,能够自动.有效的组织和管理继承自QObject的Qt对象,这种机制就是对象树.子对象动态分配空间不需要释放.

  3. QtCore是Qt的精髓(包括五大模块:元对象系统,属性系统,对象模型,对象树,信号槽)

    作者:小豆君的干货铺链接:https://www.zhihu.com/question/27040542/answer/218384474来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业 ...

  4. QT +坐标系统 + 自定义控件 + 对象树的验证(自动进行析构)_内存回收机制

    通过创建一个新的按钮类,来进行析构函数的验证,即对象树概念的验证.当程序结束的时候会自动的调用析构函数, 验证思路: 要验证按钮会不会自动的析构,(即在QPushButton类里面的析构函数添加qDe ...

  5. QT_4_QpushButton的简单使用_对象树

    QpushButton的简单使用 1.1 按钮的创建 QPushButton *btn = new QPushButton; 1.2 btn -> setParent(this);设置父窗口 1 ...

  6. Qt对象模型之二:对象树与元对象系统

    一.对象树的概念 Qt中使用对象树(object tree)来组织和管理所有的QObject类及其子类的对象.当创建一个QObject时,如果使用了其他的对象作为其父对象(parent),那么这个 Q ...

  7. 【转载】 C#中手动创建一个DataTable对象并写入数据

    在C#操作集合数据的过程中,有时候需要手动创建一个DataTable对象,并手动设置DataTable对象的Columns列名等信息,最后再往手动创建的DataTable对象中写入相应的数据信息,此时 ...

  8. QT从入门到入土(二)——对象模型(对象树)和窗口坐标体系

    摘要 我们使用的标准 C++,其设计的对象模型虽然已经提供了非常高效的 RTTI 支持,但是在某些方面还是不够灵活.比如在 GUI 编程方面,既需要高效的运行效率也需要强大的灵活性,诸如删除某窗口时可 ...

  9. qt 中的对象树

    本节内容讲解了什么是对象树以及其所带来的 GUI 编程好处.最后说明了在对象树中析构顺序问题并举了个特殊的例子,来说明平时编程中需要注意的一个点. 什么是对象树? 我们常常听到 QObject 会用对 ...

随机推荐

  1. 【HDFS API编程】开发环境搭建

    使用HDFS API的方式来操作HDFS文件系统 IDEA Java 使用Maven来管理项目 先打开IDEA,New Project 创建GAV然后next 默认使用的有idea内置的Maven,可 ...

  2. leetcode1027

    最直接的思路是三层循环,但是会超时,代码如下: public class Solution { public int LongestArithSeqLength2(int[] A) { ; var l ...

  3. matlab中变量问题——readonly 索引超出矩阵维度 workspacefunc 215

    matlab程序运行过程中会出现如上提示,在网上检索未果,键入dbstop if error语句也无法定错误之处,就想这个错误不是一般的错误. 通过间隔打断点的方式最后定位错误为一句exist = f ...

  4. Python常用内置函数介绍

    Python提供了一个内联模块buildin.内联模块定义了一些开发中经常使用的函数,利用这些函数可以实现数据类型的转换.数据的计算.序列的处理等功能.下面将介绍内联模块中的常用函数. Python内 ...

  5. 分布式版本控制系统Git的安装和使用

    作业要求来自https://edu.cnblogs.com/campus/gzcc/GZCC-16SE2/homework/2097 GitHub远程仓库的地址:https://github.com/ ...

  6. C++ 设置光标问题

    一.隐藏光标 1.引入头文件window.h 2.  定义光标信息结构体变量 CONSOLE_CURSOR_INFO  cursor info={1,0}; typedef struct _CONSO ...

  7. Secondary Indices

    [Secondary Indices] EOSIO has the ability to sort tables by up to 16 indices.  A table's struct cann ...

  8. mongodb异常恢复

    构造mongdb异常 启动mongodb,bash mongodb.sh #!/bin/bash pid_file=/var/run/mongodb/mongod.pid pid_dir=/var/r ...

  9. gcc 与 g++的区别

    原文: http://www.cnblogs.com/wb118115/p/5969775.html ------------------------------------------------- ...

  10. spring整合mybatis在使用.properties文件时候遇到的问题

    在spring里使用org.mybatis.spring.mapper.MapperScannerConfigurer 进行自动扫描的时候,设置了sqlSessionFactory 的话,可能会导致P ...