WPF 内置了丰富的 UI 控件,但是他们都是独立。在实际的开发中,我们经常需要将几个 UI 控件组合起来用。
比如下面 效果图 中的 “账号输入项”:背景白色,左侧 Label控件,右侧 TextBox控件。当然它还有一些变种:背景是透明的,下面有条白线。
为了和 “UI 控件” 名字区分,我们约定将几个 “UI 控件” 组合的统称为 “表单控件项”
上次代码使用了 DockPanel 容器控件,并设置其内的 Label 控件属性
DockPanel.Dock="Left"
,这样做的主要好处就是 TextBox 空间宽度可以自适应填满整个 DockPanel 的剩余宽度空间
在 原始实现,一个 表单控件项 的实现需要4行代码(每个控件里还有大量的属性设置)。当软件里只有一两个 表单控件项 的时候还可以,但是如果有大量的这些 表单控件项,那么将非常不利于维护。OK,接下来我们将利用 Style 和 ControlTemplate
来优化上述的代码,看看优化后实现上述两个效果的代码:
只用一行代码我们就实现了上述功能,相比 原始实现 简单了很多。那我们是如何做到的呢?主要功劳还是 Style="{StaticResource styleFormcItemLTB}"
这一句,下面我们最为核心的 styleFormcItemLTB
样式资源的实现代码:
其实原理很简单,就是把 原始实现 的代码稍作一些修改然后移植到 Style 的 ControlTemplate 中,然后通过 {TemplateBinding ...}
继承 TextBox 的一些属性。
以样式
styleFormcItemLTB
作为基础,通过继承 TextBox 控件里的自定义样式属性值自动设置 表单控件项 内各个控件属性,快速的构建出 表单控件项
这里有个知识点需要注意,代码:
在 ControlTemplate 中我们使用了上述代码,这段代码初学者这可能一下子理解不了。但是如果你把它移除了,你会发现运行软件是输入框没有了,那说明这段代码负责呈现一个输入框的,那能不能直接把它替换为:
尝试了一下,替换以后可以正常显示输入框,但是在 cs 文件中我们发现无法通过 uiUsername.Text
获取到输入框中的值;很明显,这个代码只实现了展示效果,没有实现一个控件的逻辑,
生成了一个新的 TextBox 控件,没有和
通过上面的尝试,我们基本可以确定 uiUsername.Text
设置和获取控件的值。
为什么x:Name="PART_ContentHost"
这句话。
我们知道WPF控件是不用有固定形状的,我们可以通过 Style 来任意改变它的具体表现。
但是,控件本身具有特定的逻辑和作用:比如一个按钮,应该是可以点击的;一个输入框控件,应该是可以输入的。
这里就存在了一个矛盾:如果可以任意改变控件的具体表现,那么如何保证它特有的逻辑和作用呢?
答案就是 WPF 控件的 “部件” 概念。简单的说,就是你可以任意改变我,但是要提供我期待的部件,否则我的逻辑和作用就不能得到保证。如果阅读 TextBoxBase 的 MSDN 参考(TextBox 继承于 TextBoxBase),你会看到如下的特性:
[TemplatePartAttribute(Name = "PART_ContentHost", Type = typeof(FrameworkElement))]
[LocalizabilityAttribute(LocalizationCategory.Text)]
public abstract class TextBoxBase : Control
上述代码引用自 https://msdn.microsoft.com/zh-cn/library/system.windows.controls.primitives.textboxbase
该特性表明 TextBoxBase 控件期待你提供一个名字叫 PART_ContentHost
的部件,该部件必须是 FrameworkElement。而 TextBoxBase 将会把具体的 TextView 和 TextEditor 放到 PART_ContentHost 里面。
而能设置 x:Name="PART_ContentHost"
的只有 Decorator 或 ScrollViewer 元素,所以下面的代码就会出错:
// 提示错误:只有 Decorator 或 ScrollViewer 元素可以用作 PART_ContentHost
不使用 因为这个该元素不支持
Foreground
属性值
经过测试 也支持
x:Name="PART_ContentHost"
,不过其不支持Foreground
属性值
上述的代码设计中,我们采用了 TextBox 关联 styleFormcLTB
样式,然后在 TextBox 里设置样式属性值,styleFormcLTB
通过集成这些属性值快速构建出 表单控件项。
后来在使用过程中这种方式会导致 TextBox 禁用状态下时样式无法改变(默认变灰),就算在 styleFormcLTB
设置了 Trigger IsEnabled
也无法改变样式。就感觉 TextBox 控件里的样式属性值优先级高于 styleFormcLTB
里的属性值,优先级低的无法改变优先高级的属性值,没有办法只能基于 styleFormcLTB
样式类在按需做出其他样式类了,重构的代码如下:
styleFormcItemLTB01
中的首位 0 表示没有背景,末尾 1 表示没有背景情况下一种样式(下划线),当然也可以自定义 00、02、03、04 …
styleFormcItemLTB10
中的首位 1 表示白色背景,末尾 0 表示白色背景下的一种样式,当然也可以自定义 01、02、03、04 …