WPF的UI更新方式

转载 2012年03月23日 16:47:49

WPF的UI更新方式

緣由

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

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

範例1:Bad Example

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

Ex1Bad.xaml

<Windowx:Class="WpfApplication10.Ex1Bad"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Ex1Bad"Height="300"Width="300">
  <StackPanel>
    <LabelName="lblMsg"Content="Nothing"/>
    <ButtonContent="Start"Name="btnStart"Click="btnStart_Click"/>
  </StackPanel>
</Window>

Ex1Bad.xaml.cs
usingSystem.Threading;
usingSystem.Windows;
 
namespaceWpfApplication10
{
  publicpartial class Ex1Bad : Window
  {
    publicEx1Bad()
    {
      InitializeComponent();
    }
 
    privatevoid btnStart_Click(objectsender, 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

usingSystem.Threading;
usingSystem.Windows;
usingswf = System.Windows.Forms;
 
namespaceWpfApplication10
{
  publicpartial class Ex2WinformDoEvents : Window
  {
    publicEx2WinformDoEvents()
    {
      InitializeComponent();
    }
 
    privatevoid btnStart_Click(objectsender, 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

usingSystem;
usingSystem.Security.Permissions;
usingSystem.Threading;
usingSystem.Windows;
usingSystem.Windows.Threading;
 
namespaceWpfApplication10
{
  publicpartial class Ex3WPFDoEvents : Window
  {
    publicEx3WPFDoEvents()
    {
      InitializeComponent();
    }
 
    privatevoid btnStart_Click(objectsender, 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)]
    publicvoid DoEvents()
    {
      var frame = newDispatcherFrame();
      Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
          newDispatcherOperationCallback(ExitFrame), frame);
      Dispatcher.PushFrame(frame);
    }
 
    publicobject ExitFrame(objectf)
    {
      ((DispatcherFrame)f).Continue = false;
 
      returnnull;
    }
 
    publicstatic void DoEvents2()
    {
      Action action = delegate{ };
      Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Input, action);
    }
  }
}

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

DoEvents is Evil

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

跑以下的程式碼看看吧。

privatevoid btnEvil_Click(objectsender, RoutedEventArgs e)
{
  for(inti = 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

usingSystem.ComponentModel;
usingSystem.Threading;
usingSystem.Windows;
usingSystem.Windows.Controls;
 
namespaceWpfApplication10
{
  publicpartial class Ex4BackgroundWorker : Window
  {
    publicEx4BackgroundWorker()
    {
      InitializeComponent();
    }
 
    privatevoid btnStart_Click(objectsender, RoutedEventArgs e)
    {
      ExecuteLongTimeWork(lblMsg,"Starting");
      ExecuteLongTimeWork(lblMsg,"Doing");
      ExecuteLongTimeWork(lblMsg,"Finished");
    }
 
    privatevoid ExecuteLongTimeWork(Label label, stringmessage)
    {
      var backgroundWorker = newBackgroundWorker();
      backgroundWorker.DoWork += (s, o) => {
        Thread.Sleep(3000);//執行長時間工作
      };
 
      backgroundWorker.RunWorkerCompleted += (s, args) =>
                                               {
                                                 Dispatcher.BeginInvoke(newAction(() =>
                                                 {
                                                   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 更新方式,也是有不少的學問呢!

分享一个漂亮WPF界面框架创作过程及其源码

本文会作为一个系列,分为以下部分来介绍: (1)见识一下这个界面框架; (2)界面框架如何进行开发; (3)辅助开发支持:Demo、模板、VsPackage制作。 框架源码如下所示。 ...

分享一款Metro风格的WPFUI

直接上效果图: 源码下载:点击打开链接
  • ljf5566
  • ljf5566
  • 2014年11月29日 19:09
  • 5205

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

9款WPF用户界面控件对比评测

WPF(Windows Presentation Foundation)是微软推出的基于Vista的用户界面框架,它提供了统一的编程模型、语言和框架,真正做到了分离界面设计人员与开发人员的工作;同时它...

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

WPF UI布局之概述

在线演示:http://v.youku.com/v_show/id_XNzA5NDk2Mjcy.html 清晰版视频+代码下载:http://115.com/lb/5lbeer0m9lad 一、简介...
  • gjysk
  • gjysk
  • 2014年05月11日 22:39
  • 3644

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

WPF实现DoEvents

static void DoEvents() { DispatcherFrame frame = new DispatcherFrame(true); ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:WPF的UI更新方式
举报原因:
原因补充:

(最多只允许输入30个字)