使用WPF编写界面程序,遇到的和委托相关问题,求解答。

使用WPF编写界面程序,遇到的和委托相关问题,求解答。,第1张

在多线程编程中,我们经常会需要在子线程中访问主线程的内容,特别是更改主窗体的UI界面内容,如果直接访问跨线程资源,系统就会报线程访问错误.查了MSDN发现由于这样的做法是线程不安全的。在WinForm中窗体类提供了Invoke方法以从子线程中访问主线程资源.在WPF中Window类并没有Invoke方法,但是WPF提供了专门负责线程调度工作的Window.Dispatcher类,每个线程都有一个,我们在一个线程中要让另一个线程做事情,其实就是调用目标Dispatcher调度完成.

Dispatcher

WPF规定了(事实上在.net2.0中便已规定了)UI元素只能由创建该元素的线程来访问。比如我们从新开的一个线程中访问主界面中的元素会出现运行时的异常。Dispatcher来维持着这一规定,并组织着消息循环。Dispatcher负责检测访问对象的线程与对象创建线程是否一致,不一致则抛出异常。值得一提的是,Dispatcher的消息循环中的Work Item是有优先级的,这可以让高优先级的项能有更多的工作时间。比如界面绘制比处理用户输入的优先级要高,这使得界面动画更加流畅。这也就是为什么,我们在调用Dispatcher.Invoke ( DispatcherPriority,…握肢戚) 与Dispatcher. BeginInvoke (DispatcherPriority,…)要传入一个优先级参数的原因。下面是对各个优先级的说明:

优先级

说明

Inactive

工作项目已排队但未处理。

SystemIdle

仅当系统空闲时才将工作项目调度到 UI 线程。这是实际得到处理的项目的最低优先级。

ApplicationIdle

仅当应用程序本身空闲时才将工作项目调度到 UI 线程。

ContextIdle

仅在优先级更高的工作项目得到处理后才将工作项目调度到 UI 线程。

Background

在所有布局、呈现和输入项目都得到处理后才将工作项目调度到 UI 线程。

Input

以与用户输入相同的优先级将工作项目调度到 UI 线程。

Loaded

在所有布局和呈现都完成后才将工作项目调度到 UI 线程。

Render

以与呈现引擎相同的优先级将工作项目调度到 UI 线程。

DataBind

以与数据绑定相同的优先级将工作项目调度到 UI 线程。

Normal

以正常优先级将工作项目调度到 UI 线程。这是调度大多数应用程序工作饥冲项目时的优先级。

Send

以最高优先级将工作项目调度到 UI 线程。

上面提到了Dispatcher维持着一个规矩“只有创建该对象的线程可以访问该对象”。这里的对象不仅仅是指一些UI控件(比如Button),而是所以的派生于DispatcherObject类的对象。

对于阻塞的 *** 作,不一定需要开启新线程

当我们遇到某个费时的 *** 作是,第一反映往往是开启一个新线程,然后在后台去处理它,以便不阻塞我们的用户界面。当然,这是正确的想法。当并段陵不是所有的都需如此。仔细想想界面阻塞的原因,我们知道其是由于时间被消耗在某个费时的Work Item上了,而那些处理用户界面的Work Item还在那里苦苦等候。So,我们只要别让他们苦苦等候就可以了,只要用户有界面 *** 作我们就处理,线程上的其他空闲时间来处理我们的复杂 *** 作。我们将复杂的费时的 *** 作细化成很多不费时的小 *** 作,在这些小 *** 作之间的空隙处我们来处理相应用户界面的 *** 作

阻塞的情况如下, MouseUp与MouseLeave会被阻塞:

(MouseDown)->(费时的,复杂 *** 作)->(MouseUp)->(MouseLeave)…

细化后的情况如下,MouseUp与MouseLeave不会被阻塞:

(MouseDown)->(不费时的,小 *** 作,复杂 *** 作的1/n)->(MouseUp)->(不费时的,小 *** 作,复杂 *** 作的1/n) ->(MouseLeave)…

