1. XAML介绍
1. XAML介绍
XAML(Extensible Application Markup Language)(发音:zammel)可扩展应用程序标记语言。
XAML是为构建应用程序用户界面而创建的一种新的描述性语言。
XAML提供了一种便于扩展和定位的语法来定义和程序逻辑分离的用户界面,而这种实现方式和ASP.NET中的"代码后置"模型非常类似。
XAML是一种解析性的语言,尽管它也可以被编译。它的优点是简化编程式上的用户创建过程,应用时要添加代码和配置等。
2. XAML 编译
XAML经过编译后,会生成BAML(Binary Application Markup Language 二进制应用程序标记语言)。BAML是标记化的,这样可以用较短的标记来代替XAML。
在项目的obj文件夹下,可以找到编译后的BAML文件。这是临时文件,当可以运行的dll生成后,BAML已经被嵌入到dll中去了。
3. XAML基础知识
XAML文档中的每一个元素都映射为.NET类的一个实例。元素的名称对应类名。
举例:<Button>
-对应WPF创建Button对象
元素其实就是类,如Window元素代表的是System.Windows.Window类
*Window元素代表整个窗口,Grid元素中可以放置任何控件。
- 通常来说,*元素一般为窗口(Window),页(Page),用户控件(UserControl)等。
- 与所有XML文档一致,在XAML文档中只能有一个*元素
4. Window类的常用属性
属性 | 说明 |
---|---|
Title | 窗口的标题 |
Width | 窗口的宽度 |
Height | 窗口的高度 |
Visibility | 指示窗口可见性 |
TopMost | 指定窗口是否是顶层窗口 |
WindowState | 指示窗口的状态(最大化,最小化等) |
WindowStyle | 指示窗口的样式(有无边框等) |
WindowStartupLocation | 指示窗口的启动位置 |
ShowInTaskbar | 指示窗口是否在状态栏显示 |
Icon | 允许你定义窗口的图标,该图标通常显示在窗口标题之前的左上角。 |
ResizeMode | 这可以控制最终用户是否以及如何调整窗口大小。默认是CanResize,允许用户像任何其他窗口一样调整窗口大小,使用最大化/最小化按钮或拖动其中一个边缘。CanMinimize将允许用户最小化窗口,但不能最大化它或拖动它更大或更小。NoResize是最严格的,最大化和最小化按钮被移除,窗口不能被拖得更大或更小。 |
SizeToContent | 决定Window是否应调整自身大小以自动适应其内容。默认是Manual, 这意味着窗口不会自动调整大小。其他选项有Width,Height和WidthAndHeight,分别对应自动调整宽度,高度或同时调整两者。 |
Topmost | 默认是false, 但如果设置为true,除非最小化,否则您的窗口将保持在其他窗口之上。仅适用于特殊情况。 |
更多的属性:
https://docs.microsoft.com/en-us/dotnet/api/system.windows.window?view=netcore-3.1#properties
5. 后台代码类
XAML只用于构造用户界面,那它的事件处理程序代码在哪呢?
一般我们在WPF中添加窗口时,可以到一个xxx.xaml会带一个xxx.xaml.cs,这个xxx.xaml.cs就是后台代码类,用于处理事件。
而这个这台代码类是通过Class特性来进行连接的。Class带了前缀x,说明这里是XAML语言中通用的部分
<Window x:Class="Class.MainWindow">
这个后台代码类在创建窗口时,会自动生成,我们可以看一下它的结构
public partial class MainWindow : Window{
public MainWindow(){
InitializeComponent();
}
}
这个类比较简单,只有一个构造函数,构造函数里执行了
InitializeComponent();
这个 InitializeComponent()
的作用就是调用System.Windows.Application
类的LoadComponent()
方法从程序集中提取BAML,并用它来构造用户界面。当解析BAML时,会创建控件对象,设置其属性并关联所有事件处理程序
public partial class MainWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector {
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Uri resourceLocater = new System.Uri("/LDSP;component/mainwindow.xaml", System.UriKind.Relative);
#line 1 "..\..\..\MainWindow.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);
#line default
#line hidden
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
this._contentLoaded = true;
}
}
如果不执行InitializeComponent() ,UI就不会被正常加载 ,而只显示默认空白窗口。所以当我们自己添加构造函数时,也要确保执行了InitializeComponent()
6. 嵌套元素
XAML文档就是一颗嵌套的元素树。
<Window>
<Grid>
<Button Content="OK"/>
<Label Content="label"/>
<TextBox/>
</Grid>
</Window>
XAML让每个元素决定如何处理嵌套元素,处理顺序如下:
- 如果父元素实现了IList接口,解析器将调用IList.Add()方法,并且为该方法传入子元素作为参数
- 如果父元素实现了IDictionary接口,解析器将调用IDictionary.Add()方法,并且为该方法传递子元素作为参数。 当使用字典集合时,需要设置x:Key特性,以便指定键名。
- 如果父元素使用ContentProperty特性进行修饰,解析器将使用子元素设置相应的属性
Grid控件支持ContentProperty特性,这个特性是继承自System.Windows.Controls.Panel类。
可以看到Panel类的部分声明如下:
[Localizability(LocalizationCategory.Ignore), ContentProperty("Children")]
public abstract class Panel : FrameworkElement, IAddChild
这表示可以使用任何子元素或内部文本来设置Children的属性。
XAML解析器根据是否是集合属性(集合实现了IList或IDictionary接口),采用不同方式处理内容属性。因为Panel.Children属性返回一个UIElementCollection对象,而UIElementCollection类又实现了IList接口,所以解析器使用IList.Add()方法将嵌套的内容添加到Grid控件中。
上面的XAML代码实际上对应以下的C#代码:
Grid grid = new Grid();
Button button = new Button();
button.Content = "OK";
Label label = new Label();
label.Content = "label";
TextBox textBox = new TextBox();
grid.Children.Add(button);
grid.Children.Add(label);
grid.Children.Add(textBox);
this.Content = grid;
说明:
在WPF中,会经常使用ContentProperty特性。
就Grid而言,Grid类使用ContentProperty特性来标识Panel.Children属性。
而其它控件会用 ContentProperty来标识 其它属性。
- 如Button类会使用ContentProperty特性标识 Button.Content属性(System.Windows.Controls.Button类继承自System.Windows.Controls.ContentControl)
[DefaultProperty("Content"), ContentProperty("Content")]
public class ContentControl : Control, IAddChild
7. 扩展标记
当我们需要将属性值设置为一个已经存在的对象,或者希望将属性值绑定到另一个控件来动态地设置属性值。这个时候就需要使用标记扩展。
语法:{标记扩展类 参数}
如:
<Label Foreground="{x:Static SystemColors.SystemColors.ControlLightBrush}"></Label>
7.1 XAML中定义了以下扩展标记
扩展标记 | 功能说明 |
---|---|
x:Type | 为命名类型提供 Type 对象。 此扩展最常用于样式和模板。 |
x:Static | 生成静态值。 这些值来自于值类型代码实体,它们不直接是目标属性值的类型,但可以计算为该类型。 |
x:Null | null指定为属性的值,可用于特性或属性元素值。有时候我们不想使用默认值或继承的值,就需要使用x:Null来进行置空 |
x:Array | 详细 https://docs.microsoft.com/en-us/dotnet/desktop-wpf/xaml-services/xarray-markup-extension |
7.2 WPF中定义了以下扩展标记(仅限WPF)
StaticResource
:通过替换已定义资源的值来为属性提供值。StaticResource
计算最终在 XAML 加载时进行,并且在运行时没有访问对象图的权限。
DynamicResource
:通过将值推迟为对资源的运行时引用来为属性提供值。 动态资源引用强制在每次访问此类资源时都进行新查找,且在运行时有权访问对象图。 为了获取此访问权限,WPF 属性系统中的依赖项属性和计算出的表达式支持 DynamicResource
概念。
Binding
:使用在运行时应用于父对象的数据上下文来为属性提供数据绑定值。 此标记扩展相对复杂,因为它会启用大量内联语法来指定数据绑定。
RelativeSource
:提供用于Binding的源信息,该信息可以在运行时对象树中导航多个可能的关系。 对于在多用途模板中创建的绑定,或在未充分了解周围的对象树的情况下以代码创建的绑定,此标记扩展为其提供专用源。
TemplateBinding
:使控件模板能够使用模板化属性的值,这些属性来自于将使用该模板的类的对象模型定义属性
ColorConvertedBitmap
:详细https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/colorconvertedbitmap-markup-extension
ComponentResourceKey
:详细https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/componentresourcekey-markup-extension
ThemeDictionary
:详细https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/themedictionary-markup-extension
8. 附加属性
附加属性是 XAML 中引入的一种编程概念。
- 附加属性可以理解为:这种属性可用于多个控件,但这一类属性不是在控件内部定义,而是在其它类中定义。
- 附加属性常用于控件布局。自己封装控件时,也会用到。
附加属性语法格式:typeName.propertyName
附加属性的工作原理如下:
每个控件都拥有自己内部定义的属性。但当在容器中放置控件时,控件会根据容器的类型而获得额外特性。这时就需要使用附加属性来设置这些附加的细节。
布局时,常用一种结构如下:
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!--通过附加属性来设置在Grid的哪一行,哪一列-->
<Label Content="HelloWorld" Grid.Row="1" Grid.Column="1"/>
</Grid>
附加属性是一个编程概念,并不是真正的属性。我们在设置附加属性的时候,这们实际上被转换为方法调用。
格式如: DefiningType.SetPropertyName()
Grid.SetRow(label, 1);
Grid.SetColumn(label, 1);
当调用SetPropertyName()方法时,解析器传递两个参数:被修改的对象,指定的属性值。
二、App.xaml
App.xaml是你的应用程序定义的起点。当你创建一个新的WPF应用时,Visual Stuido将自动为你创建它,同时还包括一个名为App.xaml.cs的后置代码文件。跟Window类似,这两个文件里面定义的是部分类,它们允许你同时在XAML标记和后置代码中完成工作。
App.xaml.cs 继承自Application类,在WPF Windows应用程序中是一个中心类。.NET会进入这个类,从这里启动需要的窗口或页面。这也是一个订阅一些重要应用程序事件的地方,例如,应用程序启动事件,未处理的异常事件等,更多内容会在后面提到。
App.xaml文件最常被用到的功能之一,是定义全域型资源,而那些资源可能會被用在整个应用程式中,用來取代全域风格。
1. App.xaml 结构
在创建一个新的应用程序时,自动生成的App.xaml可能会看到这样的内容:
<Application x:Class="WpfTutorialSamples.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
这里要注意的主要是StartupUri
属性,它实际上指定了当应用程序启动时应该被加载的Window或Page
。在这个例子中,MainWindow.xaml会被启动,但是如果你想使用另外一个window作为启动入口点,你只需要修改一下这个属性即可。
在某些场景下,你可能想在第一个窗口显示时或者在它的显示方式上多一些控制。在这个例子中,你可以移除StartupUri属性和值,然后把对它做的所有工作通过代码-后置方式来替代。这个将在下面进行演示。
2. App.xaml.cs 结构
通常情况下,对于一个新创建应用程序,与之匹配的App.xaml.cs 可能看起来像这个样子:
using System;
using System.Collections.Generic;
using System.Windows;
namespace WpfTutorialSamples
{
public partial class App : Application
{
}
}
你会看到这个类继承自Application类,它允许我们在应用级别去做一些事情。举个例子,你可以订阅Startup事件,然后手动创建你的启动窗口。
这是一个示例:
<Application x:Class="WpfTutorialSamples.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="Application_Startup">
<Application.Resources></Application.Resources>
</Application>
注意StartupUri是如何由一个对Startup事件的订阅来替换的(通过XAML对事件进行订阅在另外一个章节中有介绍)。在代码后置中,你可以像这样来使用事件:
using System;
using System.Collections.Generic;
using System.Windows;
namespace WpfTutorialSamples
{
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
// Create the startup window
MainWindow wnd = new MainWindow();
// Do stuff here, e.g. to the window
wnd.Title = "Something else";
// Show the window
wnd.Show();
}
}
}
比起使用StartupUri属性,在这个示例中比较酷的事是我们可以在显示启动窗口之前对它进行操纵。在这里,我们改变了它的title,虽然它不是特别有用,但是你还可以对其他事件进行订阅,或者显示一个启动屏幕(飞溅窗口)。当你可以控制所有东西的时候,你就可以做很多事情了。