程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> 如何避免多控件窗體重新布局時閃爍

如何避免多控件窗體重新布局時閃爍

編輯:關於C#

適用場景:

需要在某容器控件中動態裝載多個子控件,而且該容器控件可能需要改變WindowFormState,即從Normal轉變為Maxmized,或者是其他狀態轉換啦, what ever :)

如果沒有應用任何特殊處理,你就會發現,當容器控件狀態轉換時,其上的子控件在經過一陣狂閃之後(可能背景控件顏色和自身相互交替出現),最終恢復至平靜; 這種情形當然無論是程序員自己和客戶都不願意看到的

ok,廢話一通之後,咱們開始解釋原因,以及考慮解決方案

為什麼會閃爍?

因為窗體控件狀態轉換時,windows需要負責"擦除"其背景,重新繪制,在一台性能並不優良的終端上(很大可能程度上客戶端電腦都不是那麼強勁吧) ,這個過程不是一時半會就能完成的,尤其對於很多個子控件的情況,因此就…

解決之道?

如果稍微寫過WinForm程序的同學,肯定或多或少的用過ListView控件,簡單易用嘛 :) 那麼一定也知道該控件有2個比較有意思的方法:

BeginUpdate

Prevents the control from drawing until the EndUpdate method is called.

EndUpdate

Resumes drawing of the list view control after drawing is suspended by the BeginUpdate method.

從msdn的解釋來看,這2個方法的應用能解決往ListView控件中分多次Add ListViewItem時閃爍的問題,ok,既然它能這麼處理,咱們自己的容器控件為什麼不能依葫蘆畫瓢呢?

btw. 其實我一開始也沒任何好方法解決閃爍問題,後來偶爾想到ListView的此特性 :)

看看ListView.BeginUpdateInternal方法怎麼寫:

internal void BeginUpdateInternal()
{
 if (this.IsHandleCreated)
 {
  if (this.updateCount == 0)
  {
   this.SendMessage(11, 0, 0);
  }
  this.updateCount = (short) (this.updateCount + 1);
 }
}

關鍵一行在 this.SendMessage(11, 0, 0); 蝦米意思呢? 它給自身Send了一個code為11的windows消息,11代表蝦米?

在windows消息定義中可以看到 WM_SETREDRAW = 0x0B (0x0B也就是11),這行代碼的意思是告訴windows對ListView控件停止重繪界面,直到顯式要求重新繪制為止. 很牛叉對不對 :Dok,在EndUpdateInternal中又做了蝦米?

internal bool EndUpdateInternal(bool invalidate)
{
 if (this.updateCount <= 0)
 {
  return false;
 }
 this.updateCount = (short) (this.updateCount - 1);
 if (this.updateCount == 0)
 {
  this.SendMessage(11, -1, 0);
  if (invalidate)
  {
   this.Invalidate();
  }
 }
 return true;
}

同樣有一行代碼: this.SendMessage(11, –1, 0); 11還是同一個意思,此時告知windows可以重繪ListView控件了

ok,到這時候應該明白這2個方法含義了吧,也就是說對子控件的操作都是在一個“凍結”的狀態中進行的,等到所有准備工作就緒,才對最終狀態重新繪制,因此界面就不會出現閃爍狀態.

如何依葫蘆畫瓢?

要知道不是每個控件類都有提供BeginUpdate和EndUpdate方法,所以需要自己親自打造一個

1. SendMessage如何來? 從windows api interop而來,很簡單,有個工具可以提供所有api函數到c#方法的轉換: P/Invoke Interop Assistant

2.11這個定義以及類似的東東怎麼找? 強大的google或者bing可以幫忙 :D

至於代碼怎麼寫,就不需要偶來操刀啦 :D

後記:

這樣處理之後,是不是發現閃爍從此就不再出現了?但是……還有問題

拿一個無邊框窗體舉例,當它從Normal狀態變為Maxmized(順便設置TopMost為true),你會很高興看到該窗體包含的子控件真的不閃了,從Maxmized回到Normal時,也不閃了,但是很詭異的問題發生了: 任務欄不見了?取而代之的是當前窗體的背景色??? 難道任務欄沒有重繪回來???

發生什麼事情了? 不是只讓窗體的重繪停止了嗎,怎麼會影響到任務欄窗口?

ok,解決辦法是有的,發個消息給任務欄窗口讓它強制重繪,怎麼寫?

同樣,對於.net中的任何控件,都自帶Invalidate方法,通過調用該方法,可以強制重繪控件的整體或某矩形部分, 又要畫瓢啦

在Invalidate中可以看到這麼一行

SafeNativeMethods.RedrawWindow(new HandleRef(this.window, this.Handle), (NativeMethods.COMRECT) null, NativeMethods.NullHandleRef, 0x85);

現在的問題是,如何獲取任務欄窗口句柄?  bing一把就會發現,很簡單: FindWindow(“Shell_TrayWnd”, “”);

ok, 後面的事情就簡單啦

再後記

不難發現,Control對象其實是自帶BeginUpdateInternal方法的,但m$很惡毒的把它弄成internal的了… 而且只被少數幾個控件享用: ListView,

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved