由于近几年主要在做Web项目,客户端的项目主要是以维护为主,感觉对于基础知识的掌握没有那么牢靠,趁着这个周末重新复习下WPF的相关知识。
文章内容主要来自大佬刘铁锰老师的经典著作《深入浅出WPF》。
因为是复习,所以知识内容不会一一记录,如有需要了解更多可以看书中内容。
注意:博客中的代码示例我是以avalonia为UI框架写的。代码可能部分跟WPF的稍有不同。
XAML(读作zaml)是WPF技术中专门用于设计UI 的语言
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
</Window>
这个示例中,Window
是一个XAML元素,它表示窗口组件。xmlns
属性定义了XML命名空间,即指明XAML所使用的命名空间。在这里,http://schemas.microsoft.com/winfx/2006/xaml/presentation
是WPF的命名空间。
这个示例中的XAML代码只有一个Window
元素,它是一个空的容器。可以在Window
元素中添加其他界面元素,例如按钮、文本框等,来构建应用程序的用户界面。同样,可以在XAML中设置属性来更改元素的外观和行为。
先不说WPF中两个属性的定义,我们先看看对应一个类的对象。
1)属性是指类体里用get或set封装好的属性。属性是面向对象的理论范畴。比如说一个盒子,盒子的高度,长度,都是这个盒子的属性。在C#中实现的时候可以通过GET SET 封装。
2)特性是指应用于类,字段,方法,接口的进一步说明,用专业的术语就是给类,字段,方法,接口补充元数据,说的再白一点就是给它们打上标记,打了标记后编译器就知道如何来编译它。特性是属于编程语言层面的东西。比如2个相同的类,为了表示这2个类不完全相同或者有差异。这时候就要针对这两个类加一些特性。
[Serializable] // 这是Attribute,打上该标记的类说明可以被序列化
class Order
{
protected internal Single Price { get; set; } // 这是Property
[Obsolete("此方法已过时,请改用xxx.")] // 打上该标记说明此方法是过时的
public Single GetPrice()
{
return default(Single);
}
}
在看在XAML中:
Attribute 在XAML中的对于标签的属性特征,以下都是Window标签下的attribute
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:AvaloniaMarkdown.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="AvaloniaMarkdown.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
xmlns:md="clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia"
Icon="/Assets/avalonia-logo.ico"
Title="AvaloniaMarkdown"
Property 在后台代码中针对对象的属性特征,对应的后端类的对象Text,就是一个 property:
private string _text="hello";
public string Text
{
get => _text;
set => this.RaiseAndSetIfChanged(ref _text, value);
}
private string _filePath;
xmlns[:可选的映射前缀]="名称空间"
用于引用外来程序集
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:AvaloniaMarkdown.ViewModels"
没有映射前缀的是默认名称空间,默认名称空间只能有一个。
通过xmlns,我们可以直接使用这些CLR名称空间中的类型
XAML文件对应的.xaml.cs文件中的类的声明使用了partial关键字,可以把一个类拆分在多处定义,只要各部分代码不冲突即可,由于partial机制,我们实现逻辑代码留在.cs文件中,把UI元素相关代码分离出去。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
<Window>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10*"/>
<ColumnDefinition Width="10*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="10*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0"
Grid.Column="0" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="9*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" >
<Button>打开</Button>
<Button>保存</Button>
<Label/>
</StackPanel>
</Grid>
<TextBox Grid.Row="1" Grid.Column="0"/>
<md:MarkdownScrollViewer Grid.Row="1"
Grid.Column="1"/>
</Grid>
</Window>
XAML UI 框架是树状结构,以
x:Name的作用:
在资源字典(Resource Dictionary)中使用,构成其中的元素。资源(Resource )非常重要,存放需要重复使用的内容。
<Application.Resources>
<Color x:Key="SystemAccentColor">rgb(155, 138, 255)</Color>
<Color x:Key="SystemAccentColorDark1">rgb(155, 138, 255)</Color>
<Color x:Key="SystemAltMediumLowColor">rgb(52, 53, 65)</Color>
<Color x:Key="ApplicationPageBackgroundThemeBrush">rgb(52, 53, 65)</Color>
<Color x:Key="ControlStrokeColorDefaultBrush">rgb(94, 95, 109)</Color>
</Application.Resources>
只能单一元素充当其内容。
例如:Button、Label等(具体看书中列表)
除了用于显示主体内容的区域外,控件还具有一个显示标题(header)的区域
例如:GroupBox、TabItem
- 显示列表化的数据
- 内容属性为Items或ItemsSource
- 每种ItemsControl都对应有自己的条目容器(Item Container)
例如:ListBox、TreeView
在UI 上起装饰效果,比如可以使用Border元素为一些组织在一起的内容加个边框
例如:Border、ViewBox
最常用的文本控件
绘制图形使用的元素
所有的UI布局元素都属于这一族
Panel元素控制布局
包括:Canvas、Grid、StackPanel等
WPF的UI形成的树形结构,我们称之为可视化树(Visual Tree)
控件框架形成的树形结构,我们称之为逻辑树(Logic Tree)
五种大类
下面复习下它们的使用方法:
网格形式布局
定义多少列
定义了多少行
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10*"/>
<ColumnDefinition Width="10*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="10*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0"
Grid.Column="0" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="9*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Center">
<Button Classes="small" Margin="0,0,20,0" Command="{Binding UploadCommand}">打开</Button>
<Button Classes="small" Margin="0,0,20,0" Command="{Binding SaveCommand}">保存</Button>
<Label Content="{Binding FilePath}" Margin="0,0,20,0"/>
</StackPanel>
</Grid>
StackPanel可以把内部的元素在纵向或横向上紧凑排列,形成栈式布局
适合场合:
常用属性 | 数据类型 | 可选值 | 说明 |
---|---|---|---|
Orientation | Orientation | Horizontal(水平排列)\Vertical(垂直排列) | 决定内部元素是水平还是垂直排列,默认值(Vertical) |
Background | Brush | 背景色(Red/Yellow等等) | |
HorizontalAlignment | HorizontalAlignment | Center(中心)/Left(靠左)/Right(靠右)/Stretch(拉伸以填充父元素) | 决定内部元素在水平方向的对齐方式 |
VerticalAlignment | VerticalAlignment | Top(上方)/Center(中心)/Bottom(下方)/Stretch(拉伸以填充父元素) | 决定内部元素在垂直方向的对齐方式 |
画布,可以使用Left、Top、Right、 Bottom。内部元素通过离上下左右的距离控制元素在布局中的位置。
DockPanel会对每个子元素进行排序,并停靠在面板的一侧,多个停靠在同侧的元素则按顺序排序,。
<Grid>
<DockPanel Width="Auto" Height="Auto">
<Button DockPanel.Dock="Left" >1</Button>
<Button DockPanel.Dock="Top">2</Button>
<Button DockPanel.Dock="Right">3</Button>
<Button DockPanel.Dock="Bottom">4</Button>
</DockPanel>
</Grid>
流式布局,根据
Orientation
属性来设置其水平或垂直布局方向
默认是水平排列
<WrapPanel>
<Button />
<Button />
<Button />
<Button />
<Button />
<Button />
</WrapPanel>
垂直排列
<WrapPanel Orientation="Vertical">
</WrapPanel>
数据交互核心属性,在字段定义的set语句中使用一个PropertyChanged事件,,当为Binding设置了数据源后,就会自动侦听PropertyChanged事件
WPF
using CommunityToolkit.Mvvm
private string _searchKeyword;
public string SearchKeyword
{
get => _searchKeyword;
set => SetProperty(ref _searchKeyword, value);
}
Avalonia
using ReactiveUI;
private string _filePath;
public string FilePath
{
get => _filePath;
set => this.RaiseAndSetIfChanged(ref _filePath, value);
}
依赖属性是一种本身没有可以没有值,能通过使用Binding从数据源获取值的属性。拥有依赖属性的对象称为“依赖对象”。
特点包括:
附加属性,被环境赋予的属性,作用是将属性与数据类型(宿主)解耦,让数据类型的设计更加灵活
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10*"/>
<ColumnDefinition Width="10*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="10*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0"
Grid.Column="0" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="9*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" >
<Button>打开</Button>
<Button>保存</Button>
<Label/>
</StackPanel>
</Grid>
<TextBox Grid.Row="1" Grid.Column="0"/>
<md:MarkdownScrollViewer Grid.Row="1"
Grid.Column="1"/>
</Grid>
上面 TextBox 的Grid.Row,Grid.Column都是附加属性。
路由事件被激发后是沿着Visual Tree传递的,只有这样,“藏”在Templete里的控件才能把消息送出来。
每个WPF的界面的元素都具有一个名为Resources的属性,这个属性继承自FrameWorkElement类,其类型为ResourceDictionary,用来存储资源。
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="/Assets/Lang/en-US.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
在使用资源时候分为静态资源(StaticResource)和动态资源(DynamicResource)
在程序载入内存时对资源一次性使用,之后就不再去访问这个资源了
<TextBox Grid.Row="4"
Name="Editor3"
AcceptsReturn="True"
Text="{Binding Source={StaticResource VMLocator}, Path=EditorViewModel.Editor3Text,Mode=TwoWay}"
FontSize="{Binding Source={StaticResource VMLocator}, Path=EditorViewModel.EditorCommonFontSize}" />
程序运行过程中仍然会去访问资源
<Rectangle Name="PART_BottomRightCorner"
Fill="{DynamicResource DataGridScrollBarsSeparatorBackground}"
Grid.Column="2"
Grid.Row="2" />
<ControlTemplate>
<Border Name="DataGridBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="Auto,*,Auto,Auto">
<DataGridColumnHeader Name="PART_TopLeftCornerHeader"
Theme="{StaticResource DataGridTopLeftColumnHeader}" />
<DataGridColumnHeadersPresenter Name="PART_ColumnHeadersPresenter"
Grid.Column="1"
Grid.Row="0" Grid.ColumnSpan="2" />
<Rectangle Name="PART_ColumnHeadersAndRowsSeparator"
Grid.Row="0" Grid.ColumnSpan="3" Grid.Column="0"
VerticalAlignment="Bottom"
Height="1"
Fill="{DynamicResource DataGridGridLinesBrush}" />
<DataGridRowsPresenter Name="PART_RowsPresenter"
Grid.Row="1"
Grid.RowSpan="2"
Grid.ColumnSpan="3" Grid.Column="0">
<DataGridRowsPresenter.GestureRecognizers>
<ScrollGestureRecognizer CanHorizontallyScroll="True" CanVerticallyScroll="True" />
</DataGridRowsPresenter.GestureRecognizers>
</DataGridRowsPresenter>
<Rectangle Name="PART_BottomRightCorner"
Fill="{DynamicResource DataGridScrollBarsSeparatorBackground}"
Grid.Column="2"
Grid.Row="2" />
<Image Source="/Assets/maskGrad.png"
Grid.Row="1" Grid.RowSpan="2" Grid.Column="0" Grid.ColumnSpan="4"
VerticalAlignment="Stretch"
HorizontalAlignment="Right"
Width="60"
Stretch="Fill"
IsHitTestVisible="False"
ZIndex="1" />
<ScrollBar Name="PART_VerticalScrollbar"
Orientation="Vertical"
Grid.Column="2"
Grid.Row="1"
Width="{DynamicResource ScrollBarSize}"
ZIndex="2" />
<Grid Grid.Column="1"
Grid.Row="2"
ColumnDefinitions="Auto,*">
<Rectangle Name="PART_FrozenColumnScrollBarSpacer" />
<ScrollBar Name="PART_HorizontalScrollbar"
Grid.Column="1"
Orientation="Horizontal"
Height="{DynamicResource ScrollBarSize}" />
</Grid>
<Border Name="PART_DisabledVisualElement"
Grid.ColumnSpan="3" Grid.Column="0"
Grid.Row="0" Grid.RowSpan="4"
IsHitTestVisible="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CornerRadius="2"
Background="{DynamicResource DataGridDisabledVisualElementBackground}"
IsVisible="{Binding !$parent[DataGrid].IsEnabled}" />
</Grid>
</Border>
</ControlTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
设计外观和行为动作
Setter 类的Property属性用来指明你想为目标的哪个属性赋值,Value属性则是你提供的属性值
<Style Selector="Grid Button">
<Setter Property="BorderBrush" Value="rgb(94, 95, 109)" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="FontSize" Value="16" />
<Setter Property="Padding" Value="10,0,10,2" />
<Setter Property="Height" Value="32" />
<Setter Property="CornerRadius" Value="6" />
<Setter Property="Margin" Value="0" />
<Setter Property="FontFamily" Value="avares://TmCGPTD/Assets/Lato-Regular.ttf#Lato" />
<Setter Property="Transitions">
<Transitions>
<BrushTransition Property="Background" Duration="0:0:0.2" />
</Transitions>
</Setter>
</Style>
上面的例子是针对 Grid Button的Style,使用了若干个Setter 来设置 Grid 中的Button的一些属性,这样,在程序中, Grid 中的Button就会具有统一的风格。
当条件满足时会触发一个行为。
<Grid>
<TextBlock Text="raokun" Width="75" Height="20">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="blue" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
上面的例子,是当鼠标移动在上面时,字体的颜色变成蓝色。