在为WPF控件定义模板时,如果我们为一个元素指定了一个名称,那么我们就可以在模板的定义中通过该名称引用该元素了。但是为何我们不能用这种方法引用在同一个XAML中定义的其它界面元素呢?对于这个问题,您需要了解WPF所提供的名称管理机制。
XAML中的NameScope
首先来讲讲WPF的名称管理机制NameScope,也即是名称范围。名称范围主要提供了两种功能:记录XAML名称与界面元素实例之间的关联关系;防止名称冲突。可以说,第二种功能是第一种功能实现时所产生的副作用。而在XAML中引用某个名称时,WPF会自动使用相应的NameScope执行对名称的查找。
那么,WPF的名称范围是如何在XAML等程序组成中起作用的呢?如果一个元素在XAML中使用x:Name或Name属性设置了名称,那么WPF会为该属性设置执行一些额外的执行逻辑,如在对应的cs文件中自动生成具有相同名称的成员,并将它们注册到相应的名称范围中。如果在该范围中多次使用了相同的名称,那么WPF会抛出一个异常。在XAML中对某个元素进行引用的时候,WPF会从该NameScope中寻找该名称所对应的界面元素以进行操作。
当然,用户并不需要显式地对名称范围进行处理。默认情况下,WPF会使用一定的机制保证该文件中的各个界面元素可以拥有合适的名称范围。在XAML中常常作为根元素的Page类及Window类都提供了对名称范围的支持。如果XAML中的根元素并不是这两个类型,那么XAML处理器会在处理过程中为该文件隐式地添加一个Page元素作为新的根元素。通过这种方法,WPF可以保证XAML文件中对x:Name以及Name的使用可以将名称正确地注册进相应的名称范围中。
如果一个界面元素并不是由XAML处理器加载和处理的,那么它将不会附加一个名称范围。在这种情况下,您需要通过NameScope.SetNameScope()方法为该元素附加一个名称范围。该函数的原型如下:
1 public static void SetNameScope(DependencyObject dependencyObject, INameScope value);
在上面所展示的原型中,SetNameScope()函数所要求的dependencyObject参数需要是FrameworkElement或FrameworkContentElement类,或是实现了INameScope接口的对象。
而在XAML中,我们则需要使用NameScope的NameScope附加属性声明新名字范围。例如,您可以通过下面的代码在StackPanel上声明一个新的名字范围:
1 <StackPanel NameScope.NameScope=”NewScope”…/>
同时由于WPF中的样式和模板可以被多个界面元素重用,为了支持在其中为界面元素指定名称,WPF为它们提供了独立的名称范围。这也便是我们在模板中所使用的名称与其它元素拥有相同名称时也不会产生冲突的原因。如果您需要访问模板中的已命名元素,那么您需要通过调用Template属性的FindName()函数。
动态操作名称范围
对于一个已经被加载的XAML而言,其所对应的元素树已经被构建完毕,对名称的注册已经完成。在这种情况下,如果您仍然需要将该元素注册进名称范围之中,那么您就需要通过RegisterName()函数向名称范围注册该名称。那么,首先一个问题就是,既然名称范围可以嵌套,我们应当如何找到合适的名称范围?
答案是NameScope.GetNameScope()函数。该函数接受一个DependencyObject类型的参数,并返回与该实例相关联的名字范围:
1 public static INameScope GetNameScope(DependencyObject dependencyObject);
如果作为参数传入的dependencyObject并没有与之关联的名称空间,那么该函数将返回null。也就是说,如果需要得到一个界面元素所对应的名字空间,您需要依次查找其父元素,并通过GetNameScope()函数检测其是否拥有合适的名字空间。
在取得了合适的名称范围之后,我们就可以对该名称范围进行操作了。您可以通过在该名称范围实例之上调用RegisterName()函数完成对特定对象的注册。该函数的原形如下:
1 public void RegisterName(string name, Object scopedElement);
该函数的使用非常简单:以传入参数name作为名称范围内的关键字在名称范围内注册scopedElement所表示的对象。
除此之外,NameScope类还提供了从名称范围注销名字的函数:UnregisterName()。该函数只接受一个名为name的string类型参数:
1 public void UnregisterName(string name);
该函数用来从名称范围内注销特定名称。在通过RegisterName()函数动态注册一个名称之后,您常常需要在该名称所对应的对象不再可用的情况下注销该名称。首先,该名称所对应的对象由于不再可用,其状态可能和当前程序运行状态不再一致。其次,我们可以通过注销该名称允许该名称被重用。
另外,您还可以通过在名称范围之上调用FindName()寻找特定名称所对应的对象:
1 public Object FindName(string name);
在调用该函数时,您只要将name参数设置为您所想要查找的名字即可。需要注意的是,该函数的返回值的类型为object。同时该函数将在没有对应的名字时返回null。
除此之外,WPF还提供了更为简单的方法。虽然说FrameworkElemenet以及FrameworkContentElement并没有实现INameScope接口,但是其仍然提供了该接口所规定的FindName()、RegisterName()以及UnregisterName()等成员。这些函数内部会递归地查找其父元素是否拥有名称范围,直到该递归过程查找到一个拥有名称范围的元素为止。在找到了具有名称范围的元素后,该函数将调用该名称范围的相应函数。通过这些函数,您可以更轻松地完成对名称范围的操作。
写在最后
洋洋洒洒地写了这么多,我所想实际阐述的却仅仅是WPF对名字的管理。很多人在学习及使用WPF的过程中,并不是很清楚地了解怎样去引用一个名字所关联的界面元素。在跨组成对名称进行使用时,如在控件和控件模板之间,您需要通过不同的名字空间对名字进行解析,从而找到您所需要的界面元素。在遇到需要引用元素的时候,您就可以通过本文所提到的名字空间判断对一个名字进行引用的方法是否正确。
转载请注明原文地址:http://www.cnblogs.com/loveis715/archive/2012/04/09/2439803.html
商业转载请事先与我联系:silverfox715@sina.com