博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
《Programming WPF》翻译 第9章 4.模板
阅读量:5897 次
发布时间:2019-06-19

本文共 4302 字,大约阅读时间需要 14 分钟。

原文:

对一个自定义元素最后的设计考虑是,它是如何连接其可视化的。如果一个元素直接从FrameworkElement中派生,这将会适当的生成它自己的可视化。(第7章描述了如何创建一个图形外观。)尤其是,如果你创建了一个元素,是为了提供一个特定的可视化表现,该元素应该完全控制这个可视化是如何管理的,一旦你编写了一个控件,通常你不会将一个图形硬编码到里面。

记住,一个控件的工作是提供行为。可视化是由控件模板提供的。这种可视化是由控件模板提供的。一个控件可能提供一组默认的可视化,而应允许这些可视化被替换,为了提供像内迁控件一样的弹性。(第五章描述了如何使用模板替换一个控件的可视化)符合这种方法的控件,这里可视化从控件中分离出来,通常引用到一个没有外观的控件。所有内迁到WPF的控件都是没有外观的。

当然,控件完全独立于其可视化是不可能的。任何控件将对模板必须满足的需求施加影响,如果控件操作正确。这些需求的程度随控件不同而不同。例如,Button有一个相当简单的需求——仅仅需要一个占位符放置标题或内容。Slider控件需要更广泛的需求:可视化必须提供两个按钮(增加和减少),“Thumb”,以及运行时Thumb上的一个跟踪。此外,它还需要能够响应点击和拖动在这些元素的任意一个,以及能够定位这个Thumb

在任意控件类型和样式或模板之间有一个隐式的约定。这个控件允许它的外观通过替换可视化树的方式进行自定义,但是这棵树必须轮流提供代表这棵树的某些特征。这个约定的本性依赖于这个控件,内嵌控件使用一些不同的样式,紧紧依赖于它们的可视化结构。下面的部分描述了很多将控件与其模板联系在一起的方式

 

9.4.1属性别名

控件和模板间最松散的约定形式是控件简单的定义了公有属性,以及允许模板来决定哪一个属性在别名中可见。(参见第5章获取更多属性别名的信息。)这个控件并不关心

在控件中是什么。

这里有一个单行的约定:控件提供属性和命令,不需要返回值。尽管如此,如果必要的话,这样的控件仍能响应用户输入。事件路由允许事件从可视化向上冒泡到控件。控件能够处理这些事件而不需要知道任何关于可视化本性的信息。

为了支持这个模型,你所要做的是,使用本章先前描述的依赖属性机制,来实现这些属性。示例9-11显示了一个自定义控件,并且定义了一个单独的名为Foo的依赖属性,Brush类型。

依赖属性支持这个控件的用户在模板中提及,正如示例9-12所示。

示例9-12

<
ControlTemplate 
TargetType
="
{x:Type local:MyCustomControl}
"
>
    
<
Grid
>
        
<
Rectangle 
Fill
="
{TemplateBinding Foo}
"
 
/>
    
</
Grid
>
</
ControlTemplate
>

所有的依赖属性自动支持属性别名。这种情形下的“约定”是由一组你的控件提供的依赖属性暗示的。

9.4.2占位符

一些控件希望在模板中找到一个特定的占位符元素。这将要么采取该元素指定类型的形式,或者可以是一个元素标记了一个特定的属性。

控件通过派生于ContentControl支持内容模板,使用元素类型的方法。它们希望在模板中找到一个ContentPresenter元素。这是一个特殊意图的元素,它的工作是在其他内容中担当一个占位符。

实际上,这是一个松散的强迫性的约定。如果模板中没有ContentPresenterContentControl通常不会申诉。控件并不绝对依赖于表现的内容,为了放在那里起作用。或者你能到达另一个极端,以及放一些ContentPresenter在你的模板中,可以使子内容多次出现。

你不需要做任何特殊的事情来支持ContentPresenter的使用,只要你派生于ContentControl,它可以很好的工作。控件的用户能够编写一个模板,正如示例9-13所示。

示例9-13

 

<
ControlTemplate 
TargetType
="
{x:Type local:MyContentControl}
"
>
    
<
Grid
>
        
<
Rectangle 
Fill
="White"
 
/>
        
<
ContentPresenter 
/>
    
</
Grid
>
</
ControlTemplate
>

9.4.3通过属性指定占位符

一些控件寻找用一个特定属性标记的元素。例如,派生于ItemsControl的控件,如ListBoxMenuItem,希望模板包括一个带有Panel.IsItemsHost属性设为true的元素。这标志了Panel将要扮演控件数据项目的宿主。ItemCOntrol使用附属属性取代占位符的原因是,允许你决定使用什么类型的Panel,作为数据项的宿主。(ItemControl还支持ItemsPresenter占位符元素的使用。这将使用于当样式不希望利用特定的panel类型的时候以及想要使用无论控件的默认panel是什么的时候)

