備注:Rimi是我們用的一個分布式機(jī)制。
在進(jìn)行設(shè)備樹(也就是一個CTreeCtrl控件)更新修改的時候,遇到了一個比較bug的問題。
為了提供更好的用戶體驗(yàn),甲方希望設(shè)備樹更新之后滾動條位置能夠保持與更新前一致。設(shè)備樹的更新過程是這樣的:更新消息來自Rimi的通知機(jī)制,類似于函數(shù)回調(diào),客戶端在收到消息之后調(diào)用Rimi的對象方法來獲取新的設(shè)備樹信息,然后更新樹。乍看之下,要完成這個修改好像很簡單,只要更新前先記錄滾動條的滾動位置,更新后還原位置,如果更新后滾動條滾動范圍變化了還要微調(diào)一下位置,邏輯上來講就這么幾個步驟。
我一開始也是按照這樣的思路,GetScrollPos()獲取當(dāng)前滾動條的滾動位置,然后更新樹(先刪除所有節(jié)點(diǎn)再逐個添加,其他省略...),GetScrollRange()獲得新的滾動范圍,最后SetScrollPos()將舊的位置與滾動范圍最大值中最小的一個設(shè)回去(這里用到的ScrollBar是CTreeCtrl自動產(chǎn)生的,注意不是兩個控件,這里調(diào)用的函數(shù)都是CTreeCtrl的方法)。但實(shí)際效果是,樹更新后滾動條滾到準(zhǔn)確的位置,但樹的視圖到了最頂,點(diǎn)擊一下滾動條的那個方塊才能回到之前的位置。也就是說,滾動位置的更新與樹的視圖分離了。
之后,我一直以為是我控件的方法用錯了,對著MSDN和CSDN糾結(jié)了很久。最后忍無可忍,自己寫了個測試Demo,里面就一Dialog,一CTreeCTrl,樹上隨便加了些東西,然后又一按鍵,按鍵后會重新刷新樹,再滾動到原來的位置,結(jié)果居然是對的,視圖跟著滾動條的位置變化了。為了更好的模擬設(shè)備樹節(jié)點(diǎn)增刪的效果,我在按鍵響應(yīng)上又作了處理,按一下重刷樹的時候會隱藏幾個節(jié)點(diǎn),再按一下這些節(jié)點(diǎn)顯示出來,滾動位置按照客戶端里面的一個處理方法,結(jié)果居然也是正確的。問題變得玄乎了!
無意間發(fā)現(xiàn)客戶端里面有個手動刷新設(shè)備樹的快捷鍵,估計(jì)是當(dāng)年pb做調(diào)試的時候留下來的。快捷鍵的響應(yīng)直接調(diào)用更新樹的函數(shù),重刷后的顯示出人意料地是對的。比較一下兩種更新方式的過程:
Rimi: 通知到來—>更新樹(Rimi回調(diào)函數(shù),Rimi自己維護(hù)了一個線程池,遠(yuǎn)程調(diào)用在被調(diào)用端的發(fā)起者都是Rimi自己的線程)
快捷鍵: 按鍵響應(yīng)—>更新樹(MFC消息處理函數(shù))
更新樹所用到的是同一個函數(shù),但調(diào)用者卻是不同的。因?yàn)镽imi用了boost::function,那我也在按鍵響應(yīng)的時候?qū)σ{(diào)的函數(shù)用function來包裝一下,造成兩者在調(diào)用棧上調(diào)用的函數(shù)、順序大部分是一致的,只有最底層不同,一邊是Rimi,一邊是MFC消息傳遞。
后來jianhao說,以前在Rimi的回調(diào)函數(shù)里面調(diào)Rimi對象的方法出過問題,然后我又順道回憶起之前zxb在Rimi函數(shù)(還是對象方法)里面調(diào)system()也有問題。
難道說Rimi線程就是“萬惡之源”?好吧,我把更新代碼移到另外一個線程里面,Rimi回調(diào)的時候喚醒更新線程,更新后視圖還是不能跟著滾動位置變;將快捷鍵的響應(yīng)也修改一下,自己不作更新,也是喚醒更新線程,這個方法也變得不靈了,囧!這可以說明問題跟Rimi線程無關(guān)。
難道說線程調(diào)用才是“萬惡之源”?把之前做的那個Demo小改了一把,線程做刷新,按鍵響應(yīng)只喚醒更新線程,果然不靈了!上網(wǎng)google了一把,關(guān)鍵字“mfc 線程 操作控件”,首先映入眼簾的是《MFC中跨線程操作控件會不會出現(xiàn)像C#中的異常問題?》。這時候我也不關(guān)心這個帖子的內(nèi)容了,線程操作控件有異常是吧,那就不用線程做咯!這時候我才回想起WIN32里面有自定義消息這玩意,MFC里面給定一個消息ID,ON_MESSAGE綁定一個處理函數(shù),PostMessage或SendMessage來發(fā)消息,然后由WIN32自己的消息循環(huán)來調(diào)用處理函數(shù),這樣應(yīng)該是可以保證用非Rimi線程來更新設(shè)備樹的。再一次把Demo小改了一把,按鍵響應(yīng)Post一個自定義消息,消息處理函數(shù)做刷新,結(jié)果是對的;再改,按鍵響應(yīng)喚醒線程,線程里面Post自定義消息,結(jié)果也是對的。
原以為是控件使用問題,又以為是Rimi不兼容問題,最后實(shí)質(zhì)為MFC跨線程使用控件的問題。其實(shí)我也不清楚這是不是真正的問題,畢竟我MFC既不懂又用得少。That's all!
最后附上我的測試代碼
http://www.shnenglu.com/Files/neverwinter/testtree.rar
在進(jìn)行設(shè)備樹(也就是一個CTreeCtrl控件)更新修改的時候,遇到了一個比較bug的問題。
為了提供更好的用戶體驗(yàn),甲方希望設(shè)備樹更新之后滾動條位置能夠保持與更新前一致。設(shè)備樹的更新過程是這樣的:更新消息來自Rimi的通知機(jī)制,類似于函數(shù)回調(diào),客戶端在收到消息之后調(diào)用Rimi的對象方法來獲取新的設(shè)備樹信息,然后更新樹。乍看之下,要完成這個修改好像很簡單,只要更新前先記錄滾動條的滾動位置,更新后還原位置,如果更新后滾動條滾動范圍變化了還要微調(diào)一下位置,邏輯上來講就這么幾個步驟。
我一開始也是按照這樣的思路,GetScrollPos()獲取當(dāng)前滾動條的滾動位置,然后更新樹(先刪除所有節(jié)點(diǎn)再逐個添加,其他省略...),GetScrollRange()獲得新的滾動范圍,最后SetScrollPos()將舊的位置與滾動范圍最大值中最小的一個設(shè)回去(這里用到的ScrollBar是CTreeCtrl自動產(chǎn)生的,注意不是兩個控件,這里調(diào)用的函數(shù)都是CTreeCtrl的方法)。但實(shí)際效果是,樹更新后滾動條滾到準(zhǔn)確的位置,但樹的視圖到了最頂,點(diǎn)擊一下滾動條的那個方塊才能回到之前的位置。也就是說,滾動位置的更新與樹的視圖分離了。
之后,我一直以為是我控件的方法用錯了,對著MSDN和CSDN糾結(jié)了很久。最后忍無可忍,自己寫了個測試Demo,里面就一Dialog,一CTreeCTrl,樹上隨便加了些東西,然后又一按鍵,按鍵后會重新刷新樹,再滾動到原來的位置,結(jié)果居然是對的,視圖跟著滾動條的位置變化了。為了更好的模擬設(shè)備樹節(jié)點(diǎn)增刪的效果,我在按鍵響應(yīng)上又作了處理,按一下重刷樹的時候會隱藏幾個節(jié)點(diǎn),再按一下這些節(jié)點(diǎn)顯示出來,滾動位置按照客戶端里面的一個處理方法,結(jié)果居然也是正確的。問題變得玄乎了!
無意間發(fā)現(xiàn)客戶端里面有個手動刷新設(shè)備樹的快捷鍵,估計(jì)是當(dāng)年pb做調(diào)試的時候留下來的。快捷鍵的響應(yīng)直接調(diào)用更新樹的函數(shù),重刷后的顯示出人意料地是對的。比較一下兩種更新方式的過程:
Rimi: 通知到來—>更新樹(Rimi回調(diào)函數(shù),Rimi自己維護(hù)了一個線程池,遠(yuǎn)程調(diào)用在被調(diào)用端的發(fā)起者都是Rimi自己的線程)
快捷鍵: 按鍵響應(yīng)—>更新樹(MFC消息處理函數(shù))
更新樹所用到的是同一個函數(shù),但調(diào)用者卻是不同的。因?yàn)镽imi用了boost::function,那我也在按鍵響應(yīng)的時候?qū)σ{(diào)的函數(shù)用function來包裝一下,造成兩者在調(diào)用棧上調(diào)用的函數(shù)、順序大部分是一致的,只有最底層不同,一邊是Rimi,一邊是MFC消息傳遞。
后來jianhao說,以前在Rimi的回調(diào)函數(shù)里面調(diào)Rimi對象的方法出過問題,然后我又順道回憶起之前zxb在Rimi函數(shù)(還是對象方法)里面調(diào)system()也有問題。
難道說Rimi線程就是“萬惡之源”?好吧,我把更新代碼移到另外一個線程里面,Rimi回調(diào)的時候喚醒更新線程,更新后視圖還是不能跟著滾動位置變;將快捷鍵的響應(yīng)也修改一下,自己不作更新,也是喚醒更新線程,這個方法也變得不靈了,囧!這可以說明問題跟Rimi線程無關(guān)。
難道說線程調(diào)用才是“萬惡之源”?把之前做的那個Demo小改了一把,線程做刷新,按鍵響應(yīng)只喚醒更新線程,果然不靈了!上網(wǎng)google了一把,關(guān)鍵字“mfc 線程 操作控件”,首先映入眼簾的是《MFC中跨線程操作控件會不會出現(xiàn)像C#中的異常問題?》。這時候我也不關(guān)心這個帖子的內(nèi)容了,線程操作控件有異常是吧,那就不用線程做咯!這時候我才回想起WIN32里面有自定義消息這玩意,MFC里面給定一個消息ID,ON_MESSAGE綁定一個處理函數(shù),PostMessage或SendMessage來發(fā)消息,然后由WIN32自己的消息循環(huán)來調(diào)用處理函數(shù),這樣應(yīng)該是可以保證用非Rimi線程來更新設(shè)備樹的。再一次把Demo小改了一把,按鍵響應(yīng)Post一個自定義消息,消息處理函數(shù)做刷新,結(jié)果是對的;再改,按鍵響應(yīng)喚醒線程,線程里面Post自定義消息,結(jié)果也是對的。
原以為是控件使用問題,又以為是Rimi不兼容問題,最后實(shí)質(zhì)為MFC跨線程使用控件的問題。其實(shí)我也不清楚這是不是真正的問題,畢竟我MFC既不懂又用得少。That's all!
最后附上我的測試代碼
http://www.shnenglu.com/Files/neverwinter/testtree.rar
Qt也不能跨線程?如果在非UI線程emit信號給控件,那也應(yīng)該相當(dāng)于跨線程操作吧?
resume service cause they are the most responsible! Our company represents resume writer to suit the precise area of science you wish.