举一个简单的例子,假定我们的主界面上要显示一个数字,其为Window1的CurrentNum属性,我们已经将界面上的某个TextBlock与其绑定了起来:

<TextBlock x:Name="textBlock_ShowNum"

Text="{Binding ElementName=window1,Path=CurrentNum}"

VerticalAlignment="Center"

HorizontalAlignment="Center" />

当我们点击界面上的一个按钮后,要求该数字被不停的累加,直到再次点击该按钮是停止.实际效果相当于:

while (this.IsCalculating)

{

this.CurrentNum++

}

如果我们直接按照上面的While语句来书写程序,明显,当用户点击按钮后,整个线程将在这里被堵死,界面得不到更新,用户也没有办法再次点击按钮来停止这个循环,遭透了。

既不开启新线程又不阻塞界面应该怎么办呢?

我们知道this.CurrentNum++语句以及更新绑定到CurrentNum属性的TextBlock并不耗费时间的,耗费时间的是他们的累加而成的死循环,所以,我们将这个循环分解成无数个仅仅由this.Current++语句组成的小方法,并在这些小方法的之间来处理用户界面:

public delegate void NormalDelegate()

void button_StartOrStop_Click(object sender, RoutedEventArgs e)

{

if (this.IsCalculating)

{

NormalDelegate calNumDelegate = new NormalDelegate(this.CalNum)

this.Dispatcher.BeginInvoke(DispatcherPriority.SystemIdle, calNumDelegate)

}

}

private void CalNum()

{

this.CurrentNum++

if (this.IsCalculating)

{

NormalDelegate calNumDelegate = new NormalDelegate(this.CalNum)

this.Dispatcher.BeginInvoke(DispatcherPriority.SystemIdle, calNumDelegate)

}

}

上面的两段代码可以简单地如下示意:

阻塞的情况如下, MouseUp与MouseLeave会被阻塞:

(MouseDown)->(费时的While(true))->(MouseUp)->(MouseLeave)…

细化后的情况如下,MouseUp与MouseLeave不会被阻塞:

(MouseDown)->(不费时的CalNum)->(MouseUp)->(不费时的CalNum) ->(MouseLeave)…

用Delegate.Invoke()或Delegate.BeginInvoke()来开启新线程

除了new 一个Thread对象外,使用Delegate的Invoke或BeginInvoke方法也可以开启新的线程。

假设有下面这一个很费时的方法,我们应该如何使用Delegate来改造呢

private void TheHugeMethod()

{

Thread.Sleep(2000)

this.button_Test.Content = "OK!!!"

}

首先,我们声明一个可以用于TheHugeMethod方法的代理:

public delegate void NormalMethod()

然后对TheHugeMethod构造一个NormalMethod类型的对象,并调用其Invoke方法(同步调用)或BeginInvoke方法(异步调用)

void button_Test_Click(object sender, RoutedEventArgs e)

{

NormalMethod hugeMethodDelegate = new NormalMethod(this.TheHugeMethod)

hugeMethodDelegate.BeginInvoke(null, null)

}

由于是开启了新的线程,所以TheHugeMethod方法中对this.button_Test控件的调用语句也得改造一下:

private void TheHugeMethod()

{

Thread.Sleep(2000)

//will crash

//this.button_Test.Content = "OK!!!"

NormalMethod updateUIDelegate = new NormalMethod(this.UpdateUI)

this.button_Test.Dispatcher.BeginInvoke(DispatcherPriority.Normal, updateUIDelegate)

}

private void UpdateUI()

{

this.button_Test.Content = "OK!!! "

}

在新线程中执行消息循环

一般情况下我们不需要在新线程中执行消息循环了,因为我们常常是在新线程中执行一些后台 *** 作而不需要用户在新线程中执行UI *** 作(比如我们在新线程中从网络上下载一些数据然后UI线程来显示这些数据)。当有时新线程却是需要消息循环的,最简单的例子是 *** 作系统的“资源管理器”,每一个资源管理器窗口都在一个单独的线程中(它们都在同一个进程中)。

