杂七杂八——Name与x:Name的关系
小序:
若是想用Google搜包含冒号的内容怎么办?好比我想搜x:Name这个字符串……
原来,应该是这样——x::Name
这世道,连搜索也要加转义,全民程序员,要不要人活了?
正文:
从第一天学习XAML语言开始,我就一直没分清为何对于一个XAML标签既能够设置它的Name又能够设置它的x:Name。问过一些同事,你们好像对这种比较孔乙己的问题不太感兴趣。今天花了些时间看了看,收获还挺多的。与你们分享一下。
首先,让咱们剖析一下XAML代码与C#代码之间的关系。
你们都知道,XAML是“用来设计UI”的,设计师用XAML设计出来的UI其后台代码(程序逻辑)能够由程序员用C#或者VB去写——这叫作Code-behind。实际上,设计师用XAML和程序用C#都是在构建同一个类,换句话说就是:把一个类劈成两半,与UI相关的那半由设计师用XAML写,与逻辑相关的那半由程序员用C#写。
.NET之因此支持这种劈开写的功能,得益于partial这个关键字。请你们看这两段代码
-
- public partial class Car
- {
- Color bodyColor;
- Color windowColor;
- Polygon door;
- Polygon seat;
- }
-
-
- public partial class Car
- {
- public void Accelerate() { }
- public void Break() { }
- }
- public class Car
- {
-
- Color bodyColor;
- Color windowColor;
- Polygon door;
- Polygon seat;
-
-
- public void Accelerate() { }
- public void Break() { }
- }
实际效果是彻底同样的。只是前者是把UI和逻辑劈开写,后者是混在一块儿写罢了。
劈开的确是劈开了,但让设计师用C#代码去实现UI恐怕不现实——让Blend直接生成C#不是不多是事情,只是C#描述UI太不直观了。因而,微软更进一步,把界面描述语言又向设计师方向推动了一层,也就是XAML语言。因而,开发和设计的格局就变成了这样:
有了XAML和将XAML解析为C#/VB的解析器,设计师们就能以本身最高的工做效率与程序员们合做开发软件了。目前关于XAML是如何解析成C#/VB的资料很是少。
Name揭秘
下面让咱们把目光集中在XAML->C#的解析上来,看看Name和x:Name的本质是什么。
让咱们看一段代码:
- <Window x:Class="WpfApplication2.Window1"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window1" Height="100" Width="300" Background="SteelBlue">
- <StackPanel>
- <TextBox Name="textBox1"/>
- <TextBox Name="textBox2"/>
- <Button Content="Show Name" Click="Button_Click"/>
- </StackPanel>
-
- <x:Code>
- <![CDATA[
-
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- Button btn = e.OriginalSource as Button;
- textBox1.Text = btn.Name;
-
- textBox2.Name = "Made_in_China";
- textBox2.Text = textBox2.Name;
- }
-
- ]]>
- </x:Code>
- </Window>
运行结果是:
我用XAML定义了三个UI元素,其中两个TextBox是有Name的。凡是你在XAML代码里设置了它的Name,那么在C#代码里就会有一个对应的变量。这可也很好解释,看看IL程序集就知道了——
不难看出,XAML解析器会为XAML代码中设置了Name的元素声明同名的引用变量,并且设置Name的元素则不会有引用变量生成(不过这个元素对应的对象是存在的,而且是VisualTree/LogicalTree上的结点)。
经过上面的代码,我看能够看出,Name的做用有两个:
1. 告诉XAML解析器为设置了Name的元素声明对应的引用变量(本例中是textBox1和textBox2),变量名使用Name的值。
2. 将XAML元素对应的对象(本例中是两个TextBox的实例)的Name属性设置为Name的值。
注意,引用变量一旦声明以后名字就不能改了,但对象的Name属性仍然能够改(示例中我就把由textBox2变量引用着的实例的Name属性改为Made_in_China了。)
让咱们再挖深点儿——TextBox的Name属性是从哪儿继承来的呢?查一查MSDN,原来是从FrameworkElement那儿继承来的。这个Name属性是很是重要的——若是你想在一棵“树”上查找叫某个名字的元素,调用“树根”的FindName方法就能够作到了。特别须要注意的是——FindName所使用的参数是对象Name属性的值而不是引用着这个对象的变量的名字。若是你的程序里只在XAML里设置了一次Name,那么引用变量的名字和对象Name属性的值刚好同样。但若是你改变了对象Name属性的值,那可就要当心了!请看下面的代码:
- <Window x:Class="WpfApplication2.Window1"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window1" Height="100" Width="300" Background="SteelBlue">
- <StackPanel>
- <TextBox Name="textBox1"/>
- <TextBox Name="textBox2"/>
- <Button Content="Show Name" Click="Button_Click"/>
- </StackPanel>
-
- <x:Code>
- <![CDATA[
-
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- textBox2.Name = "Made_in_China";
-
- TextBox t = this.FindName("Made_in_China") as TextBox;
- if(t==null)
- {
- return;
- }
- else
- {
- MessageBox.Show("OK");
- }
- }
-
- ]]>
- </x:Code>
- </Window>
注意,除非我取消对第17行的注释,否则,尽管我已经把textBox2.Name改为了Made_in_China,但因为这个新名字尚未被注册(即没有使用RegisterName方法将Made_in_China和textBox2所引用的对象关联起来),咱们仍然不能经过FindName找到它。
我知道这段话挺拗口,不过有一点你想经过某种方法查找由DataTemplate自动生成的UI元素时,或许应该跑来读一读这段绕口令:P
最后再啰嗦一句:为何这个Name属性能够起到在运行时被看成查找标识呢?是由于FrameworkElement被一个名为RuntimeNamePropertyAttribute的attribute所修饰。这个attribute明确指定,FrameworkElement的Name属性具有了做为查找标识的资格。TextBox等类派生自FrameworkElement,天然也有这个功能。下面是FrameworkElement类的声明。
- [RuntimeNamePropertyAttribute("Name")]
- [StyleTypedPropertyAttribute(Property = "FocusVisualStyle", StyleTargetType = typeof(Control))]
- [XmlLangPropertyAttribute("Language")]
- public class FrameworkElement : UIElement,
- IFrameworkInputElement, IInputElement, ISupportInitialize
- {
-
- }
x:Name揭秘
x:Name的x加一个冒号,说明它来自x这个名称空间。这个名称空间是定义在XAML的根元素上的。也就是这句:
这个x就是XAML的字头了。这个名称空间的本意就是告诉咱们——这个名称空间里所装的元素都与XAML解析有关。好比,我在代码里还使用了x:Code,把原本应该呆在C#代码里的内容请到XAML里来了。
可见,x:Name与Name根本不是一个层面上的东西——Name是直接与元素和面向对象编程相关的东西;x:Name是XAML语言解析层面上的东西。
若是咱们把上面代码中的全部Name都改为x:Name,全部效果都是同样的。
不知道XAML中标有x:的内容是否是会被“预处理”一下。
Name与x:Name关系揭秘
不过,若是你的逻辑感比较强,你会发现这样一个问题——为一个XAML元素声明对应的引用变量,这不是面向对象编程层面的东西而是XAML解析层的东西。并且,若是Name在语义学上“恪守本分”的话,它应该只去设置一下对象的Name属性值而不去管是否是声明变量的事儿。
大胆设想一下,你会猜到,当XAML解析器发现一个元素的Name被设置了,就会去调用x:Name的那套机制。也就是说,引用变量是在x:Name机制被调用的时候声明的。一样,若是你设置的是元素的x:Name,XAML解析器会在声明变量以后再去给实例的Name属性设置值。
这样的猜测可以获得证明吗?让咱们在MSDN里搜刮一下。
在x:Name的注释里,咱们能找到这段话:
Under the standard build configuration for a WPF application project that uses XAML, partial classes, and code-behind, the specified x:Name becomes the name of a field that is created in the underlying code when XAML is processed, and that field holds a reference to the object.
而在FrameworkElement.Name属性的文档里,又能找到这句话:
This property essentially provides a WPF framework-level convenience property to set the XAML x:Name Attribute.
也就是说,Name的确会去调x:Name那套机制。为何这么作?多是为了写起来方便。不过,我真不太喜欢这种搅和在一块儿的风格。我宁肯使用Name去给对象的Name属性赋值而使用x:Name去声明变量。
貌似“Under the standard build configuration ”这句话有点玄机。不知道非standard编译配置会有什么样的效果,怎样才能自定义编译配置呢?
不喜欢这种风格的缘由还在于:Name和x:Name互相调用会在某些逻辑下出问题,特别是“先有鸡仍是先有蛋”这种状况下。
关于在XAML中使用同一个程序集中的User Control
说到“先有鸡仍是先有蛋”的问题,让我想起了另外一个困扰本身好久的问题。请看下面的代码:
假设我有这样一个project,
如今我想把MyControl用在个人Window1里。若是代码写成这样:
- <Window x:Class="WpfApplication.Window1"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:local="clr-namespace:WpfApplication"
- Title="Window1" Height="300" Width="300">
- <Grid>
- <local:MyControl Name="myControl"/>
- </Grid>
- </Window>
当编译的时候,会报出错误:
最让人啼笑皆非的缘由就是“由于MyControl是在同一个程序集里,你就得使用x:Name而不是Name!”这算什么解释?跟是否是同一个程序集有什么关系?
TO BE CONTINUE...