XAML属性赋值转换之谜(WPF XAML语法解密)
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语法解密)的更多相关文章
- XAML属性和事件
1.元素属性 XAML是一种声明性语言,XAML编译器会为每一个标签创建一个与之对应的对象.对象创建出来之后要对它的属性进行必要的初始化之后才有使用意义.因为XAML语言不能写程序运行逻辑,所以一份X ...
- 标记扩展和 WPF XAML
本主题介绍 XAML 的标记扩展概念,包括其语法规则.用途以及底层的类对象模型. 标记扩展是 XAML 语言以及 XAML 服务的 .NET 实现的常规功能. 本主题专门详细论述了用于 WPF X ...
- XAML 属性元素,标记扩展和注释
这节来讲一下XAML中的属性元素,标记扩展,和注释. 属性元素 一般的,我们想要对一个标签的属性赋值,可以直接在标签内部键入属性名给其赋值,如我们给button的Content属性赋值: <Bu ...
- WPF XAML
xmlns 在xml中专门用于声明名字控件, xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 是 ...
- 使用MVVM DataTemplate在WPF XAML视图之间切换
原文 使用MVVM DataTemplate在WPF XAML视图之间切换 更新:这个技术的改进版本,一个不创建视图,可以在以下链接找到: http://www.technical-recipes.c ...
- 使用MVVM DataTriggers在WPF XAML视图之间切换/Window窗口自适应内容大小并居中
原文 使用MVVM DataTriggers在WPF XAML视图之间切换 相关文章: http://www.technical-recipes.com/2016/switching-between- ...
- WPF XAML之bing使用StringFormat
WPF XAML之bing使用StringFormat // 转化为百分比 Text="{Binding Progress, StringFormat=\{0:P\}}" < ...
- xml 转换成对象(采用反射机制对对对象属性赋值)
/// <summary> /// 采用反射机制对对对象属性赋值 /// </summary> /// <param name="node">& ...
- [WPF,XAML] 跳动的心
原文:[WPF,XAML] 跳动的心 没什么艺术细胞,原谅,原谅! <Canvas Width="0" Height="0"> <Canvas ...
随机推荐
- C语言实现BMP图片生成
## #include <stdio.h> #include <stdlib.h> #include <string.h> typedef unsigned cha ...
- SSH 等效性问题 总提示输入密码问题
家目录权限问题 .chmod 700 /home/.. 得到的教训就是没事儿不要乱修改家目录权限,一时方便,可能在别的地方载跟头 ~~ 浪费好许时间 哎
- css3种引入方式,样式与长度颜色,常用样式,css选择器
# CSS三种引入方式 ## 一.三种方式的书写规范 #### 1.行间式 ```html<div style="width: 100px; height: 100px; backgr ...
- 堆操作,malloc
PS:堆空间缺省值都是cd,栈空间缺省值都是cc 内存有四区:栈.全局(静态).常量.除此以外的空间暂时不能随意使用,但是通过malloc函数申请就可以使用了. 利用malloc申请一个int变量,注 ...
- 20155326 实验四 Android程序设计实验报告
20155326 实验四 Android程序设计实验报告 实验内容 1.基于Android Studio开发简单的Android应用并部署测试; 2.了解Android.组件.布局管理器的使用: 3. ...
- 1.java面向对象编程三大特性之封装
封装即把一个对象的属性.行为等放在一个实体类中隐藏起来,不允许外部对其进行修改,但是被封装的属性.行为会对外提供一个接口与外部联系,这个对外的接口通常情况下就是set().get()方法.可以通过se ...
- whereis+whatis+man
使用Linux过程中无论是使用shell命令.程序开发或者用户文档都需要使用到强大的男人man命令. 使用方法也十分简单,以查看ls命令的使用方法为例: man ls man的搜索路径通常包括以下两个 ...
- struts2 18拦截器详解(十)
ModelDrivenInterceptor 该拦截器处于defaultStack中的第九的位置,在ScopedModelDrivenInterceptor拦截器之后,要使该拦截器有效的话,Actio ...
- day06_雷神_面向对象初识
day_06 递归函数 自己用自己.一般递归100多次,都没有解决的问题,放弃递归. count = 0 def func1(): global count count += 1 print(coun ...
- 自动化构建工具gradle安装教程(使用sdkman安装)
gradle是什么?(wiki解释) Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化建构工具.它使用一种基于Groovy的特定领域语言来声明项目设置,而不是传统的 ...