WPF-ImportantExample/README/重要说明.md

554 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 重要说明
## `InkCanvas`
### `InkCanvasEditingMode` 枚举
`EditingMode` 属性有以下几种值,每个值对应不同的功能和行为:
1. **`Ink`**
- 这是默认值,允许用户绘制笔迹(手写)到画布上。
- 用户可以使用手写笔或触摸屏在 `InkCanvas` 上绘制。
- 适用于需要自由绘画的情况。
- **举例**:用户在画布上用手写笔画出图形或写字。
2. **`EraseByPoint`**
- 允许用户通过触摸或手写笔直接擦除指定位置的笔迹。
- 在擦除模式下,用户可以通过点击或在某个区域上划过来擦除画布上的笔迹。
- **举例**:点击画布上的笔迹来删除它。
3. **`EraseByStroke`**
- 允许用户通过选择并删除特定的笔迹(线条)来擦除。
- 在此模式下,用户通过拖动手写笔(或鼠标)来选中一条笔迹,然后删除它。
- **举例**:用户可以通过点击或拖动鼠标擦除整条笔迹。
4. **`Select`**
- 允许用户选择画布上的一条或多条笔迹。
- 用户可以通过绘制一个框或点击已有的笔迹来选择它们。这些选中的笔迹可以进行后续的操作,比如移动、修改等。
- **举例**:用户选择一条已绘制的线条进行编辑或移动。**`None`**
- 不允许任何编辑操作。即画布处于只读模式,用户无法绘制、擦除或选择任何内容。
- **举例**:画布只是一个静态展示,不能进行任何手写或操作。各种模式的实际应用场景:
1. `Ink` 模式
- 用于自由绘图应用,例如绘画软件,用户可以随意画出线条、形状,甚至手写文字。
2. `EraseByPoint``EraseByStroke` 模式
- 用于擦除功能,在用户绘制之后需要修改或删除内容时,提供擦除笔迹的功能。
3. `Select` 模式
- 在用户完成绘制后,可以用来选择、移动或修改笔迹。它适合在用户绘制完后需要对某些元素进行调整的情况,比如在草图应用中编辑单个图形。
4. `None` 模式
- 用于展示或演示,画布上不允许任何编辑操作。常用于只读视图,例如用于展示静态图片或其他内容,用户不能进行任何绘画或修改。
## WPF中的事件
在 WPF 中事件是用户交互的一个重要部分WPF 的事件机制包括常见的用户界面交互事件(如按钮点击、鼠标点击、键盘输入等),以及比普通事件更复杂的“隧道”和“冒泡”事件。下面我会详细说明如何在 XAML 中使用事件(如 `Click``MouseUp` 事件),并介绍如何通过委托创建事件。同时,我还会讲解事件的“隧道”和“冒泡”机制,并通过代码示例进行说明。
### 一、WPF 中的事件Click 和 MouseUp 事件
#### 1. **Click 事件**
`Click` 事件通常与按钮控件等交互元素关联。当用户点击按钮时,`Click` 事件被触发。
**XAML 示例:**
```xml
<Button Content="Click Me" Click="Button_Click" />
```
**C# 代码示例:**
```csharp
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Button was clicked!");
}
```
在这个例子中,`Button_Click` 方法会在按钮被点击时被触发。
#### 2. **MouseUp 事件**
`MouseUp` 事件会在用户释放鼠标按钮时触发。你可以使用此事件来响应鼠标按钮的释放操作。
**XAML 示例:**
```xml
<Button Content="Mouse Up Test" MouseUp="Button_MouseUp" />
```
**C# 代码示例:**
```csharp
private void Button_MouseUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Mouse button released!");
}
```
`MouseUp` 事件可以用于检测鼠标何时被释放,通常用于拖放操作、绘图操作等。
------
### 二、使用委托创建事件
WPF 中的事件通常是基于委托的。委托在 C# 中是一种类型安全的函数指针,可以用来引用方法。
#### 1. **委托的创建和事件的声明**
首先定义一个委托类型,然后使用该委托创建事件。
**C# 示例:**
```csharp
// 1. 定义委托类型
public delegate void MyCustomEventHandler(object sender, EventArgs e);
// 2. 创建事件
public event MyCustomEventHandler MyCustomEvent;
private void RaiseCustomEvent()
{
// 3. 触发事件
MyCustomEvent?.Invoke(this, EventArgs.Empty);
}
```
#### 2. **订阅事件**
创建事件之后,可以使用 `+=` 操作符订阅事件,在事件触发时执行相应的方法。
**C# 示例:**
```csharp
public MainWindow()
{
InitializeComponent();
// 订阅事件
MyCustomEvent += OnCustomEventTriggered;
}
private void OnCustomEventTriggered(object sender, EventArgs e)
{
MessageBox.Show("Custom event triggered!");
}
private void TriggerButton_Click(object sender, RoutedEventArgs e)
{
// 触发自定义事件
RaiseCustomEvent();
}
```
在这个例子中,当点击一个按钮时,会触发 `RaiseCustomEvent` 方法,并调用 `OnCustomEventTriggered` 方法。
------
### 三、隧道时间和冒泡事件
WPF 支持 **事件路由**,即事件传播的方式。事件有两种传播模式:**冒泡Bubble** 和 **隧道Tunnel**
#### 1. **冒泡事件**Bubble Events
冒泡事件是指事件从事件源开始,逐级向上传播到父元素。比如,点击一个按钮时,`Click` 事件会先在按钮上触发,然后向上传播到父级容器、窗口等。
**代码示例:**
```xml
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Event Bubbling" Height="350" Width="525">
<Grid Background="LightGray" MouseDown="Grid_MouseDown">
<Button Content="Click Me" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Window>
```
**C# 示例:**
```csharp
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Mouse Down event bubbled up to Grid");
}
```
在上面的例子中,`Grid` 控件订阅了 `MouseDown` 事件,而当按钮被点击时,事件会从按钮开始冒泡,最终触发 `Grid_MouseDown` 方法。
#### 2. **隧道事件**Tunnel Events
隧道事件是事件传播的反向过程,事件从根元素(如窗口)开始,逐级向下传播到事件源。隧道事件通常以 `Preview` 开头(如 `PreviewMouseDown`)。
**代码示例:**
```xml
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Event Tunneling" Height="350" Width="525">
<Grid Background="LightGray" PreviewMouseDown="Grid_PreviewMouseDown">
<Button Content="Click Me" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Window>
```
**C# 示例:**
```csharp
private void Grid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("PreviewMouseDown event tunneled from root element");
}
```
在这个例子中,`PreviewMouseDown` 是一个隧道事件,它会从 `Window` 开始向下传播。如果点击按钮,`PreviewMouseDown` 事件会首先触发,然后传播到 `Grid`,接着传播到按钮。
------
### 四、冒泡与隧道的区别:
- **冒泡事件**:从事件源开始,向上传播到父元素,直到最顶层的容器(比如窗口)。
- 常见的冒泡事件:`Click`, `MouseUp`, `MouseDown`
- 事件传播的顺序:事件源 → 子元素 → 父元素。
> **委托和事件**:你可以通过自定义委托来创建自定义事件,并通过 `+=` 订阅事件。
- **隧道事件**:从最顶层的容器(如窗口)开始,向下传播到事件源。
- 常见的隧道事件:`PreviewMouseDown`, `PreviewKeyDown`
- 事件传播的顺序:父元素 → 子元素 → 事件源。
> **冒泡事件和隧道事件**WPF 中的事件支持路由机制,事件有两种传播方式:冒泡(从源元素向父元素)和隧道(从父元素向源元素)。
### 5、阻止事件
在 WPF 中,冒泡事件和隧道事件都可以通过两种主要方式进行拦截或阻止:**`Handled` 属性**和**`e.Handled = true`**。这两种方法可以阻止事件继续向上传播或向下传播,分别适用于冒泡事件和隧道事件。
#### 一、阻止冒泡事件
冒泡事件是从事件源(如按钮)开始,向父元素或祖先元素传播。你可以通过设置事件的 `Handled` 属性为 `true` 来阻止冒泡事件的进一步传播。
**阻止冒泡事件的传播**
通过设置 `e.Handled = true`,可以停止冒泡事件继续传播到父级元素。
假设有一个 `Button` 被嵌套在一个 `Grid` 中,你希望在点击按钮时,事件不再继续向上传播到 `Grid`
**XAML 示例:**
```xml
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Event Bubbling" Height="350" Width="525">
<Grid Background="LightGray" MouseDown="Grid_MouseDown">
<Button Content="Click Me" HorizontalAlignment="Center" VerticalAlignment="Center" MouseDown="Button_MouseDown"/>
</Grid>
</Window>
```
**C# 示例:**
```csharp
// Grid 上的 MouseDown 事件
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("MouseDown event bubbled up to Grid");
}
// Button 上的 MouseDown 事件
private void Button_MouseDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("MouseDown event triggered on Button");
// 阻止事件继续冒泡到 Grid
e.Handled = true;
}
```
在这个例子中,当按钮被点击时,`Button_MouseDown` 事件被触发,然后设置 `e.Handled = true`,阻止了事件继续冒泡到 `Grid`。因此,`Grid_MouseDown` 事件不会被触发。
**为什么使用 `e.Handled = true`**
`Handled` 属性标记事件是否已被处理。如果你设置 `e.Handled = true`,就意味着事件已经被处理,事件将不会继续传递到父元素,从而防止了冒泡事件继续传播。
#### 二、阻止隧道事件
隧道事件是从根元素开始,向下传播到事件源。要阻止隧道事件的传播,同样需要设置 `Handled` 属性为 `true`
**阻止隧道事件的传播**
通过设置 `e.Handled = true` 来阻止隧道事件继续传播到子元素。
假设有一个 `Grid``Button`,你希望阻止 `PreviewMouseDown`(隧道事件)继续向下传播。
**XAML 示例:**
```xml
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Event Tunneling" Height="350" Width="525">
<Grid Background="LightGray" PreviewMouseDown="Grid_PreviewMouseDown">
<Button Content="Click Me" HorizontalAlignment="Center" VerticalAlignment="Center" PreviewMouseDown="Button_PreviewMouseDown"/>
</Grid>
</Window>
```
**C# 示例:**
```csharp
// Grid 上的 PreviewMouseDown 事件(隧道事件)
private void Grid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("PreviewMouseDown event tunneled down to Grid");
}
// Button 上的 PreviewMouseDown 事件(隧道事件)
private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("PreviewMouseDown event tunneled to Button");
// 阻止事件继续传播到 Grid
e.Handled = true;
}
```
在这个例子中,`Button_PreviewMouseDown` 事件会首先触发。设置 `e.Handled = true` 后,事件就不会继续传递给 `Grid_PreviewMouseDown`,即阻止了隧道事件的传播。
> - **冒泡事件的阻止**:通过设置 `e.Handled = true` 来阻止事件向上传播(即冒泡到父元素)。
> - **隧道事件的阻止**:通过设置 `e.Handled = true` 来阻止事件向下传播(即阻止传递给子元素)。
>
> ##### 为什么要阻止事件传播?
>
> 阻止事件传播可以帮助你控制事件的流动,避免多个元素同时响应同一事件。例如,点击按钮时,可能既希望按钮处理该事件,也不希望父容器(如 `Grid`)对该事件做出响应。在这种情况下,你可以通过设置 `Handled = true` 来确保事件不再被父级元素处理。
>
> ##### 其他相关方法
>
> - **`Preview` 事件和普通事件:** `Preview` 事件是隧道事件,在事件传播到目标控件之前触发;普通事件是冒泡事件,在目标控件后开始传播。
> - **`e.Handled` 的局限性:** `e.Handled = true` 只在事件的传播链中起作用,通常是在局部处理事件时使用。如果你需要阻止某个控件的交互行为(比如禁止按钮点击),你可能还需要结合其他逻辑来实现。
## 所有元素生命周期
WPF 中的生命周期通常指的是从应用程序启动到关闭的整个过程中各个控件和窗口的创建、初始化、显示、交互、销毁等阶段。WPF 的生命周期可以分为 **应用程序生命周期****窗口生命周期**,并且每个控件、窗口和元素都有相应的生命周期事件。
WPF 应用程序的生命周期是从启动应用程序到关闭应用程序的全过程。这个生命周期涉及到应用程序的启动、运行和退出,通常是由 `App.xaml` 文件中的代码来管理。
### 1. **应用程序启动**
应用程序的启动是 WPF 生命周期的第一步。`App.xaml` 中定义了应用程序的启动入口。应用程序通常通过 `App.xaml.cs` 文件中的 `OnStartup` 方法启动。
- **`App.xaml`**:定义了应用程序的资源、样式、启动窗口等。
- **`App.xaml.cs`**:定义了应用程序的启动逻辑,通常重写 `OnStartup` 方法来处理启动相关的逻辑。
**示例:**
```xml
<Application x:Class="WpfApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
</Application>
```
**`App.xaml.cs`**
```csharp
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e); // 调用基类的 OnStartup 方法
// 启动时的其他逻辑
MessageBox.Show("Application Started");
}
}
```
### 2. **应用程序初始化**
WPF 应用程序启动后,首先会初始化资源字典、应用程序的 UI 树、数据绑定、主题等。在这个阶段,应用程序会加载 `App.xaml` 中定义的资源(如样式和主题),并为界面控件分配空间。
### 3. **应用程序运行**
应用程序运行时,`MainWindow` 等窗口会被创建和显示,用户可以与应用程序交互。此时事件循环开始运行,应用程序会响应各种事件(如鼠标点击、键盘输入等)。
### 4. **应用程序退出**
当应用程序关闭时,退出事件会被触发,窗口销毁,资源释放,所有的托管资源和非托管资源将被清理。
- **`OnExit` 方法**:当应用程序关闭时,`OnExit` 方法会被调用,通常用来处理应用退出时的清理工作。
**`App.xaml.cs`**
```csharp
protected override void OnExit(ExitEventArgs e)
{
base.OnExit(e); // 调用基类的 OnExit 方法
// 退出时的其他清理逻辑
MessageBox.Show("Application Exited");
}
```
## Windows类的生命周期
在 WPF 中,`Window` 类代表一个应用程序的窗口,它是应用程序的主要交互界面。`Window` 类的生命周期包括从窗口创建、显示、交互到关闭的一系列事件和过程。理解 `Window` 类的生命周期对于开发健壮和高效的 WPF 应用程序非常重要。以下是 `Window` 类生命周期的详细解释,涵盖了创建、初始化、加载、显示、关闭等各个阶段。
### 一、`Window` 类生命周期概述
`Window` 类的生命周期大致可以分为以下几个阶段:
1. **创建阶段**:窗口被实例化。
2. **初始化阶段**:窗口的资源和控件进行初始化。
3. **加载阶段**:窗口及其内容被布局并显示。
4. **激活阶段**:窗口获得焦点并成为用户交互的窗口。
5. **关闭阶段**:窗口即将关闭,清理资源。
6. **销毁阶段**:窗口被完全销毁,释放资源。
### 二、`Window` 类生命周期的详细过程
#### 1. **创建阶段**
在 WPF 中,`Window` 类通常通过 `new` 操作符创建。当创建窗口时,`Window` 的构造函数被调用,并且它会开始初始化窗口的基本结构。
- **构造函数调用**`Window` 类的构造函数首先被调用,在这个阶段,`InitializeComponent()` 方法会被调用,这个方法用于加载 XAML 文件并初始化控件。
**示例:**
```csharp
public MainWindow()
{
InitializeComponent(); // 初始化 XAML 控件
}
```
#### 2. **初始化阶段**
窗口初始化时,会进行控件初始化以及一些基础设置。此时,窗口的控件已经创建好,但尚未显示到屏幕上。
- **`OnInitialized` 方法**:当窗口的控件和资源完全初始化时,`OnInitialized` 方法会被调用。此时,控件的属性已经设置好,但尚未进行布局和渲染。
**示例:**
```csharp
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
// 初始化完成后执行的逻辑
MessageBox.Show("Window Initialized");
}
```
#### 3. **加载阶段**
加载阶段是窗口开始准备显示的过程。WPF 会在这个阶段计算控件的布局,应用控件的样式,并最终将控件的可视化内容绘制到屏幕上。控件的大小、位置和可见性等属性都在此阶段确定。
- **`OnLoaded` 方法**:当窗口的控件和布局完全准备好并呈现时,`OnLoaded` 方法会被调用。此时窗口已经显示在屏幕上,控件已经布局完成。
**示例:**
```csharp
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
// 控件加载完成后的逻辑
MessageBox.Show("Window Loaded");
}
```
此时,你可以访问窗口中已经加载的控件,并可以做一些初始化的后续工作,如绑定数据、动态添加控件等。
#### 4. **激活阶段**
当窗口获得用户输入焦点时,它会被激活并成为前景窗口。此时,窗口将接收用户的鼠标、键盘等输入。激活窗口通常是用户与窗口交互的开始。
- **`Activated` 事件**:窗口获得焦点并被激活时,`Activated` 事件会被触发。
**示例:**
```csharp
private void Window_Activated(object sender, EventArgs e)
{
MessageBox.Show("Window Activated");
}
```
此事件通常用于更新窗口的内容或状态,或者启动与用户交互的操作。
#### 5. **关闭阶段**
窗口进入关闭阶段时,应用程序会开始清理窗口资源,释放窗口占用的内存和其他资源。此时,窗口关闭前会进行一些处理,比如保存数据、确认是否退出等。
- **`OnClosing` 方法**:当窗口即将关闭时,`OnClosing` 方法会被调用。如果你希望在窗口关闭前进行一些操作(比如询问用户是否保存更改),可以在此方法中实现。如果你希望取消窗口的关闭操作,可以设置 `e.Cancel = true` 来阻止关闭。
**示例:**
```csharp
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
// 在窗口关闭前执行一些操作
MessageBox.Show("Window is Closing");
// 可以通过设置 e.Cancel = true 来取消窗口关闭
// e.Cancel = true;
}
```
- **`Closing` 事件**`OnClosing` 触发前会触发 `Closing` 事件。此时,窗口正在关闭,资源释放操作还未完成。
**示例:**
```csharp
private void Window_Closing(object sender, CancelEventArgs e)
{
MessageBox.Show("Window Closing");
}
```
#### 6. **销毁阶段**
窗口销毁时,所有的资源都会被释放,窗口将完全从内存中清除。窗口的销毁通常发生在窗口关闭后,资源被释放并且窗口对象将被垃圾回收。
- **`OnClosed` 方法**:当窗口完全关闭时,`OnClosed` 方法会被调用。此时,可以执行一些清理工作,比如释放资源、注销事件等。
**示例:**
```csharp
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// 窗口关闭后的清理工作
MessageBox.Show("Window Closed");
}
```
- **`Closed` 事件**:当窗口完全关闭后,`Closed` 事件会被触发。此时窗口已经销毁,资源已经释放。
**示例:**
```csharp
private void Window_Closed(object sender, EventArgs e)
{
MessageBox.Show("Window Closed");
}
```
> ###
>
> | 阶段 | 方法或事件 | 作用 |
> | ---------- | -------------------------------- | ---------------------------------------- |
> | **创建** | `Window()` 构造函数 | 窗口的实例化,初始化控件和资源 |
> | **初始化** | `OnInitialized` | 窗口的控件已创建,但未进行布局和显示 |
> | **加载** | `OnLoaded` | 控件和窗口显示并完成布局 |
> | **激活** | `Activated` 事件 | 窗口获得输入焦点并成为前景窗口 |
> | **关闭** | `OnClosing` 方法,`Closing` 事件 | 窗口即将关闭,可以进行数据保存或确认操作 |
> | **销毁** | `OnClosed` 方法,`Closed` 事件 | 清理资源,窗口销毁,释放内存 |
>
> - **`OnLoaded`**:窗口完全加载并显示后调用。
> - **`Activated`**:窗口获得焦点并被激活时触发。
> - **`OnClosing`**:窗口即将关闭时调用。
> - **`OnClosed`**:窗口完全关闭后调用。