XAML与XML类似,就是XML延伸过来的。为了更好的表达一些功能,WPF对XML做了扩展,有些功能是WPF在后台悄悄的替你做了。有时候,虽然实现了某个功能,但是对实现原理还是很茫然。今天就讲讲XAML中赋值操作。

1 通过类型转换赋值

赋值是最简单最常见的操作,举例:

 <Button  Width="" Height="">
</Button>

这里把Width值赋值为200;用代码实现赋值,则为Button.With = 200; 这种赋值操作很直接,大家都能理解。但是仔细想想,感觉有点不对劲。XAML表达式Width="200",这里200是字符串,Width类型是double。字符串200怎么就转换成double了!你会说,200很明显可以转换为double类型,有什么大惊小怪的!

有时,程序实现的逻辑操作很傻瓜,人很容易理解的事,程序并不一定能理解。需要你告诉XAML编译器,怎么把字符串型转换成double型。确实有 一个转换类悄悄的把字符串型转换成了double型。

通过元文件,可以查到Width属性定义。

//
// 摘要:
// 获取或设置元素的宽度。
//
// 返回结果:
// 元素的宽度,单位是与设备无关的单位(每个单位 1/96 英寸)。默认值为 System.Double.NaN。此值必须大于等于 0.0。有关上限信息,请参见“备注”。
[Localizability(LocalizationCategory.None, Readability = Readability.Unreadable)]
[TypeConverter(typeof(LengthConverter))]
public double Width { get; set; }
Width属性定义[TypeConverter(typeof(LengthConverter))]。这句话就表明width转换类型是LengthConverter。当XAML编译器看到Width赋值操作,就会调用LengthConverter。输入是字符串,返回就是double。
你可能感觉到,对这个属性讲解有点啰嗦。我这里是想告诉你:几乎所有的赋值操作,都需要这种转换。
引申: 更深一步讲,如果我们定义了一个属性,这个属性是一个复杂的类型。在XAML如何赋值? 比如自己定义了类型如下:
public class MyPointItem
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}

有一个类包含此属性:

  public class MyClass
{
public MyPointItem Item { get; set; }
}

在XAML语法中如何对Item赋值,XAML语法只认识字符串型。这时需要参考上文Width处理方式。需要自己定义些转换类。定义一个类型继承TypeConverter,实现里面的函数。

比如这样赋值MyClass.Item = "123,456";你需要告诉编译器,如何将"123,456"转化成类型MyPointItem。这里字符串用逗号分隔,你可以用别的符号分隔;比如“#”,只要你的转换函数能处理就行。完整的处理函数如下:

//定义转换类型
public class MyPointItemConverter : TypeConverter
{
public override bool CanConvertFrom( ITypeDescriptorContext context, Type sourceType)
{
if (sourceType is string)
return true;
return base.CanConvertFrom(context, sourceType);
} public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType is MyPointItem)
return true; return base.CanConvertTo(context, destinationType);
} public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
if (value is string)
{
try
{
return MyPointItem.Parse(value as string);
}
catch (Exception ex)
{
throw new Exception(string.Format("Cannot convert '{0}' ({1}) because {2}", value, value.GetType(), ex.Message), ex);
}
} return base.ConvertFrom(context, culture, value);
} public override object ConvertTo(ITypeDescriptorContext context,
CultureInfo culture, object value, Type destinationType)
{
if (destinationType == null)
throw new ArgumentNullException("destinationType"); MyPointItem gpoint = value as MyPointItem; if (gpoint != null)
if (this.CanConvertTo(context, destinationType))
return gpoint.ToString(); return base.ConvertTo(context, culture, value, destinationType);
}
} //自定义类型
[TypeConverter(typeof(MyPointItemConverter))]
public class MyPointItem
{
public double Latitude { get; set; }
public double Longitude { get; set; } internal static MyPointItem Parse(string data)
{
if (string.IsNullOrEmpty(data))
return new MyPointItem(); string[] items = data.Split(','); //用逗号分隔,和XAML赋值中字符串分隔符保持一致
if (items.Count() != )
throw new FormatException("should have both latitude and longitude"); double lat, lon;
try
{
lat = Convert.ToDouble(items[]);
}
catch (Exception ex)
{
throw new FormatException("Latitude value cannot be converted", ex);
} try
{
lon = Convert.ToDouble(items[]);
}
catch (Exception ex)
{
throw new FormatException("Longitude value cannot be converted", ex);
} return new MyPointItem() { Latitude=lat, Longitude=lon };
}
}

