WPF的UI更新方式

2011年6月22日星期三

WPF的UI更新方式

緣由

在以往的 VB6,或者是 Windows Form 應用程式中,更新 UI的方式極為簡單,往往只是 Application.DoEvents 就可以更新。Windows Form 中更有 Invoke 與 BeginInvoke 可以彈性地使用。

那在 WPF 中,要如何更新 UI 的內容呢?

範例1:Bad Example

當然,要從一個不正確的範例開始。

Ex1Bad.xaml

?
1
2
3
4
5
6
7
8
9
< Window x:Class = "WpfApplication10.Ex1Bad"
         xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
         Title = "Ex1Bad" Height = "300" Width = "300" >
   < StackPanel >
     < Label Name = "lblMsg" Content = "Nothing" />
     < Button Content = "Start" Name = "btnStart" Click = "btnStart_Click" />
   </ StackPanel >
</ Window >

Ex1Bad.xaml.cs

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System.Threading;
using System.Windows;
 
namespace WpfApplication10
{
   public partial class Ex1Bad : Window
   {
     public Ex1Bad()
     {
       InitializeComponent();
     }
 
     private void btnStart_Click( object sender, RoutedEventArgs e)
     {
       lblMsg.Content = "Starting..." ;
       Thread.Sleep(3000); //執行長時間工作
       lblMsg.Content = "Doing..." ;
       Thread.Sleep(3000); //執行長時間工作
       lblMsg.Content = "Finished..." ;
     }
   }
}

這裡以 Thread.Sleep(3000)讓 UI Thread 睡個3秒鐘,來模擬長時間的工作。

這是個常見的程式碼,但卻是沒有用。在 Windows Form 的 API 中有 Application.DoEvents() 可以呼叫。WPF 中沒有類似的嗎?

範例2:使用Windows Form的 DoEvents

原來,WPF 中雖然沒有類似的 api 可以呼叫,但仍可以直接呼叫 Windows Form 的 Application.DoEvents.當然,需要引用 System.Windows.Forms.dll。

Ex2WinformDoEvents.xaml

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System.Threading;
using System.Windows;
using swf = System.Windows.Forms;
 
namespace WpfApplication10
{
   public partial class Ex2WinformDoEvents : Window
   {
     public Ex2WinformDoEvents()
     {
       InitializeComponent();
     }
 
     private void btnStart_Click( object sender, RoutedEventArgs e)
     {
       lblMsg.Content = "Starting..." ;
       swf.Application.DoEvents();
       Thread.Sleep(3000); //執行長時間工作
       lblMsg.Content = "Doing..." ;
       swf.Application.DoEvents();
       Thread.Sleep(3000); //執行長時間工作
       lblMsg.Content = "Finished..." ;
     }
   }
}

在更新UI後,呼叫 swf.Application.DoEvents(),就可以更新 UI 了。這樣的方式與之前的 VB6是一模一樣的手法。

範例3:WPF DoEvents

哦?WPF 沒有 DoEvents 可以使用,只能呼叫老前輩Windows Form 的 API 嗎?也不是。在 DispacherFrame 文章中就有sample.

Ex3WPFDoEvents.xaml.cs

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using System;
using System.Security.Permissions;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
 
namespace WpfApplication10
{
   public partial class Ex3WPFDoEvents : Window
   {
     public Ex3WPFDoEvents()
     {
       InitializeComponent();
     }
 
     private void btnStart_Click( object sender, RoutedEventArgs e)
     {
       lblMsg.Content = "Starting..." ;
       DoEvents();
       Thread.Sleep(3000); //執行長時間工作
       lblMsg.Content = "Doing..." ;
       DoEvents();
       Thread.Sleep(3000); //執行長時間工作
       lblMsg.Content = "Finished..." ;
     }
 
     [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
     public void DoEvents()
     {
       var frame = new DispatcherFrame();
       Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
           new DispatcherOperationCallback(ExitFrame), frame);
       Dispatcher.PushFrame(frame);
     }
 
