WPF 入门笔记
Posted BoiledYakult
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WPF 入门笔记相关的知识,希望对你有一定的参考价值。
本篇博文对接上篇末尾处WPF常用布局控件的综合应用,为痕迹g布局控件介绍课后作业的一个思路方法。
前言
首先来谈一谈布局原则:
WPF
窗口只能包含一个元素(Window元素属于内容控件,内容控件只允许有一个子元素),所以我们得在窗口中放置一个容器,才能使我们的窗口放置更多的内容。
所以在WPF
中,布局由容器决定,使用容器布局需要注意以下几点:
- 不要显示设置元素的尺寸:可以通过设置最大和最小尺寸来限定范围。
- 不要使用屏幕坐标来指定元素位置:根据元素在那种容器中,来合理安排元素的位置。如需要元素之间留白,可以使用Margin设置边距。
- 可以嵌套布局容器:新建
WPF
程序会默认提供一个Grid
容器,但是我们仍可在Grid
中添加容器来安排我们的布局。
一般的布局流程可以概括为以下几步:
- 确定布局需求:根据
UI
设计和功能需求,确定所需的布局方式和控件组织结构。 - 选择合适的布局容器:根据布局需求选择适合的布局容器,如
Grid
、StackPanel
、WrapPanel
等。 - 设置布局属性:使用布局属性控制控件在容器中的位置、大小、对齐方式等。
- 嵌套布局容器:根据需要,嵌套多个布局容器以实现复杂的布局效果。
- 调试和优化布局:使用布局调试工具,如布局边框和布局分隔器,对布局进行调试和优化,确保布局效果符合预期。
常用布局面板回顾:
Name | Description |
---|---|
Grid |
根据一个不可见的表格在行和列中安排元素,最灵活容器之一。 |
StackPanel |
在水平或垂直的堆栈中放置元素,多用于复杂窗口中的一些小区域。 |
WrapPanel |
自适应款高度并自动换行 |
DockPanel |
根据容器的整个边界调整元素 |
Uniform |
Grid 简化版,强制所有单元格具有相同尺寸。 |
Canvas |
最基本的面板,只是一个存储控件的容器,使用固定的坐标绝对定位元素 |
常用布局属性:
Property | Description |
---|---|
HorizontalAlignment |
用于设置子元素在容器中的水平位置 - Center、Left、Right、Stretch |
VerticalAlignment |
用于设置子元素在容器中的垂直位置 - Center、Top、Bottom、Stretch |
Margin |
用于指定元素与其父级或同级之间的距离 - 4个方向的边距(左、上、右、下)使用,可以同时设置4个相同边距、也可以单独设置每条边的边距 |
Padding |
用于指定元素与其子级之间的距离 - 4个方向的边距(左、上、右、下)使用,可以同时设置4个相同边距、也可以单独设置每条边的边距 |
布局详解
作业页面布局如下:
大致上可以划分为图示中的两行一列的布局,然后再细化(这里为了方便查看,我把表格线显示了出来):
<Window x:Class="HELLOWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:HELLOWPF"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="8*"/>
</Grid.RowDefinitions>
<Border Background="AntiqueWhite"/>
<Grid Grid.Row="1" ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="6*"/>
</Grid.ColumnDefinitions>
</Grid>
</Grid>
</Window>
然后完成子元素的布局,左边是个四行两列的布局:
<Grid Grid.Column="0" ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Margin="10" Background="#219afd"/>
<Border Margin="10" Background="#61b721" Grid.Row="0" Grid.Column="1"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="1" Grid.ColumnSpan="2"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="2" Grid.Column="0"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="2" Grid.Column="1"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="3" Grid.Column="0"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="3" Grid.Column="1"/>
</Grid>
右边是个四行三列的布局,也可以通过设置行高的比例设计为三行三列的布局:
<Grid Grid.Row="1" Grid.Column="1" ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Margin="10" Background="#ffa000"/>
<Border Margin="10" Background="#30b8c4" Grid.Column="1"/>
<Border Margin="10" Background="#e87a6e" Grid.Column="2"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="1" Grid.ColumnSpan="3"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="2" Grid.Column="0"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="2" Grid.Column="1"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="2" Grid.Column="2"/>
</Grid>
这样就初步完成了这个页面的布局:
<Window x:Class="HELLOWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:HELLOWPF"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="900">
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="8*"/>
</Grid.RowDefinitions>
<Border Background="AntiqueWhite"/>
<Grid Grid.Row="1" ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="6*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Margin="10" Background="#219afd"/>
<Border Margin="10" Background="#61b721" Grid.Row="0" Grid.Column="1"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="1" Grid.ColumnSpan="2"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="2" Grid.Column="0"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="2" Grid.Column="1"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="3" Grid.Column="0"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="3" Grid.Column="1"/>
</Grid>
<Grid Grid.Row="1" Grid.Column="1" ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Margin="10" Background="#ffa000"/>
<Border Margin="10" Background="#30b8c4" Grid.Column="1"/>
<Border Margin="10" Background="#e87a6e" Grid.Column="2"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="1" Grid.ColumnSpan="3"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="2" Grid.Column="0"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="2" Grid.Column="1"/>
<Border Margin="10" Background="AntiqueWhite" Grid.Row="2" Grid.Column="2"/>
</Grid>
</Grid>
</Grid>
</Window>
WPF 入门笔记之事件
一、事件路由
1. 直接路由事件
起源于一个元素,并且不能传递给其他元素
MouserEnter 和MouserLeave 就是直接事件路由
2. 冒泡路由事件
在包含层次中向上传递,首先由引发的元素触发,然后被父元素引发,直到到达WPF的元素树的顶部位置
例如:MouserUp
以下控件都绑定了,MouseUp事件。根据输出的顺序表现冒泡路由的效果
<Window x:Class="Haos.WPF.Case.Event.BubbleRouteWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Haos.WPF.Case.Event" mc:Ignorable="d" Title="BubbleRouteWindow" Height="300" Width="300"> <!--冒泡路由--> <Grid Margin="3" MouseUp="SomethingClick"> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <Label Margin="5" Background="AliceBlue" BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Center" Grid.Row="0" MouseUp="SomethingClick"> <StackPanel MouseUp="SomethingClick"> <TextBlock Margin="3" TextAlignment="Center" MouseUp="SomethingClick">Image and Picture Lable</TextBlock> <Image Source="/Images/logo_ye.png" Width="20" Stretch="Fill" MouseUp="SomethingClick"></Image> <TextBlock Margin="3" TextAlignment="Center" MouseUp="SomethingClick">Courtesy of the StackPanel</TextBlock> </StackPanel> </Label> <ListBox Margin="5" Name="lstMessage" Grid.Row="1"></ListBox> <CheckBox Margin="5" Grid.Row="2" Name="Check_Box">Handle first event</CheckBox> <Button Grid.Row="3" Margin="5" Padding="2" Click="Btn_Click">Clear List</Button> </Grid> </Window>
namespace Haos.WPF.Case.Event { /// <summary> /// BubbleRouteWindow.xaml 的交互逻辑 /// </summary> public partial class BubbleRouteWindow : Window { public BubbleRouteWindow() { InitializeComponent(); } public int EventCounter = 0; /// <summary> /// MouseUp 的处理程序 /// </summary> /// <param name="sender">触发者</param> /// <param name="e">事件参数</param> private void SomethingClick(object sender, MouseButtonEventArgs e) { EventCounter++; string message = $"#{EventCounter}:\r\nSender:{sender.ToString()}\r\nSource:{e.Source}\r\nOriginal Source:{e.OriginalSource}"; lstMessage.Items.Add(message); e.Handled = (bool)Check_Box.IsChecked; } private void Btn_Click(object sender, RoutedEventArgs e) { EventCounter = 0; lstMessage.Items.Clear(); } } }
3. 隧道路由事件
在包含层次中向下传递,首先由WPF的元素树的顶部触发,然后向子元素引发,直到到达最后一个子元素
隧道路由的事件名称以Preview开头,例如PreviewKeyDown键盘按下事件
<Window x:Class="Haos.WPF.Case.Event.ChunnelRouteWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Haos.WPF.Case.Event" mc:Ignorable="d" Title="ChunnelRouteWindow" Height="300" Width="300" PreviewKeyDown="SomethingKeyUp"> <!--隧道路由事件--> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <StackPanel Grid.Row="0" PreviewKeyDown="SomethingKeyUp"> <TextBlock Margin="3" HorizontalAlignment="Center" PreviewKeyDown="SomethingKeyUp">Image and text Lable</TextBlock> <Image Source="/Images/logo_ye.png" Width="20" Stretch="Fill" PreviewKeyDown="SomethingKeyUp"></Image> <DockPanel PreviewKeyDown="SomethingKeyUp"> <TextBlock PreviewKeyDown="SomethingKeyUp">Type here:</TextBlock> <TextBox PreviewKeyDown="SomethingKeyUp"></TextBox> </DockPanel> </StackPanel> <ListBox Margin="5" Name="lstMessage" Grid.Row="1"></ListBox> <CheckBox Margin="5" Grid.Row="2" Name="Check_Box">Handle first event</CheckBox> <Button Grid.Row="3" Margin="5" Padding="2" Click="Btn_Click">Clear List</Button> </Grid> </Window>
二、事件类型
1. 生命周期事件
1.1 Window.Initialized:在所有子元素都被设置完成时触发
这个元素已经被构建出来,并且它的属性值都被设置好了,所以通常都是子元素先于父元素触发这个事件.当一个元素的 Initialized 事件被触发, 通常它的子树都已经初始化完成, 但是父元素还未初始化. 这个事件通常是在子树的 Xaml 被加载进来后触发的. 这个事件与 IsInitialized 属性相互绑定
1.2 Window.Activated和Window.Deactivated:在窗口成为前台窗口时发生(激活)/在窗口成为后台窗口时发生
户在运行系统上的多个窗口中切换时,Activated和Deactivated在窗口的生命周期里会发生多次
1.3 Window.Loaded:在元素布局,呈现和准备交互时发生
为了让一些事情能在所有内容都显示给用户之前马上执行,可以用Loaded事件
1.4 Window.ContentRendered:在窗口的内容被渲染后发生
ContentRendered事件只对窗口第一次完全呈现出来进行触发。为了让一些事情能在所有内容都显示给用户之后马上执行,可以用ContentRendered事件
1.5 Window.Closed:当窗口即将关闭时发生
1.6 Window.Closing:Closed之后立即发生 Close 被调用,并且可以处理以取消关闭窗口。
2.鼠标事件
2.1 捕获鼠标坐标
捕获鼠标相对,元素的位置
2.2 捕获鼠标
当元素捕获鼠标以后,其他元素就无法触发其他元素上的,鼠标按钮类事件。直到 Mouse.Capture(null);方法接受到一个null参数。
2.3 鼠标拖放输入
用户单击或选择元素上一块区域,拖放动作开始,将鼠标移动到其他的元素上并且该元素可以接受拖放信息。
文本框自带拖放功能。
首先设置鼠标按下事件时,绑定拖放的源。在放目标元素开启,允许放属性AllowDrop。同时绑定Drop事件处理放的操作
<Window x:Class="Haos.WPF.Case.Event.MouseEventWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Haos.WPF.Case.Event" mc:Ignorable="d" Title="MouseEventWindow" Height="300" Width="300"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <!--获取鼠标位置--> <Rectangle Name="rect" Fill="AliceBlue" MouseMove="Rect_Move"></Rectangle> <!--捕获鼠标--> <Button Grid.Row="1" Name="Btn_Capture" Click="Capture_Click">Capture the Mouse</Button> <TextBlock Grid.Row="2" Name="Txt_Block">Mouse posstion at (0,0) in window coordinates</TextBlock> <DockPanel Grid.Row="3"> <!--设置拖动的源--> <Label Background="DarkKhaki" Name="Txt_Scouce" MouseDown="Scouce_Down">this is label mouse</Label> <!--被放的对象,开启允许拖放接受数据 AllowDrop="True"--> <Label Background="Aqua" AllowDrop="True" Name="Lbl_Drop" Drop="Lbl_Drops"></Label> </DockPanel> </Grid> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; namespace Haos.WPF.Case.Event { /// <summary> /// MouseEventWindow.xaml 的交互逻辑 /// </summary> public partial class MouseEventWindow : Window { public MouseEventWindow() { InitializeComponent(); } /// <summary> /// 鼠标在矩形移动 /// </summary> /// <param name="sender"></param> /// <param name="e">MouseEventArgs 不具备鼠标按钮,鼠标滚轮的事件提供的参数</param> private void Rect_Move(object sender, MouseEventArgs e) { //获取鼠标坐标 Point point = e.GetPosition(this); Txt_Block.Text = $"Mouse posstion at ({point.X},{point.Y}) in window coordinates"; if (point.X == 0) { //让被捕获的鼠标释放 Mouse.Capture(null); } } private void Capture_Click(object sender, RoutedEventArgs e) { Mouse.Capture(rect); Btn_Capture.Content = "鼠标被捕获…"; } /// <summary> /// 鼠标按下设置,拖放的源 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Scouce_Down(object sender, MouseButtonEventArgs e) { Label lbl = sender as Label; ; //启动拖动 DragDrop.DoDragDrop(lbl, lbl.Content, DragDropEffects.Copy); } /// <summary> /// 拖放,放的事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Lbl_Drops(object sender, DragEventArgs e) { Label lbl = sender as Label; lbl.Content = e.Data.GetData(DataFormats.Text); } } }
3.键盘事件
按照执行顺序排列如下
3.1 PreviewKeyDown:隧道键盘按下
3.2 KeyDown:冒泡键盘按下
3.3 PreviewTextInput:文本正在输入事件
3.4 TextChanged:本文框值发生改变
3.5 PreviewKeyUp:隧道键盘弹起
3.6 KeyUp:冒泡键盘弹起
<Window x:Class="Haos.WPF.Case.Event.KeyboardEventWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Haos.WPF.Case.Event" mc:Ignorable="d" Title="KeyboardEventWindow" Height="300" Width="300"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <DockPanel Margin="5" Grid.Row="0"> <TextBlock Margin="3">Type here:</TextBlock> <TextBox Focusable="True" PreviewKeyDown="KeyEvent" KeyDown="KeyEvent" PreviewKeyUp="KeyEvent" KeyUp="KeyEvent" PreviewTextInput="TextBox_PreviewTextInput" TextChanged="TextBox_TextChanged"></TextBox> </DockPanel> <ListBox Margin="5" Grid.Row="1" Name="lstMessage"></ListBox> <Button Grid.Row="2" Name="Btn_Clear" Padding="3" Margin="3" Click="Btn_Clear_Click">Clear ListBox</Button> </Grid> </Window>
namespace Haos.WPF.Case.Event { /// <summary> /// KeyboardEventWindow.xaml 的交互逻辑 /// </summary> public partial class KeyboardEventWindow : Window { public KeyboardEventWindow() { InitializeComponent(); } private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e) { string message = $"Event:{e.RoutedEvent} Key:{e.Text}"; lstMessage.Items.Add(message); } private void KeyEvent(object sender, KeyEventArgs e) { string message = $"Event:{e.RoutedEvent} Key:{e.Key}"; lstMessage.Items.Add(message); } private void TextBox_TextChanged(object sender, TextChangedEventArgs e) { string message = $"Event:{e.RoutedEvent}"; lstMessage.Items.Add(message); } private void Btn_Clear_Click(object sender, RoutedEventArgs e) { lstMessage.Items.Clear(); } } }
3.7 焦点相关:
Focusable设置控件是否能够获得焦点,和TabIndex属性设置按下Tab键获得焦点的顺序。在WPF中是使用树形结构布局的,所以当按下Tab键时,焦点会移动到当前元素的一个子元素。如果没有子元素,会移动到同级下一个元素的第一个子元素
4.手写笔事件
5.多点触控事件
以上是关于WPF 入门笔记的主要内容,如果未能解决你的问题,请参考以下文章