转换类型不是万能的: 只有类型转换,也会遇到难以处理的情况。比如MyClass.Item = "null"。我的意思是将Item赋值为null。但是编译不会这么处理,仍然会调用转换类型MyPointItemConverter,结果就会抛出异常!WPF为此又引入了扩展标识符的概念。

2 扩展标识符

扩展标识符有特殊的语法,如果属性赋值为null,语法如下:
MyClass.Item ="{x:Null}"; 这里的Null其实是一个类型,继承自MarkupExtension;
//
// 摘要:
// 实现 XAML 标记以返回 null 对象,可使用该对象在 XAML 中将值显式设置为 null。
[MarkupExtensionReturnType(typeof(object))]
[TypeForwardedFrom("PresentationFramework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
public class NullExtension : MarkupExtension
{
//
// 摘要:
// 初始化 System.Windows.Markup.NullExtension 类的新实例。
public NullExtension(); //
// 摘要:
// 提供要用作值的 null 作为此标记扩展的输出。
//
// 参数:
// serviceProvider:
// 可为标记扩展实现提供服务的对象。
//
// 返回结果:
// 空引用。
public override object ProvideValue(IServiceProvider serviceProvider);
}
MyClass.Item ="{x:Null}"这句话的意思就是:编译器生成类型NullExtension,调用函数ProvideValue,将此返回值赋值给MyClass.Item;

再举个例子:
Height="{x:Static SystemParameters.IconHeight}”;
编译器处理逻辑是:生成类型StaticExtension,将字符串“SystemParameters.IconHeight”传给构造函数,调用函数ProvideValue,返回double类型。
其实StaticExtension会将字符串“SystemParameters.IconHeight”认为一个静态变量。XAML眼里只有字符串!

绑定 -- 一种很常用的扩展标识符类型
看如下语法:
 <Button  Width="" Height=""
Content="{Binding Height,RelativeSource={RelativeSource Self}}">
</Button>

对content的赋值,是不是感到一头雾水! binding其实也是扩展标识,最终继承自MarkupExtension;

  Binding : BindingBase --> BindingBase : MarkupExtension;

所以binding的作用也是将字符串转换成我们需要的类型。不过binding的参数比较多,有时候需要转好几个弯,才能找到真的源头!

对于上面的赋值,咱做个分析,来看看编译器处理的步骤:

1)生成Binding类型,构造函数传入“Height”,

2)Binding有一个属性为RelativeSource,参见元文件

 //
// 摘要:
// 通过指定绑定源相对于绑定目标的位置,获取或设置绑定源。
//
// 返回结果:
// 一个 System.Windows.Data.RelativeSource 对象,该对象指定要使用的绑定源的相对位置。默认值为 null。
[DefaultValue(null)]
public RelativeSource RelativeSource { get; set; }

仔细看看代码,属性类型和变量名称都是RelativeSource,这是c#语法允许的。当然,这样做会使人困惑!

  RelativeSource={RelativeSource Self},第一个RelativeSource其实是Binding的属性名称,第二个是类型名。Self是一个枚举值。

这句话的意思就是,生成一个类型RelativeSource,构造函数是枚举值Self;将这个变量赋值给属性RelativeSource。

3) 当Content需要值时,就会调用Binding的ProvideValue。这个函数就会把Button的属性Height返回!

当然这里绕了很大一圈,只实现了一个简单的操作:将Button的高度显示出来!感觉好费劲!
但是:绑定有一个特点,可以感知“源”变量的变化!举例如下
 <StackPanel>
<Button x:Name="btnTest" Width="" Height=""
Content="{Binding Height,RelativeSource={RelativeSource Self}}">
</Button>
<Button Margin="" Width="" Height="" Click="Button_Click">增加高度</Button>
</StackPanel>

Button_Click函数:
 private void Button_Click(object sender, RoutedEventArgs e)
{
btnTest.Height += ;
}

当执行Button_Click时,btnTest的高度增加10,显示内容也随之变化。是不是很神奇!为什么会变化?这里需要了解WPF特殊的属性“依赖属性”。这里就不深入讲解了!

当然绑定的优点不仅仅是这些,WPF会用到大量绑定,如果这些绑定都用代码来实现,太繁琐,也不易理解。

总结:微软为了让XAML好用,费了很多心思。为了XAML能做更多的工作,编译器会替你做很多事情!简单的一个赋值操作,背后门道很多!初学者如果不了解这些门道,就感到一片茫然!本文初步揭示了赋值操作背后的一些内幕,希望你读后,有豁然开朗的感觉!

 
												