     public object ExitFrame( object f)
     {
       ((DispatcherFrame)f).Continue = false ;
 
       return null ;
     }
 
     public static void DoEvents2()
     {
       Action action = delegate { };
       Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Input, action);
     }
   }
}

DoEvents() 與 DoEvents2() 的效果相同。

DoEvents is Evil

DoEvents 這麼好用,為什麼WPF還要發明 Dispatcher,或 Windows Form 的 BeginInvoke 這些 API  呢?

跑以下的程式碼看看吧。

?
1
2
3
4
5
6
7
8
private void btnEvil_Click( object sender, RoutedEventArgs e)
{
   for ( int i = 0; i < 10000000; i++)
   {
     System.Windows.Forms.Application.DoEvents();
   }
   MessageBox.Show( "Ok" );
}

執行時,記得打開工作管理員看看CPU 的負載,會持續飆高一斷時間。雖然 UI 沒有任何的更新,為何 CPU 會飆高呢?

DoEvent 的原理是execution loop,也就是以迴圈的方式來查詢是否有要更新的訊息(message)。一看到迴圈,各位看倌就知道是怎麼回事了吧。

範例3中的WPF 的 DoEvents 也是相同的道理。

範例4:BackgroundWorker

有沒有較正常的方式來更新 UI 呢?看一下Ex1Bad.xaml.cs的設計方式吧。更新lblMessage後執行一段工作,這基本上是同步的寫作方式。在 UI Thread 上執行工作,本來就會使得 UI 停頓,使用者感到不方變。

正確的方式,是使用BackgroundWorker來執行長時間的工作,並以非同步的方式更新在 UI Tread 上的UI內容。

Ex4BackgroundWorker.xaml.cs

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
 
namespace WpfApplication10
{
   public partial class Ex4BackgroundWorker : Window
   {
     public Ex4BackgroundWorker()
     {
       InitializeComponent();
     }
 
     private void btnStart_Click( object sender, RoutedEventArgs e)
     {
       ExecuteLongTimeWork(lblMsg, "Starting" );
       ExecuteLongTimeWork(lblMsg, "Doing" );
       ExecuteLongTimeWork(lblMsg, "Finished" );
     }
 
     private void ExecuteLongTimeWork(Label label, string message)
     {
       var backgroundWorker = new BackgroundWorker();
       backgroundWorker.DoWork += (s, o) => {
         Thread.Sleep(3000); //執行長時間工作
       };
 
       backgroundWorker.RunWorkerCompleted += (s, args) =>
                                                {
                                                  Dispatcher.BeginInvoke( new Action(() =>
                                                  {
                                                    label.Content = message;
                                                  }));
                                                };
       backgroundWorker.RunWorkerAsync();
     }
   }
}

BackgroundWorker 工作方式,是建立一個新的 Thread 來執行 DoWork 的event handler,執行完畢後,再執行 RunWorkerCompleted 的event handler。因此,我們需要的是在RunWorkerCompleted 的event handler 中更新的 UI。

更新 UI 時,又使用 Dispatcher 來更新 UI。基本上,Dispatcher 是一個 Queue 的概念,凡是使用 Dispatcher 來更新 UI,WPF 會照 DispatcherPriority 的優先順序來更新 UI。

結論

雖然只是小小的UI 更新方式,也是有不少的學問呢!

