🍟 程序的本质 – 数据结构 + 算法 🍟
🥑本篇为学习李应保老师所著的《WPF专业编程指南》并搭配
WPF
开发圣经《WPF编程宝典第4版》以及痕迹大佬《WPF入门基础教程系列》文章所作笔记,对应《WPF专业编程指南》第 9 章之间内容,主要概述WPF
中关于样式的相关内容,希望可以帮到大家💖
从按钮、文本框到下拉框、列表框,WPF
提供了一系列常用控件,每个控件都有自己独特的特性和用途。通过灵活的布局容器,如网格、堆栈面板和换行面板,我们可以将这些控件组合在一起,实现复杂的界面布局。而通过样式和模板,我们可以轻松地定制控件的外观和行为,以符合我们的设计需求。
概述
在WPF
中UI
(User Interface)是通过数据来驱动的,数据是核心,UI
从属于数据并表达数据,这和传统的windows图形界面开发(比如“Winform)有很大的区别。
WPF中能够展示数据、响应用户操作的
UI`元素称为控件(Control),同时控件也是数据和行为的载体,它们被设计成总是无外观的(lookless)。控件中展现的内容称之为“数据内容”,响应用户操作后执行的方法(Method)或事件(Event)称之为“行为”。
WPF
革命性的概念就是把UI
元素(控件)的特性和显示方式分开。控件在用户界面上的样子是由控件模板决定的,WPF
为每个控件提供了默认的控件模板和相应的特性,这些默认模板提供了初始情况下控件的默认外观。同时,用户也可以用自己的模板来替换WPF
默认提供的模板,每个控件都可以成为开发者自己的个性化控件。
在WPF
中,有两个类似的类继承树:一个与界面(UI)相关,一个与内容(Content)相关,这种界面与内容分离设计使得WPF
能够更好地处理UI
元素和内容元素的不同需求。
-
UI
元素的类继承树以UIElement
为基础,它是所有可视化UI
元素的基类,可以理解为控件。UIElement
提供了处理输入事件、布局、渲染等UI
相关功能的基本支持。从UIElement
派生出了FrameworkElement
,它进一步扩展了UI
元素的功能,包括数据绑定、样式、模板等。Control
类则是FrameworkElement
中比较重要的子类,它提供了一些常见控件的默认外观和行为。 -
内容元素的类继承树以
ContentElement
为基础,它用于处理内容相关的功能,例如文本内容的显示和处理。与之相对应的是
FrameworkContentElement
,它从ContentElement
派生出来,提供了更多的内容相关功能。
需要注意的是,
ContentElement
与内容控件(Content Controls)是不一样的:ContentElement
主要用于处理文本内容,而内容控件则是一种控件,用于展示和管理单个内容元素。
控件图示,比着痕迹g的图画的:
回到文章的标题上来,WPF
中的样式(Style)是一种用于定义控件外观和行为的重要机制。它允许开发人员将属性设置封装到可重用的命名容器中,以便在应用程序中多次应用。在概念上WPF
的样式和HTML
中的CSS
类似,但由于WPF
中依赖属性的强大功能,使得WPF
可以通过样式完成CSS
所不具备的功能。
WPF
中的模板(Template)则是一种更高级别的样式,它可以完全重写控件的外观和行为,包括其布局和交互。通过使用模板,开发人员可以创建自定义控件,这些控件可能与基本控件有完全不同的外观和交互模型。
相关属性
- 按类别分类的控件
在WPF
中,”相关属性(Related Properties)”是指与控件或元素的属性之间存在一定关联或依赖关系的属性。这些属性的值通常会相互影响,当一个属性的值发生变化时,其他相关属性的值也可能会跟着改变。
常见的相关属性包括:
-
Width
和Height
:这两个属性定义了控件或元素的宽度和高度。它们通常是相互关联的,当其中一个属性的值发生变化时,另一个属性的值也可能会受到影响。 -
Margin
和Padding
:Margin
属性定义了控件或元素与其容器之间的空白区域,而Padding
属性定义了控件或元素内部内容与其边界之间的空白区域。它们的值也可能会相互影响。 -
IsEnabled
和Opacity
:IsEnabled
属性用于指示控件或元素是否处于启用状态,而Opacity
属性用于定义控件或元素的不透明度。当IsEnabled
属性的值为False
时,通常会将控件或元素的Opacity
属性设置为较低的值,以表示禁用状态。 -
IsChecked
、IsSelected
和Visibility
:这些属性常用于复选框、单选按钮、列表框等控件中。它们表示控件或元素的选中状态或可见性。当其中一个属性的值发生变化时,可能会触发其他相关属性的变化。
这些是一些常见的相关属性,具体的相关属性取决于控件或元素的类型和功能。了解这些相关属性之间的关系,可以帮助我们更好地使用和控制控件或元素的行为和外观。
样式
样式(Style),负责控制控件元素的外观以及行为,是可用于元素的属性值集合,可以把样式(Style)看成一种将一组属性值应用到多个元素的便捷方法,使用资源的最常见原因之一就是保存样式,有点类似与Web
中的css
文件,但是WPF
中的样式Style
还支持触发器(Trigger),比如当元素属性发生变化时,可通过触发器改变控件样式,这使得WPF
可以通过样式可以完成CSS
所不具备的功能。
简单来说,样式就是控件的外观、风格,在WPF
中,样式就像是控件身上的衣服。
样式可以应用于单个控件或整个应用程序范围内的所有控件。通过定义样式,可以设置控件的属性、视觉效果、动画、模板等内容,从而改变控件的外观和行为。此外,样式是组织和重用格式化选项的重要工具,我们都知道,重复的代码就是问题,不提倡在xaml
中使用重复的属性填充控件,而应该创建一系列封装了这些细节要点的样式,在需要的控件上应用该样式。
WPF
中每个控件都有Style
属性用来设定样式:
转到Style
定义可以知道,样式类Style
位于System.Windows
命名空间下:
在Style
类里面,定义了几个重要的属性:
-
TargetType
:设置样式所针对的控件类型,设置该属性后,在XAML
中设置Setters
或Triggers
中元素的Property
属性时,可以不用添加类作用域限定(这个后面部分会提到) -
Setters
:属性设置器SetterBase
对象集合 –SetterBase
类主要用于控制控件的静态外观风格 -
Triggers
:条件触发器TriggerBase
对象集合 –TriggerBase
类主要用于控制控件的动态行为风格 -
BaseOn
:在已有样式的基础上继承另一个样式 -
Resources
:资源字典ResourceDictionary
对象集合 -
IsSealed
:是否允许“派生”出其他样式
接下来我们来具体看看每个属性都是用来做什么的以及如何使用这些属性,近距离感受样式的神奇力量。
设置器 Setter
Setter
(设置器)是Style
类中的一个重要属性,其类型是SetterBaseCollection
,一个可以放入SetterBase
类型对象的容器,在Style
中Setter
属性用于设置目标对象的属性值。Setter
通常用于定义样式中的属性设置,以统一控件的外观和行为。
Setter
具有两个主要属性:
-
Property
(属性):指定要设置的属性名称。可以是任何依赖属性(DependencyProperty)或依赖对象(DependencyObject)的属性[超前警告⚠]。 -
Value
(值):指定要为属性设置的值。
Setter
的作用是在样式中定义属性设置规则,使得适用于该样式的目标对象会继承这些属性设置。当样式应用于目标对象时,Setter
将设置指定属性的值为所定义的值。在实际应用中,我们很少对某一个控件使用样式,使用样式的目的是:当改变某个样式时,希望所有使用该样式的控件都会改变它们的表现形式,从而不必对某控件逐一进行修改。
例如,可以使用Setter
在样式中设置Button
控件的背景颜色、字体大小、边距等属性。当应用该样式于Button
控件时,这些属性将自动应用,并使得所有的Button
控件具有相同的外观和行为。
Setter
可以在样式的标签中使用多个,以定义多个属性的设置。这样可以一次性为目标对象设置多个属性,提高代码的可读性和可维护性。
🆗具体案例:比如现在有三个外观风格一模一样的红绿配色按钮:
有需求需要三个按钮的风格必须是一样的,如果我们像上面xaml
里的方式控制这三个按钮的样子的话,在需要修改的时候就会很痛苦,需要挨个调整属性值,通过样式就可以很好的解决这个问题,我们可以把三个按钮预先定义在样式中:
然后把三个Button
里面的属性都删掉,可以发现Button
的样子和之前一个一个属性设置是一样的,但是这种方式相较于之前就显得灵活了很多,当需要调整的时候我们只需要调整样式中的对应属性,就可以让三个按钮跟着变化了:
🔜🔜
Setter
元素里只能指定一组属性值,可以通过多个Setter
来设置多个属性值
TargetType
是Style
类中的一个属性用来说明所定义的样式要施加的对象 🔙🔙
在上述样式中,使用了多个Setter
元素来设置按钮的属性:
-
表示在窗口的资源部分开始定义资源,其中包含样式。
-
表示定义一个针对Button控件的样式。
-
设置Button的背景颜色为绿色。
-
设置Button的边距为5个单位。
-
设置Button的字体大小为16。
-
设置Button的内容为”Bite Me!”。
-
设置Button的前景色(文本颜色)为红色。
为什么要在Resources
中定义样式呢?显然我们不能只在某个特定的控件中使用样式,当然这在WPF
中也是可行的:
但是这种定义方式和一开始我们分别定义每个按钮外观的方法如出一辙,显然失去了使用样式的意义。把样式定义在Window.Resources
中,它将适用于当前Window(窗体)
中所有的Button
控件。这意味着所有的按钮都会有绿色的背景、5个单位的边距、字体大小为16、显示文本为”Bite Me!”以及红色的前景颜色。这个时候通过修改样式中的相应属性的Value
就可以直接修改Button
的样式了,不需要为每个Button
控件都单独设置这些属性,可以大大简化界面设计和维护工作。
❗❗❗表转折:就像上面说的,现在定义的样式效果意味着所有的按钮都会有绿色的背景、5个单位的边距、字体大小为16、显示文本为”Bite Me!”以及红色的前景颜色,那如果有一个按钮不需要设定成这种样式或者它是别的样子怎么办呢?别急,有三种解决方法:
- 你给每个
Button
重新都设置需要的属性覆盖掉所设置的样式(属性的优先级),这样显然是有悖于我们使用样式的初衷的 - 使用
{x:Null}
显式清空Style
- 给定义的样式取个名字
x:key
,当需要的时候通过这个名字来找到它{StaticResource keyValue}
,这在为同一控件定义不同的样式时,非常方便。比如我们可以创建两种不同风格的Button
:
以上内容就是Setter
的用法以及样式的一般定义和使用方法。
样式继承 BaseOn
BaseOn
也是样式中几个重要属性之一,用于指定当前样式基于哪个已存在的样式进行继承和扩展。通过设置BaseOn
属性,可以创建一个新的样式,并在现有样式的基础上进行修改或添加新的设置。通过BaseOn
属性的巧妙运用,我们能够建立起一座座视觉上的宫殿,让用户陶醉其中。无论是继承经典、扩展创新,还是重塑风貌BaseOn`属性都是我们的得力助手。
基本语法如下所示:
声明的NewStyle
样式会继承ExistingStyle
中已定义的所有设置,然后可以在NewStyle
中添加、修改或覆盖需要的属性设置。使用BasedOn
属性可以提高样式的重用性,通过基于现有样式创建新的样式,可以在整个应用程序中一致地应用样式,并在需要时进行统一的更改。
比如上面例子中的样式ButtonStyle1
,我们需要一个在此基础上显示内容为斜体加粗的按钮,创建一个全新的样式当然没问题,但是设置背景色、调整间距、字体大小、字体颜色又得写一遍,用最多的时间创造最低的价值🙋,这个时候就可以通过继承ButtonStyle1
样式,加上额外的样式:
红配绿,冒傻气
尽管乍一看通过BaseOn
进行样式继承看起来非常方便,但它也存在一些缺点需要注意:
- 紧密的样式耦合:使用
BasedOn
属性继承样式时,子样式会紧密地依赖于父样式。这意味着如果父样式发生了变化,子样式可能也会受到影响,导致意外的样式改变。这种紧密的样式耦合可能会增加代码维护的复杂性。 - 代码可读性下降:当样式继承层级变得很深时,代码的可读性可能会下降。阅读代码时需要跟踪样式的继承关系,理解每个样式的作用和效果可能会变得更加困难。
- 样式冗余和性能影响:使用
BasedOn
属性继承样式时,子样式可能会继承了一些不必要的属性或样式,导致样式冗余。这可能会增加界面的渲染时间和内存消耗,对性能产生一定的影响。同时,样式继承层级的增加也可能会导致样式的解析和应用变慢。 - 难以调试和定位问题:当样式继承层级复杂时,如果出现样式的问题或者需要进行调试,可能需要在多个样式中进行追踪和定位,增加了调试的复杂性。
样式继承所产生的依赖性会使程序变得更脆弱,上面演示的实例倒还好说,但是,通常,根据不同的内容类型以及内容所扮演的角色会出现各类的样式,通过样式继承之后往往会得到一个更复杂的模型,并且真正重复使用的样式设置少之又少。除非有特殊原因要求一个样式继承自另一个样式(比如,第二个样式是第一个样式的特例,并且只改变了继承来的大量设置中的几个特征),否则不建议使用样式继承。
触发器 Triggers
触发器(Triggers)用于在特定条件满足时改变控件的外观或行为。它们是一种强大的工具,用于响应用户交互、数据变化或其他事件,从而实现动态的控件效果。
触发器可以在控件模板的Style
或ControlTemplate
中定义。它们基于属性的值来触发特定的动作或设置。
WPF中有几种类型的触发器,包括:
-
Trigger
:用于在属性值满足特定条件时触发动作或设置。例如,当按钮被点击时改变其背景色。 -
MultiTrigger
:与Trigger
类似,但可以同时满足多个属性的条件。 -
DataTrigger
:根据数据绑定的值触发动作或设置。例如,当绑定的数据达到某个特定值时隐藏控件。 -
MultiDataTrigger
:与DataTrigger
类似,但可以同时满足多个数据绑定的条件。 -
EventTrigger
:在特定事件发生时触发动作或设置。例如,当鼠标移入控件时改变其透明度。
这几种类型的触发器都是从TriggerBase
类中派生出来的。DataTrigger
和MultiDataTrigger
是一对数据触发器,两者的区别是在DataTrigger
中只能说明一个条件,而MultiDataTrigger
中则可以说明多个条件。Trigger
和MultiTrigger
也是一对触发器,和DataTrigger
相似,Trigger
中只能说明一个条件,而MultiTrigger
里可以说明多个条件。DataTrigger
和Trigger
的不同在于,DataTrigger
中带有Banding
属性,即DataTrigger
支持数据绑定。
触发器通常与Setter
一起使用,以在触发时改变控件的属性。我们可以在触发器中设置新的属性值,也可以应用动画效果或其他更复杂的操作。
浅浅尝试一下触发器:
Trigger
最简单,也是最基础的触发器,我们用一个小例子来演示一下,也作为样式的一次小复习,比如实现一个鼠标移过文本字体变大的效果:
大象踢腿
狮子拜天
二龙戏珠
龙卷风摧毁停车场
乌鸦坐飞机
佛朗明哥舞步
注意在使用触发器时要避免死循环,不要将触发器中设定的相关属性作为触发器的条件,即:改变相关属性A引起相关属性B发生改变,而相关属性B改变又引发相关属性A改变的情况。
MultiTrigger
实现一个输入效果,当输入的时候,边框变厚以提醒输入状态,同时背景颜色也发生变化:
DataTrigger 和 MultiDataTrigger
DataTrigger
和MultiDataTrigger
这一对触发器和Trigger
和MultiTrigger
非常类似,但是它们多了一个Binding
属性,需要用到数据绑定,这个后面再说。
EventTrigger
事件触发器类似winform
中的小闪电,就是通过触发特定的事件执行相应的动作,我们借助动画做一个小演示:
佛朗明哥舞步
格式化之后的代码看起来有点长😇。
定义样式到单独文件中
上面所演示的例子中,样式都是定义在目标控件所在的页面的资源里的,只有当前页面上的控件可以应用样式,这样当出现跨页面的时候样式的定义方式就显得有点无力了。想象一下,你的软件有多个页面,每个页面里面又有多个按钮、文本框、标签等控件,如果每个控件都手动设置它们的属性值来达到一致的外观,这将非常繁琐和容易出错。在大型软件工程中,当你需要开发多个WPF
应用程序或DLL
时,确保这些应用程序具有一致的外观是非常重要的。这也是样式(Styles)的真正强大之处所在。
现在的程序一般不都是会带有主题自定义的功能吗,可以通过调整给出的配置项自由修改页面效果,还有明暗主题切换。虽然WPF
没有Theme
、Skin
之类的东西,但是我们可以通过样式很好的完成这项工作捏🍗
尝试一下实现一个简单的明暗主题切换:
来看看怎么实现的:
构建主题字典
比较无聊,提取Style
切换颜色,也不知道标准否,文件结构大概如图所示:
分别创建了两个资源字典:DictionaryDarkStyle.xaml
、DictionaryDefaultStyle.xaml
用来存放默认主题以及深色主题,对应xaml
代码如下:
Default:
Dark:
加载切换主题资源字典
在App.xaml
中添加程序的资源,也就是我们定义的资源字典:
应用样式及切换
默认页面的xaml
如下:
通过按钮的点击事件来切换样式:
using System.Windows;
namespace ThemeSwitchDemo
{
///
/// MainWindow.xaml 的交互逻辑
///
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
bool isDarkStyleApplied = false;
private void button_Click(object sender, RoutedEventArgs e)
{
if (isDarkStyleApplied)
{
// 如果当前是黑色样式,则切换回默认样式
border.Style = (Style)Application.Current.Resources["BorderDefaultStyle"];
label1.Style = (Style)Application.Current.Resources["LabelDefaultStyle"];
label2.Style = (Style)Application.Current.Resources["LabelDefaultStyle"];
button.Style = (Style)Application.Current.Resources["ButtonDefaultStyle"];
isDarkStyleApplied = false;
}
else
{
// 如果当前是默认样式,则切换到黑色样式
border.Style = (Style)Application.Current.Resources["BorderDarkStyle"];
label1.Style = (Style)Application.Current.Resources["LabelDarkStyle"];
label2.Style = (Style)Application.Current.Resources["LabelDarkStyle"];
button.Style = (Style)Application.Current.Resources["ButtonDarkStyle"];
isDarkStyleApplied = true;
}
}
}
}
That’s all.
可能是初学的缘故,我觉得应该有比我这样更好的实现方式,欢迎大家留言讨论,毕竟我是初学。还有一件事,牛战士是从来不会摘下面具的🦬。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net