Winform MDI窗體切換不閃爍的解決辦法(測試通過)


https://stackoverflow.com/questions/5817632/beginupdate-endupdate-for-datagridview-request

SuspendLayout()並且ResumeLayout()不要暫停繪圖,只能暫停布局操作。給這個家伙一個機會:

public static class ControlHelper { #region Redraw Suspend/Resume [DllImport("user32.dll", EntryPoint = "SendMessageA", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)] private static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam); private const int WM_SETREDRAW = 0xB; public static void SuspendDrawing(this Control target) { SendMessage(target.Handle, WM_SETREDRAW, 0, 0); } public static void ResumeDrawing(this Control target) { ResumeDrawing(target, true); } public static void ResumeDrawing(this Control target, bool redraw) { SendMessage(target.Handle, WM_SETREDRAW, 1, 0); if (redraw) { target.Refresh(); } } #endregion }

 

MDI窗體不閃爍方法測試通過:

//.net 4.0用OptimizedDoubleBuffer
this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint |  ControlStyles.AllPaintingInWmPaint, true);
this.UpdateStyles();

真正有效的方法:在最上層窗體加上
protected override CreateParams CreateParams {
  get {
    CreateParams cp = base.CreateParams;
    cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    return cp;
  }
}

在下層的窗體和自定義控件加上
protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                cp.Style &= ~0x02000000;  // Turn off WS_CLIPCHILDREN
                return cp;
            }
        }  

注意:如果加錯地方或人品不好,某些時候可能會造成控件繪制略微不正常。

**** 如果人品爆發的話,貌似在下層窗體直接加cp.Style &= ~0x02000000就行,不需要在上層窗體加cp.ExStyle |= 0x02000000;

**** 注意下層窗體代碼在ListBox或者ListView的Anchor設有Right,且窗體BackColor與控件背景不同的時候,可能會發現控件初始化顯示不正常。需要做一下MdiParent.Refresh或者取消Right

引用MSDN中對CreateParams的說明:
    在你開發的重載控件中不要重寫這個屬性,通過這個屬性控制控件的某些風格。只有在你封裝Windows控件或者想實現某些WinForm沒有提供的風格(比如Layered Window)控制的時候再使用這個屬性。更多信息請參照MSDN上對CreateWindow方法和CreateWindowEx方法的參數CREATESTRUCT結構體的文檔注釋 。
簡述為何CreateParams能夠實現這樣高級的樣式控制,因為從CreateWindow和CreateWindowEx的名字就可以看出,CreateParam是傳遞給這倆個方法的參數,而這兩個方法又是在窗體創建的時候調用的。所以,CreateParam才能夠實現如此強大的樣式控制。

 

**********

由於窗體上控件多,且有背景的情況下,控件設為背景設為透明,會導致窗體的刷新很慢很卡,從而窗體在閃爍,卡頓。

  之前一直在網上搜尋解決的辦法,試過了很多什么雙緩沖啊之類的,發現效果並不大。

  最后找到下面的方法可以解決了。但是奇怪的是,在有些電腦上運行時會發生窗體不刷新的問題。然后就參考下面的那個網址,最下面的答案。

  http://stackoverflow.com/questions/5859826/datagridview-draws-wrong

  個人總結一下,可能是系統版本的問題所導致的。網上有說是因為“xp特有的雙緩沖繪圖機制”。后來用上這個WS_EX_COMPOSITED(用雙緩沖從下到上繪制窗口的所有子孫),再開啟窗體的透明樣式,問題都解決了。

protected override CreateParams CreateParams
{
get
{

CreateParams cp = base.CreateParams;

cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED

if (this.IsXpOr2003 == true)
{
cp.ExStyle |= 0x00080000; // Turn on WS_EX_LAYERED
this.Opacity = 1;
}

return cp;

}

} //防止閃爍

private Boolean IsXpOr2003
{
get
{
OperatingSystem os = Environment.OSVersion;
Version vs = os.Version;

if (os.Platform == PlatformID.Win32NT)
if ((vs.Major == 5) && (vs.Minor != 0))
return true;
else
return false;
else
return false;
}
}

 

**********