但当你按照如下方式编写代码来新建一个资源管理器窗口时,会出问题:

private void button_NewWindow_Click(object sender, RoutedEventArgs e)

{

Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint))

newWindowThread.SetApartmentState(ApartmentState.STA)

newWindowThread.IsBackground = true

newWindowThread.Start()

}

private void ThreadStartingPoint()

{

Window1 newWindow = new Window1()

newWindow.Show()

}

问题是newWindow闪现一下就消失了。因为该新窗口没有进入消息循环,当newWindow.Show()方法执行完毕后,新线程的一切都结束了。

正确的方法是在newWindow.Show()方法后加入Dispatcher.Run()语句,其会将主执行帧推入该Dispatcher的消息循环中。

private void ThreadStartingPoint()

{

Window1 newWindow = new Window1()

newWindow.Show()

System.Windows.Threading.Dispatcher.Run()

}

BackgroundWorker实质是:基于事件的异步模式

在多线程编程中,最爽的莫过于.net 提供了BackgroundWorker类了。其可以:

“在后台”执行耗时任务(例如下载和数据库 *** 作),但不会中断您的应用程序。

同时执行多个 *** 作,每个 *** 作完成时都会接到通知。

等待资源变得可用,但不会停止(“挂起”)您的应用程序。

使用熟悉的事件和委托模型与挂起的异步 *** 作通信。

我想大家对BackgroundWorker亦是再熟悉不过了,这里就不多做介绍了,另外“基于事件的异步模式”是WinForm的内容,但在WPF中完美运用(原因是WPF用DispatcherSynchronizationContext扩展了SynchronizationContext),可以参见MSDN“Event-based Asynchronous Pattern Overview”

winform还存在仅剩的五条理由:

对于够用党来说winform够用了

winform上手肯定比wpf简单

wpf刚出来的时候启动速度太慢,企业还是以winform为主(256M内存,XP时代)

然后桌面项目就没落了,wpf不跨平台不开源,wpf没有出手大型软件的机会,现在开源了也没跨平台。

wpf创建控件的速度还是比winform慢一点,有些情况下还是有winform比wpf快的感觉

但是在我所在的工控编程领域,我还是推wpf多,有很多方面,比如这个领域小动画很多,比如管子孙昌要有蚂蚁线动画,表达有东西在流动,十几个蚂蚁线就能让winform卡的10帧都不一定有。比如很需要line chart曲线图折线图,不断地更新,还需要平移缩放 *** 作等,winform的渲染性能太差,承载能力低。而wpf的渲染性能足够好,导致渲染性能不是瓶颈,动画可以做到流畅的60帧,当然我们群里也有人不服说工控软件要这么流畅干嘛,我会把我使用一个性能非常好的chart示例视频给他看,让他们看看威力,这个优秀体验是软件实力的彰显,其实只是用了好用的控件。WPF也更擅长无损图形和3D,这样可以借svg,autocad和solidworks的东西做成自己需要的界面,无损平面转xaml的Shapes,sw中提取简单的3d图直接可以导出xaml,方便控制动画和颜色,做出更加好的效果动画,方便程度远超qt到web的各种。

winform也有控件使用sharpdx来渲染加速,但是如果各个控件都用sharpdx的话,他们用的版本通常都是不同的,那么就只有一个控件能用上sharpdx了。所以wpf的渲染速度底则枯扒子还是很重要的,不要总是麻烦sharpdx嘛。

当然,wpf你入门以后败兆会发现开发其实比winform简单好用,画一些工控需要的乱七八糟的东西也方便,所以wpf是肯定优于winform的。但是现在桌面客户端领域里要把软件做大的少。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/12298951.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-24
下一篇 2023-05-24

发表评论

登录后才能评论

评论列表(0条)

保存