<think>首先,用户的问题是:“我想查找WPF控件更新UI的方法或相关知识 请问WPF控件更新UI的方法”。用户专门询问了WPF控件更新UI的方法。 从系统指令看: - 我必须使用中文回答。 - 行内数学表达式用$...$,但这里可能不涉及数学,所以可能不需要。 - 独立公式用$$...$$,但同样可能不适用。 - LaTeX语法正确,但在这个上下文中,主要是文本。 - 生成相关问题。 - 回答中引用的段落末尾添加引用标识,如[^1]。 - 回答结构清晰,逐步解决问题。 wakes up - 用户提供了几个引用: - 引用[1]:关于Panuon.WPF.UI组件库。 - 引用[2]:介绍了MaterialDesignInXamlToolkit和WPF UI框架。 - 引用[3]:关于WPF的控件和布局容器。 - 引用[4]:如何添加新控件使用ui:前缀。 用户说:“上述引用仅供参考,并不是用户真正的需求”,所以我不能直接依赖这些引用作为用户需求。用户真正的需求是“WPF控件更新UI的方法”,所以我需要专注于回答这个问题。 关键点:用户上一次的问题可能是相关的,但在这个消息中,用户直接给出了问题:“我想查找WPF控件更新UI的方法或相关知识 请问WPF控件更新UI的方法”。所以,我应该直接回答这个。 在WPF中,更新UI通常涉及数据绑定、事件处理、线程安全等概念。核心是确保UI更新在主线程上执行,因为WPF是单线程模型。 常见方法: 1. **使用数据绑定和INotifyPropertyChanged**:这是推荐方式。通过实现INotifyPropertyChanged接口,当属性改变时自动更新UI。 2. **使用Dispatcher**:当在非UI线程(如后台线程)更新UI时,必须使用Dispatcher来确保代码在UI线程上运行。 3. **使用事件和命令**:如Button的Click事件或ICommand实现。 4. **直接操作控件**:但不推荐,因为它可能导致线程问题。 基于引用: - 引用[3]提到“WPFUI进阶--完整了解wpf的控件和布局容器及应用”,这可能涉及布局和渲染,但用户是问“更新UI的方法”。 - 其他引用是关于UI库的,如MaterialDesignInXamlToolkit和WPF UI,它们可能提供自己的更新机制,但核心还是WPF基础。 - 引用[4]展示了如何添加控件使用xmlns:ui,但这只是添加控件,不是更新。 所以,我应该基于WPF核心概念回答。 结构化回答: 1. 介绍WPF UI更新的基本概念。 2. 解释数据绑定方法。 3. 讨论线程问题和Dispatcher。 4. 提供代码示例。 5. 生成相关问题。 参考引用: - 在回答中,如果引用提供的信息,末尾添加引用标识。 - 例如,引用[2]提到WPF UI框架,如果相关,可以引用。 可能的引用点: - 引用[2]讨论了WPF UI框架,它可能简化UI更新,但核心还是WPF。 - 引用[3]可能涉及控件布局,但用户是问“更新”方法。 最好聚焦于核心WPF。 步骤: 1. **解释为什么需要更新UI**:在后台线程工作时,UI线程必须被安全更新。 2. **推荐方法:数据绑定** - 实现INotifyPropertyChanged。 - 使用ObservableCollection for collections。 3. **使用Dispatcher** - 在后台线程中调用Dispatcher.Invoke或Dispatcher.BeginInvoke。 4. **其他方式**:如使用事件。 5. **代码示例**:用C#展示简单例子。 6. **生成相关问题**。 确保回答真实可靠:基于WPF知识。 最终回答结构: - 开头:直接回答问题。 - 主体:逐步解释方法。 - 结尾:生成相关问题。 </think> ### WPF控件更新UI的方法 在WPFWindows Presentation Foundation)中,更新UI控件是开发中的常见需求,尤其是在处理后台数据变化或用户交互时。WPF采用单线程模型(UI线程),因此更新UI必须确保线程安全,避免跨线程访问控件导致的异常。以下是几种核心方法和最佳实践,我会逐步解释并提供代码示例。 #### 1. **使用数据绑定和INotifyPropertyChanged(推荐方法)** WPF的数据绑定机制是更新UI的首选方式。它通过实现`INotifyPropertyChanged`接口,使数据源(如ViewModel)变化时自动通知UI更新。 - **步骤**: - 在数据源类中实现`INotifyPropertyChanged`。 - 在属性设置器中触发`PropertyChanged`事件。 - 在XAML中使用`Binding`将控件属性绑定到数据源。 - **优点**:解耦UI和逻辑,支持自动更新,代码简洁。 - **示例代码**: ```csharp using System.ComponentModel; using System.Runtime.CompilerServices; public class UserViewModel : INotifyPropertyChanged { private string _name; public string Name { get { return _name; } set { _name = value; OnPropertyChanged(); // 触发UI更新 } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } // 在XAML中绑定(例如TextBox) <TextBox Text="{Binding Name}" /> ``` 当`Name`属性变化时,TextBox的UI会自动更新。 #### 2. **使用Dispatcher处理线程安全更新** 如果更新发生在非UI线程(如后台任务),必须使用`Dispatcher`将操作调度到UI线程,否则会引发`InvalidOperationException`。 - **步骤**: - 获取当前控件的`Dispatcher`对象。 - 使用`Dispatcher.Invoke`(同步)或`Dispatcher.BeginInvoke`(异步)执行更新代码。 - **适用场景**:从异步操作(如Task或BackgroundWorker)更新UI。 - **示例代码**: ```csharp // 在后台线程中更新UI控件(例如更新TextBlock) Task.Run(() => { // 模拟后台工作 string newData = "更新后的文本"; // 使用Dispatcher确保线程安全 Application.Current.Dispatcher.Invoke(() => { myTextBlock.Text = newData; // myTextBlock是XAML中的控件名 }); }); ``` #### 3. **使用ObservableCollection更新集合数据** 对于列表控件(如ListBox或DataGrid),使用`ObservableCollection<T>`代替普通集合。它实现了`INotifyCollectionChanged`,当集合变化时自动更新UI。 - **步骤**: - 将数据源定义为`ObservableCollection<T>`。 - 在XAML中绑定到ItemsControl。 - **示例代码**: ```csharp using System.Collections.ObjectModel; public ObservableCollection<string> Items { get; } = new ObservableCollection<string>(); // 添加或移除项时,UI自动更新 Items.Add("新项目"); ``` ```xaml <ListBox ItemsSource="{Binding Items}" /> ``` #### 4. **直接操作控件(慎用)** 在简单场景下,可以直接设置控件属性(如`textBox.Text = "新值"`),但仅限于UI线程中执行。不推荐用于复杂逻辑,因为它可能导致维护困难。 - **注意事项**:确保在UI线程操作,否则使用Dispatcher包装。 #### 5. **其他高级方法** - **事件和命令**:通过控件事件(如Button.Click)或ICommand实现更新。例如,使用MVVM框架(如Prism)的`DelegateCommand`。 - **动画和Storyboard**:对于动态UI效果,使用Storyboard触发属性改变。这在引用[3]中提到WPF的进阶UI技巧[^3]。 - **第三方UI库**:如MaterialDesignInXamlToolkit或WPF UI(引用[2]),它们提供预定义样式和控件,简化更新逻辑,但底层原理相同[^2]。例如,使用WPF UI库时,添加控件后通过数据绑定更新。 #### 最佳实践总结 - **优先数据绑定**:90%的场景使用`INotifyPropertyChanged`和`ObservableCollection`。 - **线程安全第一**:后台操作务必用Dispatcher。 - **避免频繁更新**:批量修改减少UI渲染开销。 - **测试和调试**:使用Visual Studio的Live Visual Tree工具检查绑定错误。 如果您在特定UI库(如Panuon.WPF.UI或MaterialDesign)中工作,核心更新方法不变,但库可能提供额外辅助类(如引用[4]的ui:前缀用于控件添加)[^4]。 ### 相关问题 1. 如何在WPF中使用MVVM模式实现数据绑定更新UI? 2. WPF中Dispatcher的作用是什么?如何处理跨线程UI更新? 3. ObservableCollection和List的区别是什么?为什么在WPF中推荐使用它? 4. 如何调试WPF数据绑定失败的问题? 5. 第三方WPF UI库(如MaterialDesign)如何简化UI更新流程?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值