852 lines
33 KiB
Markdown
852 lines
33 KiB
Markdown
# 重要说明
|
||
|
||
## `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`**:窗口完全关闭后调用。
|
||
|
||
## WPF绑定模式
|
||
|
||
在 WPF(Windows Presentation Foundation)中,**数据绑定**(Data Binding)是一项核心功能,它允许将用户界面(UI)控件与数据源之间建立连接,从而使得 UI 和数据之间能够自动同步。WPF 提供了多种**绑定模式**(Binding Modes),每种模式都有其特定的行为和用途,用于处理数据从视图到视图模型(或模型)的更新、同步等。
|
||
|
||
### WPF 中的绑定模式
|
||
|
||
WPF 支持四种主要的绑定模式(Binding Modes):
|
||
|
||
1. **OneWay(单向绑定)**
|
||
2. **TwoWay(双向绑定)**
|
||
3. **OneWayToSource(单向绑定到源)**
|
||
4. **OneTime(一次性绑定)**
|
||
|
||
#### 1. **OneWay(单向绑定)**
|
||
|
||
**定义**:单向绑定模式将数据源的变化反映到 UI 元素中,即数据从数据源到视图(UI)。数据从源控件(如模型、视图模型)流向目标控件(如文本框、标签等)。
|
||
|
||
**特点**:
|
||
|
||
- 数据流动方向是从数据源到目标控件。
|
||
- UI 元素的变化不会影响数据源。
|
||
- 常用于显示数据,不需要用户交互修改数据。
|
||
|
||
**示例**:绑定一个 `Label` 的 `Content` 属性到一个模型的属性。
|
||
|
||
```xml
|
||
<TextBlock Text="{Binding Path=Name}" />
|
||
public class Person
|
||
{
|
||
public string Name { get; set; }
|
||
}
|
||
|
||
// 在视图模型中绑定:
|
||
public class MainViewModel
|
||
{
|
||
public Person Person { get; set; } = new Person { Name = "John Doe" };
|
||
}
|
||
```
|
||
|
||
#### 2. **TwoWay(双向绑定)**
|
||
|
||
**定义**:双向绑定允许数据在两个方向流动,既可以从源到目标,也可以从目标到源。当用户更改 UI 元素的值时,数据源会自动更新;同样,当数据源发生变化时,UI 元素会同步更新。
|
||
|
||
**特点**:
|
||
|
||
- 数据流动是双向的(源 -> 目标 和 目标 -> 源)。
|
||
- 常用于用户输入的场景,如文本框输入、下拉框选择等。
|
||
- 适用于需要用户交互且 UI 需要实时反映的数据。
|
||
|
||
**示例**:绑定一个 `TextBox` 的 `Text` 属性到视图模型中的属性。
|
||
|
||
```xml
|
||
<TextBox Text="{Binding Path=Name, Mode=TwoWay}" />
|
||
public class Person
|
||
{
|
||
public string Name { get; set; }
|
||
}
|
||
|
||
// 在视图模型中绑定:
|
||
public class MainViewModel
|
||
{
|
||
public Person Person { get; set; } = new Person { Name = "John Doe" };
|
||
}
|
||
```
|
||
|
||
如果用户在 `TextBox` 中输入内容,`Person.Name` 会自动更新为输入的值。
|
||
|
||
#### 3. **OneWayToSource(单向绑定到源)**
|
||
|
||
**定义**:这种绑定模式与 `OneWay` 类似,但数据流动的方向相反。它将 UI 元素的变化反映到数据源,即从目标控件(如文本框、按钮等)到数据源。
|
||
|
||
**特点**:
|
||
|
||
- 数据流动方向是从目标到数据源。
|
||
- 适用于一些需要监听 UI 元素变化并更新数据源的场景,但不要求数据源的变化影响到 UI 元素。
|
||
- 一般用于用户输入的情况,数据源的更改不需要直接反映到 UI 上。
|
||
|
||
**示例**:将 `TextBox` 的 `Text` 属性绑定到数据源,并将用户输入的值更新到数据源中。
|
||
|
||
```xml
|
||
<TextBox Text="{Binding Path=Name, Mode=OneWayToSource}" />
|
||
public class Person
|
||
{
|
||
public string Name { get; set; }
|
||
}
|
||
|
||
// 在视图模型中绑定:
|
||
public class MainViewModel
|
||
{
|
||
public Person Person { get; set; } = new Person { Name = "John Doe" };
|
||
}
|
||
```
|
||
|
||
当用户在 `TextBox` 中输入内容时,`Person.Name` 会自动更新,但不会反过来更新 `TextBox`。
|
||
|
||
#### 4. **OneTime(一次性绑定)**
|
||
|
||
**定义**:一次性绑定模式将数据源的值传递给目标控件,但不会再进行同步更新。数据只在初始时绑定一次,之后不会跟随数据源的变化而更新。
|
||
|
||
**特点**:
|
||
|
||
- 数据仅在第一次绑定时传递到目标控件。
|
||
- 适用于不需要随数据源变化自动更新 UI 的场景。
|
||
- 适用于静态数据,如初始化时显示的值。
|
||
|
||
**示例**:在应用启动时绑定初始数据。
|
||
|
||
```xml
|
||
<TextBlock Text="{Binding Path=Name, Mode=OneTime}" />
|
||
public class Person
|
||
{
|
||
public string Name { get; set; }
|
||
}
|
||
|
||
// 在视图模型中绑定:
|
||
public class MainViewModel
|
||
{
|
||
public Person Person { get; set; } = new Person { Name = "John Doe" };
|
||
}
|
||
```
|
||
|
||
`TextBlock` 的 `Text` 属性会在应用启动时绑定一次,但在之后的任何数据变化都不会反映到 UI 上。
|
||
|
||
### 选择合适的绑定模式
|
||
|
||
选择绑定模式时需要根据实际需求来决定。以下是不同情况适用的模式:
|
||
|
||
1. **`OneWay`**:
|
||
- 用于显示数据,UI 不需要编辑或修改数据。
|
||
- 适用于只读的文本展示等场景。
|
||
2. **`TwoWay`**:
|
||
- 用于需要用户输入并与数据源实时同步的场景。
|
||
- 适用于输入框、下拉框等用户可编辑控件。
|
||
3. **`OneWayToSource`**:
|
||
- 用于需要监听 UI 元素的变化并更新数据源的场景,且不需要将数据源的变化反映回 UI。
|
||
- 适用于需要提交用户输入但不需要实时更新 UI 的场景。
|
||
4. **`OneTime`**:
|
||
- 用于初始化数据,仅绑定一次,之后不再更新。
|
||
- 适用于静态数据或加载一次后不再变化的场景。
|
||
|
||
### 绑定模式的代码示例
|
||
|
||
以下是完整的 `TwoWay` 绑定的代码示例:
|
||
|
||
```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="Binding Example" Height="200" Width="300">
|
||
<Grid>
|
||
<TextBox Text="{Binding Name, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center" Width="200" />
|
||
</Grid>
|
||
</Window>
|
||
public class MainWindowViewModel
|
||
{
|
||
public string Name { get; set; } = "John Doe";
|
||
}
|
||
|
||
public partial class MainWindow : Window
|
||
{
|
||
public MainWindow()
|
||
{
|
||
InitializeComponent();
|
||
DataContext = new MainWindowViewModel();
|
||
}
|
||
}
|
||
```
|
||
|
||
在这个示例中,`TextBox` 的 `Text` 属性绑定到了视图模型中的 `Name` 属性,且使用 `TwoWay` 模式,意味着如果用户修改 `TextBox` 中的内容,`Name` 属性会实时更新,反之,`Name` 的变化会反映在 `TextBox` 中。
|
||
|
||
在 WPF 中,**多绑定**(Multiple Bindings)是指将一个目标控件的属性绑定到多个数据源上,或使用多个绑定来决定控件的显示内容。通常情况下,我们可以使用 `MultiBinding` 和 `IMultiValueConverter` 来实现多绑定。
|
||
|
||
### **基础多绑定示例**
|
||
|
||
`MultiBinding` 允许你将多个数据源绑定到一个目标控件上,并通过一个 `IMultiValueConverter` 将多个数据源的值转换为一个值,从而决定目标控件的最终显示。
|
||
|
||
假设我们有两个文本框,用户分别输入名字和姓氏,我们想在一个 `TextBlock` 中显示完整的姓名。我们可以使用 `MultiBinding` 来实现。
|
||
|
||
#### 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"
|
||
xmlns:local="clr-namespace:WpfApp"
|
||
Title="MultiBinding Example" Height="200" Width="300">
|
||
|
||
<Window.Resources>
|
||
<!-- 定义一个 MultiValueConverter 用于合并名字和姓氏 -->
|
||
<local:FullNameConverter x:Key="FullNameConverter" />
|
||
</Window.Resources>
|
||
|
||
<Grid>
|
||
<TextBox x:Name="FirstNameTextBox" HorizontalAlignment="Center" VerticalAlignment="Top" Width="200" Margin="0,20,0,0" />
|
||
<TextBox x:Name="LastNameTextBox" HorizontalAlignment="Center" VerticalAlignment="Top" Width="200" Margin="0,60,0,0" />
|
||
|
||
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,100,0,0">
|
||
<!-- 使用 MultiBinding 来绑定两个 TextBox 的值 -->
|
||
<TextBlock.Text>
|
||
<MultiBinding Converter="{StaticResource FullNameConverter}">
|
||
<Binding Path="Text" ElementName="FirstNameTextBox"/>
|
||
<Binding Path="Text" ElementName="LastNameTextBox"/>
|
||
</MultiBinding>
|
||
</TextBlock.Text>
|
||
</TextBlock>
|
||
</Grid>
|
||
</Window>
|
||
```
|
||
|
||
> 1. `MultiBinding` 允许你将多个绑定连接到同一个目标控件(这里是 `TextBlock.Text`)。
|
||
> 2. 绑定的源控件是 `FirstNameTextBox` 和 `LastNameTextBox`。
|
||
> 3. 使用 `IMultiValueConverter`(在这个例子中是 `FullNameConverter`)将多个绑定的值(名字和姓氏)转换为一个最终值并显示在 `TextBlock` 中。
|
||
|
||
### `MultiBinding` 进阶
|
||
|
||
`MultiBinding` 可以用于处理更复杂的场景,比如通过多个条件来决定控件的属性值。例如,依据多个条件显示不同的文本或颜色。
|
||
|
||
#### 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"
|
||
xmlns:local="clr-namespace:WpfApp"
|
||
Title="MultiBinding Example" Height="200" Width="300">
|
||
|
||
<Window.Resources>
|
||
<!-- 定义一个 MultiValueConverter 用于根据多个条件选择文本颜色 -->
|
||
<local:TextColorConverter x:Key="TextColorConverter" />
|
||
</Window.Resources>
|
||
|
||
<Grid>
|
||
<CheckBox x:Name="IsActiveCheckBox" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="20,20,0,0" Content="Is Active?" />
|
||
<CheckBox x:Name="HasPermissionCheckBox" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="20,60,0,0" Content="Has Permission?" />
|
||
|
||
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,100,0,0" FontSize="16">
|
||
<!-- 使用 MultiBinding 和 Converter 设置 TextBlock 的颜色 -->
|
||
<TextBlock.Foreground>
|
||
<MultiBinding Converter="{StaticResource TextColorConverter}">
|
||
<Binding Path="IsChecked" ElementName="IsActiveCheckBox" />
|
||
<Binding Path="IsChecked" ElementName="HasPermissionCheckBox" />
|
||
</MultiBinding>
|
||
</TextBlock.Foreground>
|
||
Text Block Color Based on Conditions
|
||
</TextBlock>
|
||
</Grid>
|
||
</Window>
|
||
```
|
||
|
||
#### `TextColorConverter` 实现:
|
||
|
||
```csharp
|
||
using System;
|
||
using System.Globalization;
|
||
using System.Windows.Data;
|
||
using System.Windows.Media;
|
||
|
||
namespace WpfApp
|
||
{
|
||
public class TextColorConverter : IMultiValueConverter
|
||
{
|
||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||
{
|
||
bool isActive = (bool)(values[0] ?? false);
|
||
bool hasPermission = (bool)(values[1] ?? false);
|
||
|
||
if (isActive && hasPermission)
|
||
{
|
||
return new SolidColorBrush(Colors.Green); // 如果两个条件都满足,显示绿色
|
||
}
|
||
return new SolidColorBrush(Colors.Red); // 否则显示红色
|
||
}
|
||
|
||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||
{
|
||
return null; // 不需要双向绑定
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
> - 在这个例子中,`TextBlock.Foreground` 属性的颜色依赖于两个复选框的状态(`IsActiveCheckBox` 和 `HasPermissionCheckBox`)。
|
||
>
|
||
> - ```
|
||
> TextColorConverter
|
||
> ```
|
||
>
|
||
> 判断两个条件:
|
||
>
|
||
> - 如果两个复选框都被选中,`TextBlock` 的颜色是绿色。
|
||
> - 否则,颜色为红色。
|
||
>
|
||
> 1. **绑定多个属性**:通过 `MultiBinding` 可以同时绑定多个数据源到一个目标控件的属性。在数据源变化时,`IMultiValueConverter` 将负责处理多个源的逻辑,返回最终的结果。
|
||
> 2. **双向绑定**:虽然 `MultiBinding` 主要用于单向绑定(UI -> 数据源),如果需要双向绑定,可以通过设置 `Mode=TwoWay` 来实现。但一般来说,`MultiBinding` 多用于显示和数据转换,而不是双向交互。
|
||
> 3. **性能**:在进行多绑定时,尤其是复杂的转换时,需要注意性能影响。如果绑定源的数量或更新频率较高,可能会影响应用的性能。
|
||
|