本笔记用于记录学习简单的WPF开发过程中所学到的知识以及想法,陆续更新中……
注意:这里的学习是以开发嵌入式系统上位机为目的进行的,不建议需要从事相关行业工作的朋友通过本文学习。文章名称:WPF开发笔记
作者:遮瑕修改时间:2022/11/24
WPF(Windows Presentation Foundation)是大部分内容都位于System.Windows命名空间内的.NET类型的一个子集,其核心为一个基于矢量的、与分辨率无关的显示引擎,WPF功能包括:程序标记语言 (XAML)、控件、数据绑定、布局、二维和三维图形、动画、样式、模板、文档、媒体、文本和版式。
由于其属.NET
,因此可以生成整合了.NET API
其它元素的应用程序。
通过WPF,可以使用标记
和代码隐藏
开发应用程序,通常使用XAML标记进行应用程序外观设计,同时使用托管编程语言(代码隐藏)来首先其行为,这样做可以前后端的分离,提高开发效率。
另外,微软官方对于WPF提供了详细的教学,可以进入微软官网学习。
命名空间(namespace)是对作用域的一种特殊的抽象,简单来说就是,框定一个范围,然后确保在这个范围内的函数、变量命名不会重复。例如两个学习都有学号为20220001的同学,但是他们属于两个不同的学校(命名空间),所以不会产生冲突。
.NET是微软开发的开源开发人员平台,用于生成许多不同类型的应用程序。可以使用C#、F#和Visual Basic编写.NET应用,.NET提供一组标准的通用的基类库和API。
对于WPF来说,前端有两种写法,一种是直接使用控件进行放置,还有一种是使用XAML进行编写。使用控件的好处是简单便捷,坏处是过于简单,所以大部分需要编程才能实现的功能使用控件放置会更加麻烦。可以选择两者结合使用,下面只记录XAML的相关使用方法。
注意:这里只记录XAML的语法,实际设计时会大量使用C#相关语句,此类语句由后文记录。
XAML是一种基于XML的标记语言,以声明形式实现应用程序的外观,有效的XAML必定是有效的XML。 通常用它创建窗口
、对话框
、页
和用户控件
,并填充控件
、形状
和图形
。
XAML的属性定义等操作类似于HTML,但是其比HTML更加严谨。WPF编程元素中通常以某种形式的树关系存在,由于 XAML 是基于 XML 的,因此使用它编写的 UI 汇集在嵌套元素的层次结构中,称为元素树
。 元素树提供了一种直观的逻辑方式来创建和管理 UI。
在编写XAML程序时,我们需要首先定义根元素,一个XAML只能有一个根元素, 在XAML代码中,一个Element(元素)
通常是一个Object(对象)
,在代码中映射对应.Net类。简单理解,在XAML中声明一个Element元素,其实就是对相应公共语言运行类库进行一次实例化操作。 例如为页面选择Window(桌面应用程序)或Page(网页),为外部字典选择ResourceDictionary,或为应用程序定义根选择Application。XAML代码声明一个元素对象,必须由一个开始标签“”和一个结束标签“”构成,基本语法如下:
但是仅有根元素并不是一个可以使用的应用的,我们还需要调用子元素来实现我们想要实现的功能,比如我们想要一个按钮,那就要使用进行定义,但是想要实现定义,就需要给其一个默认命名空间。因此,我们常常选择使用如下方式:
这是多个使用XAML作为UI定义标记格式的预处理器Microsoft技术中使用的相同的XAML命名空间标识符,在将以前定义的 UI 迁移到使用 C++、C# 或 Visual Basic 的 Windows 运行时应用时很有用。
由上文我们可以知道,xmlns是XML的命名空间,但是我们不仅需要XML,我们还需要XAML。因此我们常常使用“X:”前缀为XAML提供一个独立的命名空间,在编译的过程中,经常需要告诉编译器一些重要信息,比如应该与哪个C#类的编译结果合并、XAML声明的元素是public还是private、元素的变量名是什么…这些可以和XAML编译器沟通的工具,都在x命名空间中(这里使用X只是一种约定俗成,也可以使用其它的名称)。我们常用以下方式:
x命名空间的工具,可以划分为四大类:属性
、标记扩展
、指令
和数据类型
。常用的如下所示:
属性(9个):
x:Class
、x:ClassModifier
、x:Name
、x:FiledModifier
、x:Key
、x:Arguments
、x:FactoryMethod
、x:TypeArguments
、x:DataType
标记扩展(4个):x:Array
、x:Null
、x:Type
、x:Static
指令(2个):x:Code
、x:XData
类型(14个):x:Byte
、x:Int16
、x:Int32
、x:Int64
、x:Single
、x:Double
、x:Decimal
、x:Boolean
、x:Char
、x:String
、x:DateTime
、x:TimeSpan
、x:Object
此处不进行过多介绍,这里有小部分介绍
除了默认命名空间和XAML语言的XAML命名空间“x:”,你也可能在 Microsoft Visual Studio 生成的应用的初始默认 XAML 中看到其他的已映射 XAML 命名空间。例如:
“d:”XAML 命名空间旨在提供设计器支持,尤其是 Microsoft Visual Studio 的 XAML 设计界面中的设计器支持。 “d:”XAML 命名空间支持 XAML 元素上的设计器或设计时特性。
常用:
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
“mc:”表示并支持读取 XAML 的标记兼容性模式。 通常,“d:”前缀与特性 mc:Ignorable 相关联。 此技术使运行时 XAML 分析器忽略“d:”中的设计特性。
常用:
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
“local:”是一个前缀,通常会在模板化UWP应用项目
的XAML页面中为你映射它。 它映射为引用相同的命名空间,该命名空间旨在包含 x:Class 特性和所有 XAML 文件(包括 app.xaml)的代码。 只要你在此相同命名空间中定义你要在 XAML 中使用的任何自定义类,你就可以使用 local: 前缀在 XAML 中引用你的自定义类型。
来自模板化的UWP应用项目的相关前缀是common:。此前缀引用包含实用程序类(例如转换器和命令)的嵌套“Common”命名空间,你可以在“解决方案资源管理器”视图的“Common”文件夹中找到定义。
常用:
xmlns:local="clr-namespace:WPF_HelloWorld"
请勿使用
“vsm:”是有时在从其他 Microsoft 技术导入的较老 XAML 模板
中会看到的一个前缀。 该命名空间最初解决了旧版命名空间工具问题。 你应该在用于 Windows 运行时的任何 XAML 中删除“vsm:”的 XAML 命名空间定义,更改 VisualState、VisualStateGroup 和相关对象的任何前缀的用法,从而使用默认的 XAML 命名空间。
为使用XAML访问自己的自定义类型,可以在.NET语言(C#、VB)或者C++中定义,映射一个自定义的命名空间。例如,xmlns:myTypes
定义一个新 XAML 命名空间,通过在所有用法中添加令牌myTypes:
作为前缀来访问这个命名空间。
xmlns 定义包含一个值以及前缀命名。 该值是一个包含在引号内的字符串,后跟一个等号。 一种常见的 XML 约定是将 XML 命名空间与一个统一资源标识符 (URI) 相关联,这样就实现了唯一性和标识约定。 你也可以在默认 XAML 命名空间和 XAML 语言 XAML 命名空间中看到此约定,也可以在 Windows 运行时 XAML 所使用的且不太常见的 XAML 命名空间中看到此约定。 对于映射自定义类型(而不是指定一个 URI)的 XAML 命名空间,你可以为定义添加令牌using:
作为前缀。 在“using:”令牌后,可命名代码命名空间。
例如,要映射一个允许你引用“CustomClasses”命名空间的“custom1”前缀,并使用来自该命名空间或程序集的类作为 XAML 中的前缀,你的 XAML 页面应在根元素上包含以下映射:xmlns:custom1="using:CustomClasses
创建用户界面时,需要按照位置与大小排列控件从而形成布局。布局系统的基础为相对定位,这提高了控件对于窗口的适应性。
此外,该布局系统还可管理控件之间的协商以确定布局。 协商是一个两步过程:首先,控件将需要的位置和大小告知父级;其次,父级将控件可以有的空间告知控件。
- Canvas:子控件提供其自己的布局,即子控件在整个页面均可自由放置。
- DockPanel:子控件与面板的边缘对齐。
- Grid:子控件由行和列定位,即页面类似于表格,子控件在表格内放置。
- StackPanel:子控件垂直或水平堆叠。
- VirtualizingStackPanel:子控件在水平或垂直的行上虚拟化并排列。
- WrapPanel:子控件按从左到右的顺序定位,在当前行上的控件超出允许的空间时,换行到下一行。
以下将记录部分常用控件,但是控件数量过多,且操作逻辑大体相同,因此此处不做赘述,仅介绍常用控件以及微软对于控件介绍的链接。
- 按钮: Button 和 RepeatButton。
- 数据显示:DataGrid、ListView 和 TreeView。
- 日期显示和选项: Calendar 和 DatePicker。
- 对话框: OpenFileDialog、 PrintDialog和 SaveFileDialog。
- 数字墨迹: InkCanvas 和 InkPresenter。
- 文档: DocumentViewer、 FlowDocumentPageViewer、 FlowDocumentReader、 FlowDocumentScrollViewer和 StickyNoteControl。
- 输入: TextBox、 RichTextBox和 PasswordBox。
- 布局: Border、 BulletDecorator、 Canvas、 DockPanel、 Expander、 Grid、 GridView、 GridSplitter、 GroupBox、 Panel、 ResizeGrip、 Separator、 ScrollBar、 ScrollViewer、 StackPanel、 Thumb、 Viewbox、 VirtualizingStackPanel、 Window和 WrapPanel。
- 媒体: Image、 MediaElement和 SoundPlayerAction。
- 菜单: ContextMenu、 Menu和 ToolBar。
- 导航: Frame、 Hyperlink、 Page、 NavigationWindow和 TabControl。
- 选项: CheckBox、 ComboBox、 ListBox、 RadioButton和 Slider。
- 用户信息: AccessText、 Label、 Popup、 ProgressBar、 StatusBar、 TextBlock和 ToolTip。
为简化程序开发,便于前后端分离,WPF提供了一个数据绑定引擎来自动执行前端输入数据与后端变量的链接(也可以前端与前端链接), 数据绑定引擎的核心单元是Binding类
,其工作是将控件(绑定目标)绑定到数据对象(绑定源)。关系如下图:
绑定可以是双向的也可以是单向的,数据绑定的源可以是.NET属性
或者Dependency属性
,也可以是对象的属性,但是目标的属性必须是Dependency属性
。
依赖属性就是一种可以自己没有值,并能通过使用Binding从数据源获得值(依赖在别人身上)的属性。拥有依赖属性的对象称为“依赖对象”。
WPF开发中,必须使用依赖对象作为依赖属性的宿主,使二者结合起来。依赖对象的概念被DependencyObject类
所实现,依赖属性的概念则由DependencyProperty类
所实现。WPF的所有UI空间都是依赖对象。
模式 | 描述 |
---|---|
OneWay | 目标单向受源影响。 |
TwoWay | 目标、源相互影响。 |
OneWayToSource | 源单向受目标影响。 |
OneTime | 目标单向受源影响(仅一次),如果知道源属性不会变化,可以使用这种模式,降低开销。 |
Default | 此类绑定依赖于目标属性,即可以是双向(对于用户可以设置的属性,如TextBlock.Text,) 也可以是单向,除非明确指明一种模式,否则都采用此种模式。 |
注意:如果不指定,它默认为TwoWay。
在每一个窗体中都有一个DataContext,它是一个Object类型,主要用于存储绑定资源。在前台进行绑定时,文件会优先找到DataContext,而后从其中寻找到具体的某一个变量。
整体来说,为实现绑定,我们可以分为以下几步:
可以看到,在此处我们将TextBlock
控件的Text
属性进行了绑定,并将路径Binding Path
设为了Name,模式设为TwoWay。
在这里选择定义一个变量作为目标,及:
class Person
{
string name = "No";
public string Name
{
get { return name; }
set { name = value; }
}
}
我们需要定义一个类Person
来装我们的变量,变量为字符串Name
,为便于直观修改Name数据,我们额外定义了一个name来存储数据,而后将name赋值给Name。
上文有提及,文件会进入DataContext进行查询,我们将Person类赋给DataContext后,即可完成绑定。
DataContext = new Person();
以下为整体实现代码:
这是前端代码:
这是后端代码:
using System.Windows; // Window
namespace WPF_Hello
{
class Person
{
string name = "No";
public string Name
{
get { return "123"; }
set { name = value; }
}
public string Name2
{
get { return "456"; }
set { name = value; }
}
}
public partial class HelloWorld : Window
{
public HelloWorld()
{
DataContext = new Person();
}
}
}
C#由微软开发,如果拥有Javascript、Java或C++的开发经验将有助于C#的学习。
这里首先将列出一个基础的C#首先的HelloWorld输出,下文将逐步介绍:
using System;
namespace HelloWorldApplication
{
/* 类名为 HelloWorld */
class HelloWorld
{
/* main函数 */
static void Main(string[] args)
{
/* Main函数行为 */
Console.WriteLine("Hello World!");
}
}
}
以上程序大体为:
- 程序使用了
using
关键字,实现在程序中包含了System命名空间。其主要是将命名空间进行了引用,以便于后续可以简写,如果不用using,也可以在使用某个程序集中的类型时使用包含名字空间的全名。namespace
用于说明命名空间,此处声明了HelloWorldApplication命名空间。命名空间前文有所介绍,此处不进行说明。class
用于声明类,此处声明了名为HelloWorld的类。类主要是用于将同类型对象的共同属性和共同行为抽象出来封装到一起,从而避免编程时进行重复工作。例如定义一个类的成员为长宽高,那么直接声明一个对象为该类即可让该对象拥有长宽高的属性。类一般包含多个方法。方法定义了类的行为。在这里,HelloWorld 类只有一个 Main 方法。Main方法
为所有C#的程序入口点。注意,在WPF中是没有Main方法的,它自动生成了Main并放置到了其它文件中(由于本人不需要深入学习,因此没有进行了解)。- Main 方法通过语句Console.WriteLine(“Hello World”); 指定了它的行为。
WriteLine
是一个定义在System 命名空间
中的Console 类
的一个方法。该语句会在屏幕上显示消息 “Hello World”。
注意:由于本人不准备进行深入学习,因此这里只会进行一些简单的记录,如有错漏请指出。
对象(Object)类型是所有类型的终极父类,所有类型都可以向上转换为Object,Object 是 System.Object 类的别名。当一个值类型转换为对象类型时,则被称为装箱
;另一方面,当一个对象类型转换为值类型时,则被称为拆箱
。
object the_object;
the_object = 100; //这是装箱
可以将任何类型的数据都放入动态类型中,类似于Object,但是对象类型变量的类型检查是在编译时发生的,而动态类型变量的类型检查是在运行时发生的。
字符串(String)类型是 System.String 类的别名,它是从对象(Object)类型派生的。字符串类型的值可以使用两种形式进行分配:" "
和@" "
。
区别主要为两点:
a.@""
(逐字字符串)内的转移字符串会直接显示,失去转义的效果。
b.@""
中的字符串可以任意换行,换行符及缩进空格都计算在字符串长度之内。
指针类型变量存储另一种类型的内存地址。C# 中的指针与 C 或 C++ 中的指针有相同的功能,定义方式也相同。
可空类型在显示正常范围的基础上,增加了一个null值
。
在常规定义后加一个?
即可,例如:
int? i = null ;
用于判断一个数是否为null,如果为null则返还定义的值,例如:
num3 = num1 ?? 5.34; // num1 如果为空值则返回 5.34
用法等同于C、C++,但是定义方法与C不同,而且数组是一个引用类型,所以需要使用 new
关键字来创建数组的实例。
double[] balance = new double[10];
定义方法类似于C,调用时采用结构体名.成员名
。
struct Books
{
public string title;
public string author;
public string subject;
public int book_id;
};
枚举是一组命名整型常量。枚举类型是使用 enum 关键字声明的。
enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat }; //这样定义
枚举类型定义后,如果我们对其进行读取,也就是:
int x = (int)Day.Sun; //x为0
int y = (int)Day.Fri; //y为5
- public:访问不受限制。
- protected:访问限于包含类或派生自包含类的类型。
- internal:访问限于当前程序集。
- protected internal:访问限于当前程序集或派生自包含类的类型。
- private:访问限于包含类。
- private protected:访问限于包含类或当前程序集中派生自包含类的类型。
- file:已声明的类型仅在当前源文件中可见。 文件范围的类型通常用于源生成器。
new大多数情况可以看作一个类似于int、char的定义符,new关键字可以分为三个作用:
- new运算符:用于
创建对象
和调用构造函数
。- new修饰符:在用作修饰符时,new 关键字可以显式隐藏从基类继承的成员。
- new约束:用于在泛型声明中约束可能用作类型参数的参数的类型。
using、namespace等均会自动生成,同时每个控件的也会自动生成,直接在对应的位置进行编程即可。