備注:Rimi是我們用的一個分布式機制。

在進行設備樹(也就是一個CTreeCtrl控件)更新修改的時候,遇到了一個比較bug的問題。

為了提供更好的用戶體驗,甲方希望設備樹更新之后滾動條位置能夠保持與更新前一致。設備樹的更新過程是這樣的:更新消息來自Rimi的通知機制,類似于函數回調,客戶端在收到消息之后調用Rimi的對象方法來獲取新的設備樹信息,然后更新樹。乍看之下,要完成這個修改好像很簡單,只要更新前先記錄滾動條的滾動位置,更新后還原位置,如果更新后滾動條滾動范圍變化了還要微調一下位置,邏輯上來講就這么幾個步驟。

我一開始也是按照這樣的思路,GetScrollPos()獲取當前滾動條的滾動位置,然后更新樹(先刪除所有節點再逐個添加,其他省略...),GetScrollRange()獲得新的滾動范圍,最后SetScrollPos()將舊的位置與滾動范圍最大值中最小的一個設回去(這里用到的ScrollBar是CTreeCtrl自動產生的,注意不是兩個控件,這里調用的函數都是CTreeCtrl的方法)。但實際效果是,樹更新后滾動條滾到準確的位置,但樹的視圖到了最頂,點擊一下滾動條的那個方塊才能回到之前的位置。也就是說,滾動位置的更新與樹的視圖分離了。

之后,我一直以為是我控件的方法用錯了,對著MSDN和CSDN糾結了很久。最后忍無可忍,自己寫了個測試Demo,里面就一Dialog,一CTreeCTrl,樹上隨便加了些東西,然后又一按鍵,按鍵后會重新刷新樹,再滾動到原來的位置,結果居然是對的,視圖跟著滾動條的位置變化了。為了更好的模擬設備樹節點增刪的效果,我在按鍵響應上又作了處理,按一下重刷樹的時候會隱藏幾個節點,再按一下這些節點顯示出來,滾動位置按照客戶端里面的一個處理方法,結果居然也是正確的。問題變得玄乎了!

無意間發現客戶端里面有個手動刷新設備樹的快捷鍵,估計是當年pb做調試的時候留下來的。快捷鍵的響應直接調用更新樹的函數,重刷后的顯示出人意料地是對的。比較一下兩種更新方式的過程:

Rimi:   通知到來—>更新樹(Rimi回調函數,Rimi自己維護了一個線程池,遠程調用在被調用端的發起者都是Rimi自己的線程)
快捷鍵: 按鍵響應—>更新樹(MFC消息處理函數)

更新樹所用到的是同一個函數,但調用者卻是不同的。因為Rimi用了boost::function,那我也在按鍵響應的時候對要調的函數用function來包裝一下,造成兩者在調用棧上調用的函數、順序大部分是一致的,只有最底層不同,一邊是Rimi,一邊是MFC消息傳遞。

后來jianhao說,以前在Rimi的回調函數里面調Rimi對象的方法出過問題,然后我又順道回憶起之前zxb在Rimi函數(還是對象方法)里面調system()也有問題。

難道說Rimi線程就是“萬惡之源”?好吧,我把更新代碼移到另外一個線程里面,Rimi回調的時候喚醒更新線程,更新后視圖還是不能跟著滾動位置變;將快捷鍵的響應也修改一下,自己不作更新,也是喚醒更新線程,這個方法也變得不靈了,囧!這可以說明問題跟Rimi線程無關。

難道說線程調用才是“萬惡之源”?把之前做的那個Demo小改了一把,線程做刷新,按鍵響應只喚醒更新線程,果然不靈了!上網google了一把,關鍵字“mfc 線程 操作控件”,首先映入眼簾的是《MFC中跨線程操作控件會不會出現像C#中的異常問題?》。這時候我也不關心這個帖子的內容了,線程操作控件有異常是吧,那就不用線程做咯!這時候我才回想起WIN32里面有自定義消息這玩意,MFC里面給定一個消息ID,ON_MESSAGE綁定一個處理函數,PostMessage或SendMessage來發消息,然后由WIN32自己的消息循環來調用處理函數,這樣應該是可以保證用非Rimi線程來更新設備樹的。再一次把Demo小改了一把,按鍵響應Post一個自定義消息,消息處理函數做刷新,結果是對的;再改,按鍵響應喚醒線程,線程里面Post自定義消息,結果也是對的。

原以為是控件使用問題,又以為是Rimi不兼容問題,最后實質為MFC跨線程使用控件的問題。其實我也不清楚這是不是真正的問題,畢竟我MFC既不懂又用得少。That's all!

最后附上我的測試代碼
http://www.shnenglu.com/Files/neverwinter/testtree.rar