WPF 自定义表单控件项:Label + TextBox

▪ 前言

WPF 内置了丰富的 UI 控件,但是他们都是独立。在实际的开发中,我们经常需要将几个 UI 控件组合起来用。

比如下面 效果图 中的 “账号输入项”:背景白色,左侧 Label控件,右侧 TextBox控件。当然它还有一些变种:背景是透明的,下面有条白线。

为了和 “UI 控件” 名字区分,我们约定将几个 “UI 控件” 组合的统称为 “表单控件项

▪ 效果图

WPF 自定义表单控件项:Label + TextBox_第1张图片

▪ 原始实现

白色背景的 表单控件项(账号输入项)实现代码:

    
    

透明背景的 表单控件项(账号输入项)实现代码:

    
    

上次代码使用了 DockPanel 容器控件,并设置其内的 Label 控件属性 DockPanel.Dock="Left",这样做的主要好处就是 TextBox 空间宽度可以自适应填满整个 DockPanel 的剩余宽度空间

▪ 优化实现

原始实现,一个 表单控件项 的实现需要4行代码(每个控件里还有大量的属性设置)。当软件里只有一两个 表单控件项 的时候还可以,但是如果有大量的这些 表单控件项,那么将非常不利于维护。OK,接下来我们将利用 StyleControlTemplate 来优化上述的代码,看看优化后实现上述两个效果的代码:

白色背景的 表单控件项(账号输入项)实现代码:

透明背景的 表单控件项(账号输入项)实现代码:

只用一行代码我们就实现了上述功能,相比 原始实现 简单了很多。那我们是如何做到的呢?主要功劳还是 Style="{StaticResource styleFormcItemLTB}" 这一句,下面我们最为核心的 styleFormcItemLTB 样式资源的实现代码:



其实原理很简单,就是把 原始实现 的代码稍作一些修改然后移植到 StyleControlTemplate 中,然后通过 {TemplateBinding ...} 继承 TextBox 的一些属性。

以样式 styleFormcItemLTB 作为基础,通过继承 TextBox 控件里的自定义样式属性值自动设置 表单控件项 内各个控件属性,快速的构建出 表单控件项

这里有个知识点需要注意,代码:


ControlTemplate 中我们使用了上述代码,这段代码初学者这可能一下子理解不了。但是如果你把它移除了,你会发现运行软件是输入框没有了,那说明这段代码负责呈现一个输入框的,那能不能直接把它替换为:


尝试了一下,替换以后可以正常显示输入框,但是在 cs 文件中我们发现无法通过 uiUsername.Text 获取到输入框中的值;很明显,这个代码只实现了展示效果,没有实现一个控件的逻辑, 生成了一个新的 TextBox 控件,没有和 关联。

通过上面的尝试,我们基本可以确定 是生成了一个和 关联 TextBox,因为你可以在 cs 文件中通过 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" 的只有 DecoratorScrollViewer 元素,所以下面的代码就会出错:

// 提示错误:只有 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 …

你可能感兴趣的:(桌面软件,WPF,C#)