为了实现使用此技术的控件,你需要定义一个自定义附属依赖属性,将其应用到占位符。这是一个Boolean属性。示例9-14注册了这样一个附属属性,并定义了通常的访问器功能。

示例9-14

public
 
class
 ControlWithPlaceholder : Control 
{
    
public static DependencyProperty IsMyPlaceholderProperty;
    
static ControlWithPlaceholder( ) {
        PropertyMetadata 
 isMyPlaceholderMetadata 
= new PropertyMetadata(false,
            
new PropertyInvalidatedCallback 
(OnIsMyPlaceholderChanged));
        IsMyPlaceholderProperty 
= DependencyProperty.RegisterAttached(
            
"IsMyPlaceholder"typeof(bool),
            
typeof(ControlWithPlaceholder), isMyPlaceholderMetadata);
    }
    
public static bool GetIsMyPlaceholder(DependencyObject target) {
        
return (bool) target.GetValue(IsMyPlaceholderProperty);
    }
    
public static void SetIsMyPlaceholder(DependencyObject target, bool value) {
        target.SetValue(IsMyPlaceholderProperty, value);
    }

注意到示例

9-14
PropertyMetaData
提供了一个PropertyInvalidatedCallBack
。这指示了一个可以在任意时间调用的方法,这个附属属性可以在任意元素上被设置或修改。在这种方法中,我们的控件将发现哪个元素被设置为占位符,示例9-15
显示了这个方法。

示例9-15

    
private
 
static
 
void
 OnIsMyPlaceholderChanged(DependencyObject target) 
{
        FrameworkElement targetElement 
= target as FrameworkElement;
        
if (targetElement != null && GetIsMyPlaceholder(targetElement)) {
            ControlWithPlaceholder containingControl 
=
                targetElement.TemplatedParent 
as ControlWithPlaceholder;
            
if (containingControl != null{
                containingControl.placeholder 
= targetElement;
            }
        }
    }
    
private
 FrameworkElement placeholder;
    
}

这个示例开始于检测属性被应用到派生于

FrameworkElement
的对象。记住我们希望这个属性会被应用到一个特定的控件模板内的UI
元素,因此如果被应用到别的元素而不是FrameworkElement
,我们这么做就得不到什么有用的东西。

其次,我们通过GetIsMyPlaceholder访问器方法检测了属性值,该方法是我们在示例9-14为附属属性定义的。这将是些微单独的,如果有人显示的设置这个属性为false,但是如果确实是这样,我们干脆不应该把元素作为占位符。

如果这个属性设置为true,我们继续获取目标元素的TemplatedParent属性。因为元素作为控件的模板一部分,这将返回可视化所属于的控件。(如果这个元素不是控件的成员,那么返回null。既然这个属性仅仅对模板中的元素有意义,如果没有模板化的父一级,我们就做不了任何事情。)我们还检查了父一级是一个控件类型的一个实例,而且忽略了属性,如果被应用到一个模板中的元素,在某种其它类型的控件模板中。

示例9-16显示了如何在一个控件模板中使用属性,来表明是哪一个元素在占位符中。

示例9-16

<
ControlTemplate 
TargetType
="
{x:Type local:ControlWithPlaceholder}
"
>
    
<
Grid 
local:ControlWithPlaceholder.IsMyPlaceholder
="true"
 
/>
</
ControlTemplate
>

一些控件希望有一种模板,提供一组详细明确的元素,来履行特定的角色在控件的标签中。例如,

HorizontalSlider
控件希望模板包含表示可拖动thumb
的元素,这个可点击的跟踪,在thumb
的任意一边,等等。模板需要指出哪一个元素是哪一个。这可以通过使用上述显示的技术,定义多个附属属性来实现。

当你写一个使用了占位符的控件时,你可能选择不执行这个约定。例如,如果模板的任意部分不见了,slider控件不会抱怨。一旦你只提供了要寻找的一些元素,这可以工作而不用抱怨。

你可能感兴趣的文章
BootStrap 智能表单系列 四 表单布局介绍
查看>>
mysql 三大范式【转载】
查看>>
MySQLDump在使用之前一定要想到的事情 [转载]
查看>>
Dapper优秀资料
查看>>
编译型与解释型、动态语言与静态语言、强类型语言与弱类型语言的区别
查看>>
PIE SDK矢量数据的读取
查看>>
win10安装tomcat9
查看>>
廖雪峰Python3 学习笔记--编码
查看>>
两种方式分别改变alertdialog的宽和高
查看>>
TextView-setCompondDrawables用法
查看>>
由扭结理论中的琼斯多项式的证明想到的
查看>>
淘宝Hadoop集群的概况
查看>>
webservice接口读取xml文件内容
查看>>
Centos7安装rabbitmq server 3.6.0
查看>>
关于eclipse的ADT(插件)对xml的android:text属性检查修改
查看>>
linux生成自验证ssl证书的具体命令和步骤
查看>>
Mvc 提交表单的4种方法全程详解
查看>>
iostat命令学习
查看>>
SQL 三种分页方式
查看>>
查看linux是ubuntu还是centos
查看>>