節點更新要使用BeginUpdate和EndUpdate
      這一對操作對於需要批量操作更新控件的情景有比較好的效果,比如初始化時批量添加了大量節點。壞處就在於不能即時更新。所以,對於頻繁的更新節點並希望立即反映到界面的情況不適用。如果使用並且沒有禁掉清除界面消息的話,則控件看起來就會不停的閃爍,而且以白底為主,內容幾乎不可見(這個視頻繁程度而定)。因為界面更新都在EndUpdate處完成,操作太多導致EndUpdate阻塞時間過長,且清空在先,更新在后,導致界面看起來長時間處於空白狀態。

某些情況下可以使用禁止背景更新
protected override void WndProc(ref Message m)
        {
            if (m.Msg == 0x0014)  return;// 禁掉清除背景消息
            base.WndProc(ref m);
        }

public ListViewNF()
        {
            // 開啟雙緩沖
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);

            // Enable the OnNotifyMessage event so we get a chance to filter out
            // Windows messages before they get to the form's WndProc
            this.SetStyle(ControlStyles.EnableNotifyMessage, true);
        }

        protected override void OnNotifyMessage(Message m)
        {
            //Filter out the WM_ERASEBKGND message
            if (m.Msg != 0x14)
            {
                base.OnNotifyMessage(m);
            }
        }

 

******************************************

 采用LockWindowUpdate API

 [DllImport("user32.dll")]
static extern bool LockWindowUpdate(IntPtr hWndLock);

LockWindowUpdate(panelContainer.Handle);

// Clear Panel
panelContainer.Controls.Clear();

// my temporary TextBox
TextBox myT ;

for (int lauf=0; lauf < 200; lauf++)
{
    // Create New TextBox
    myT = new TextBox();

    // Add TextBox to the Panel
    panelContainer.Controls.Add(myT);
}
// redraw the window
LockWindowUpdate(IntPtr.Zero);

 

 

frmChild1.Hide( );              // 隱藏當前顯示的子窗體
2 
3 LockWindowUpdate(this.Handle);  // 鎖定父窗體
4 frmChild2.Show( );              // 顯示窗體等其他需要再顯示前做的事
5 LockWindowUpdate (IntPtr.Zero); // 解鎖父窗體
6 RedrawWindow (this.Handle, IntPtr.Zero, IntPtr.Zero, 
   RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); // (0x04 | 0x01 | 0x80)立即強制重繪父窗體及其所有子窗體

    效果好轉,但人眼還能看到一些花屏現象,仍不能一次全部完整顯示。

  

3. 使用Windows API中的SendMessage函數: 

攔截控件重繪
class  DrawingControl
{
     [DllImport( "user32.dll" )]
     public  static  extern  int  SendMessage(IntPtr hWnd, Int32 wMsg,  bool  wParam, Int32 lParam);
 
     private  const  int  WM_SETREDRAW = 11; 
 
     public  static  void  SuspendDrawing( Control parent )
     {
         SendMessage(parent.Handle, WM_SETREDRAW,  false , 0);
     }
 
     public  static  void  ResumeDrawing( Control parent )
     {
         SendMessage(parent.Handle, WM_SETREDRAW,  true , 0);
         parent.Refresh();
     }
}

 

1 frmChild1.Hide( );
2 
3 SendMessage(this.Handle, WM_SETDRAW, false, null);  // 禁止窗體中的繪制操作 ----- 1
4 frmChild2.Show( );                                  // 顯示窗體等其他需要再顯示前做的事
5 SendMessage(this.Handle, WM_SETDRAW, true, null);   // 解除禁止繪制操作     ----- 2
6 RedrawWindow (this.Handle, IntPtr.Zero, IntPtr.Zero, 
   RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); // (0x04 | 0x01 | 0x80)立即強制重繪父窗體及其所有子窗

SendMessage函數中,發送消息 WM_SETREDRAW設置SETREDRAW為FALSE,導致窗口不進行繪制。
此時,看到的窗體是假的,現象:

 
         

鼠標形狀是后面應用程序的形狀
鼠標划過,后面的應用程序就顯示出來了
人眼看到的就是“花屏”。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM