阅读 69

wpf 第三方控件,defineproperty 对象的属性

今天特别要说的是Dispatcher。 这是因为,WPF经常会遇到多线程软件接口控件更新问题。 很多初步接触了WPF接口开发的朋友相信,为了避免接口被卡住,在自己编写的线程中更新和读取控件时,会遇到以下奇怪的Exception异常:

这个异常是告诉我们的,对不起非法操作。

我个人认为99.9%的人遇到过这个问题。 因此,很多人觉得微软的WPF不好用,简单地更新界面怎么这么辛苦,它只要读取TexBox的Text属性就有可能很快崩溃。 我个人也认为,这是微软基础架构的问题,使用过程中带来了不便,但架构无法改变。 只能硬着头皮学习使用方法。

相信很多人通过无事可做的度娘找到了解决的办法,就是加上以下几句话。

this.Dispatcher.Invoke (()=) /更改您的访问空间或控制代码) ); 上面的this.Dispatcher.Invoke ) )可以解决异常问题,并解决了问题。 解决方案是完全正确的,但总觉得张皇失措,为什么要这么辛苦呢? 我也觉得可读性不好而不是直接! 但是微软如此任性,所以没办法。

好了,言归正传,就这样绕了一圈,我们需要回头看看Dispatcher是什么样的。

首先简单地说几句话,希望你能理解,但大体上最好记住:

1 )官方称,WPF一般在启动后有两个线程,一个负责呈现责任,一个负责管理UI界面。 官方简单介绍了演示人员和UI界面管理人员,这里不再赘述。 要记住的是,UI界面管理这个线程;

2 )负责管理UI界面的线程简称为UI线程。 UI线程中有Dispatcher对象。 Dispatcher对象包含此UI线程的许多工作内容(官方称为work item )的队列。 UI线程是Dispatcher负责处理涉及到该控制的这些事件。 废话很多,直接制图很粗鲁:

3 )只有创建UI控件的UI Thread才能访问和更新权限控件!

4 )其他线程(不直接创建访问控制UI控件的线程)要访问和更新某个控件,创建该控件的线程(一般为UI线程)通过关联的Dispatcher 这就是为什么this.Dispatcher.Invoke ()很常见的原因。

这样做很多实验来验证以上的理论知识。

首先创建以下WPF接口:

XAML非常简单,代码如下。

gridbuttonverticalalignment=' center ' horizontal alignment=' center ' width=' 100 ' name=' mybtn ' click me/button /

publicpartialclassmainwindow : window { public main window () { InitializeComponent ); threadTRD=newthread(myfun; //创建新线程trd.Start (; //启动新线程} public void myFun () { myBtn.Content='My Love ); }时,在新线程上运行myFun ()时会显示InvalidOperationException,并且在新线程上直接更新控件确实会导致错误。

于是,我们myFun将代码更改为:

public void myFun () ({ this.Dispatcher.Invoke ) )=)={ myBtn.Content='My Love '; ); }再次运行,线程正确修复了Button控件中的信息。

请注意,在MainWinow启动后,系统为当前UI线程分配了Dispatcher。 此Dispatcher属于名为MainWindow的实例。 这是因为主窗口继承自DispatcherObject类,而DispatcherObject包含公共属性diict。实际上,除了窗口类,DispatcherObject还包含其他控件在当前示例中,主窗口和Button由UI线程创建,而主窗口和Button的Dispatc由

her属性都指向UI线程拥有的Dispatcher,因此,我们还可以将上面的例子改为下面的方式(用myBtn.Dispatcher.Invoke()来实现按钮的更新,运行结果完全一样):


public void myFun() { myBtn.Dispatcher.Invoke(()=> { myBtn.Content = "My Love"; }); }

    从代码上看起来,myFun函数内访问this.Dispatcher貌似访问的是运行myFun线程中的Dispatcher,看起来是有点古怪,不过你只要知道this指的是MainWindow实例对象,那么MainWindow这个类实例对象的Dispatcher是UI线程拥有的对象,因此没有错误,也就不古怪了。

    假如整个代码改为下面的方式:

public MainWindow() { InitializeComponent(); myFun(); } public void myFun() { myBtn.Content = "My Love"; }

    我们发现,运行也没问题,没有任何古怪的地方,加不加this.Dispatcher.Invoke()都可以运行ok。这是由于运行myFunc的环境是在UI线程之下。为了更容易发现线程的变换,我们加入更多的测试代码,整个工程改为如下:

public MainWindow() { InitializeComponent(); Thread.CurrentThread.Name = "Main Thread";// 设置当前线程名称 Thread trd = new Thread(myFun); // 创建一个新线程 trd.Name = "New Thread"; trd.Start(); // 启动一个新线程 } public void myFun() { this.Dispatcher.Invoke(() => { myBtn.Content = Thread.CurrentThread.Name;// 将当前线程名称输出到Button上 }); }

    我们发现,代码运行到myFun()时的线程已经变成了trd所创建的线程(通过Thread.CurrentThread.Name来获知当前线程名称是个好办法),新线程的名称也成功输出到按钮上:


    由此,我们应当树立一个观点: 同一个类下的方法根据你调用的方式不同,并不一定都运行于同一个线程下。即使调用其他类的函数,也可能存在两种情况,要么运行在一个线程里,要么运行在不同的线程里。实际上是否是一个线程里完全跟如何调度相关,跟是否属于哪个类没有任何关系。

    如果实在不清楚某个线程下是否可以直接更新或访问控件,一方面你可以一股脑的都加上this.Dispatcher.Invoke()(实际上除了这个方法,还有BeginInvoke方法),另一方面可以通过控件的CheckAccess()方法或者VerifyAccess()方法来判断该控件是否允许在当前线程下被访问被更新。因此,myFun函数可以改为下面的形式:

public void myFun() { // myBtn.VerifyAccess(); //该方法在不可访问的情况下,直接抛出InvalidOperationException异常 if(!myBtn.CheckAccess()) { this.Dispatcher.Invoke(() => { myBtn.Content = Thread.CurrentThread.Name;// 将当前线程名称输出到Button上 }); } else { myBtn.Content = Thread.CurrentThread.Name;// 将当前线程名称输出到Button上 } } }

    有深究精神的童鞋,可能会问,既然某个线程拥有Dispatcher,那么是否可以通过线程直接访问Dispatcher呢?这个很简单,可以直接查找Thread线程类的资料,惊奇的发现,Thread根本不存在一个可以访问自身所拥有的Dispatcher对象的属性或者方法,搞得我也一头雾水,反正有一种"我拥有的还不能直接获得"的莫名其妙感觉。那有没有能获得的办法了呢?答案是肯定的。

    可以通过Dispatcher类本身的static方法FromThread(Thread trd)来获得某个线程所拥有的Dispatcher。下面将c#后台代码改为下面的试验代码:

public MainWindow() { InitializeComponent(); Dispatcher dsp = Dispatcher.FromThread(Thread.CurrentThread);//结果:非null bool r = dsp.Equals(this.Dispatcher);//结果:true Thread.CurrentThread.Name = "Main Thread";// 设置当前线程名称 Thread trd = new Thread(myFun); // 创建一个新线程 trd.Name = "New Thread"; trd.Start(); // 启动一个新线程 } public void myFun() { Dispatcher dsp = Dispatcher.FromThread(Thread.CurrentThread);//结果:获得的dsp值为null if (!myBtn.CheckAccess()) { this.Dispatcher.Invoke(() => { myBtn.Content = Thread.CurrentThread.Name;// 将当前线程名称输出到Button上 }); } else { myBtn.Content = Thread.CurrentThread.Name;// 将当前线程名称输出到Button上 } }

用debug调试方式,你会发现在MainWindow构造函数下的函数中获得的dsp非空,并且通过Equals()方法发现dsp就是this.Dispatcher。myFun()函数运行的线程下,dsp的结果是空,明显就不等于this.Dispatcher。

那么怎么给这个新的线程赋一个Dispatcher?

可能我看的资料还没全,至今我还只发现一种办法,并且是一种让人感觉很莫名其妙或者说很隐晦的方法,直接上代码:

public MainWindow() { InitializeComponent(); Dispatcher dsp = Dispatcher.FromThread(Thread.CurrentThread);//结果:非null bool r = dsp.Equals(this.Dispatcher);//结果:true Dispatcher newdsp = Dispatcher.CurrentDispatcher; // 获取当前的Dispatcher r = this.Dispatcher.Equals(newdsp); //结果,仍然是true,说明已经有Dispatcher的情况下不会赋新Dispatcher Thread.CurrentThread.Name = "Main Thread";// 设置当前线程名称 Thread trd = new Thread(myFun); // 创建一个新线程 trd.Name = "New Thread"; trd.Start(); // 启动一个新线程 } public void myFun() { Dispatcher dsp = Dispatcher.FromThread(Thread.CurrentThread);//结果:获得的dsp值为null Dispatcher newdsp = Dispatcher.CurrentDispatcher; // 默默的为当前新线程赋了一个Dispatcher dsp = Dispatcher.FromThread(Thread.CurrentThread); // 重新获取当前线程的Dispatcher bool r = newdsp.Equals(dsp);// 结果是true if (!myBtn.CheckAccess()) { this.Dispatcher.Invoke(() => { myBtn.Content = Thread.CurrentThread.Name;// 将当前线程名称输出到Button上 }); } else { myBtn.Content = Thread.CurrentThread.Name;// 将当前线程名称输出到Button上 } }

    仔细看上面的代码及注释。

   官网透露的资料里,告诉我们既可以通过Dispatcher.CurrentDispatcher获得当前线程的Dispatcher对象,也可以通过访问Dispatcher.CurrentDispatcher(CurrentDispatcher只可读!!!)给一个没有Dispatcher的线程自动赋一个Dispatcher,自动给一个无Dispatcher的线程赋一个Dispatcher对象这个功能显得比较古怪,但是微软就是这么任性。

    根据官网资料,一旦一个线程获得了一个Dispatcher,那么这个Dispatcher不可更改,即使被关闭。

    不过,个人还不是很清楚给一个不带界面的Thread赋一个Dispatcher的作用,貌似没什么作用。要么就是鄙人还没掌握所有资料。

    今天的故事就先讲到这里了。


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