XAML属性赋值转换之谜(WPF XAML语法解密)的更多相关文章

  1. XAML属性和事件

    1.元素属性 XAML是一种声明性语言,XAML编译器会为每一个标签创建一个与之对应的对象.对象创建出来之后要对它的属性进行必要的初始化之后才有使用意义.因为XAML语言不能写程序运行逻辑,所以一份X ...

  2. 标记扩展和 WPF XAML

      本主题介绍 XAML 的标记扩展概念,包括其语法规则.用途以及底层的类对象模型. 标记扩展是 XAML 语言以及 XAML 服务的 .NET 实现的常规功能. 本主题专门详细论述了用于 WPF X ...

  3. XAML 属性元素,标记扩展和注释

    这节来讲一下XAML中的属性元素,标记扩展,和注释. 属性元素 一般的,我们想要对一个标签的属性赋值,可以直接在标签内部键入属性名给其赋值,如我们给button的Content属性赋值: <Bu ...

  4. WPF XAML

    xmlns 在xml中专门用于声明名字控件, xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 是 ...

  5. 使用MVVM DataTemplate在WPF XAML视图之间切换

    原文 使用MVVM DataTemplate在WPF XAML视图之间切换 更新:这个技术的改进版本,一个不创建视图,可以在以下链接找到: http://www.technical-recipes.c ...

  6. 使用MVVM DataTriggers在WPF XAML视图之间切换/Window窗口自适应内容大小并居中

    原文 使用MVVM DataTriggers在WPF XAML视图之间切换 相关文章: http://www.technical-recipes.com/2016/switching-between- ...

  7. WPF XAML之bing使用StringFormat

    WPF XAML之bing使用StringFormat // 转化为百分比 Text="{Binding Progress, StringFormat=\{0:P\}}" < ...

  8. xml 转换成对象(采用反射机制对对对象属性赋值)

    /// <summary> /// 采用反射机制对对对象属性赋值 /// </summary> /// <param name="node">& ...

  9. [WPF,XAML] 跳动的心

    原文:[WPF,XAML] 跳动的心 没什么艺术细胞,原谅,原谅! <Canvas Width="0" Height="0"> <Canvas ...

随机推荐

  1. C# String字符串

    C#(静态String类) C#中提供了比较全面的字符串处理方法,很多函数都进行了封装为我们的编程工作提供了很大的便利.System.String是最常用的字符串操作类,可以帮助开发者完成绝大部分的字 ...

  2. C++调用ocx

    1.保证ocx已正常注册,可以使用 2.创建一个C++的命令行程序,在主程序#import "HZ_KevinTest.ocx" no_namespace 生成一次程序,debug ...

  3. (打表+优化)简单的求和 -- zzuli -- 1783

    http://acm.zzuli.edu.cn/problem.php?id=1783 1783: 简单的求和 Time Limit: 1 Sec  Memory Limit: 128 MBSubmi ...

  4. MySQL性能优化之延迟关联

    [背景]  某业务数据库load 报警异常,cpu usr 达到30-40 ,居高不下.使用工具查看数据库正在执行的sql ,排在前面的大部分是: SELECT id, cu_id, name, in ...

  5. android 屏幕旋转 不重新加载oncreate

    当手机设定了使用横屏或者竖屏的时候,还想要使用重力感应,可以设置activity属性 android:screenOrientation="sensor" 但是每次翻转屏幕,都会重 ...

  6. Windows Phone 8.1不完全体验报告

    在Build 2014中,微软倾心打造的Windows Phone 8.1终于粉墨登场,会场掌声不断.在大会结束后一周,经过漫长的等待,终于等到了开发者预览的推送,迫不及待地体验这一跨时代的移动系统. ...

  7. ASP 基础一

    ASP是什么? •ASP代表Active Server Pages(动态服务器页面) •需在IIS中运行的程序 我自己的理解就是UI和逻辑代码同在一个页面中,而缺点就是不易维护.code-Behind ...

  8. C#解密退款req_info结果通知

    微信支付退款结果通知API地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_16&index=10 static v ...

  9. CopyOnWriteArrayList源码解析(1)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 注:在看这篇文章之前,如果对ArrayList底层不清楚的话,建议先去看看ArrayList源码解析. ht ...

  10. MariaDB 存储过程与函数(10)

    MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可MariaDB的目的是完全兼容MySQL,包括API和命令行,MySQL由于现在闭源了,而能轻松成为MySQ ...