2012-05-14 48 views
6

我有兩個問題與使用位圖的自己的用戶控制:自己的WinForms控件閃爍並有壞性能

  1. 它閃爍,如果是通過.NET的「刷新」方法重繪。
  2. 它有一個糟糕的表現。

控制由三個位圖:

  • 靜態背景圖像。
  • 旋轉轉子。
  • 取決於轉子角度的另一個圖像。

所有使用的位圖的分辨率爲500x500像素。該控件的工作原理如下: https://www.dropbox.com/s/t92gucestwdkx8z/StatorAndRotor.gif(這是一個gif動畫)

用戶控件應該每次獲取新的轉子角度時自行繪製。因此,它具有公共財產「RotorAngle」它看起來像這樣:

public double RotorAngle 
{ 
    get { return mRotorAngle; } 
    set 
    { 
     mRotorAngle = value; 
     Refresh(); 
    } 
} 

Refresh引發Paint事件。該OnPaint事件處理程序是這樣的:

private void StatorAndRotor2_Paint(object sender, PaintEventArgs e) 
{ 
    // Draw the three bitmaps using a rotation matrix to rotate the rotor bitmap. 
    Draw((float)mRotorAngle); 
} 

但是,當我使用此代碼 - 這在其他自己的用戶控件效果很好 - 如果控制是通過雙緩衝SetStyle(ControlStyles.OptimizedDoubleBuffer, true)用戶控件不繪製的。如果我沒有將此標誌設置爲true,則控件在重繪時閃爍。

在控制構造函數中我設置:

SetStyle(ControlStyles.AllPaintingInWmPaint, true); 
SetStyle(ControlStyles.ContainerControl, false); 
// User control is not drawn if "OptimizedDoubleBuffer" is true. 
// SetStyle(ControlStyles.OptimizedDoubleBuffer, true); 
SetStyle(ControlStyles.ResizeRedraw, true); 
SetStyle(ControlStyles.SupportsTransparentBackColor, true); 

首先,我認爲它閃爍,因爲背景每次清除控制繪製。所以我設置了SetStyle(ControlStyles.AllPaintingInWmPaint, true)。但它沒有幫助。

那麼,它爲什麼閃爍?其他控件在此設置下工作得很好。爲什麼如果SetStyle(ControlStyles.OptimizedDoubleBuffer, true)沒有繪製控制。

我發現,如果我更改屬性RotorAngle後直接調用我Draw方法控制不閃爍:

public float RotorAngle 
{ 
    get { return mRotorAngle; } 
    set 
    { 
     mRotorAngle = value; 
     Draw(mRotorAngle); 
    } 
} 

但是,這導致非常糟糕的表現,特別是在全屏模式。每20毫秒不可能更新控件。你可以自己嘗試。我將在下面附上完整的Visual Studio 2008解決方案。

那麼,它爲什麼這麼差呢?每20毫秒更新其他(自己的)控件是沒有問題的。這是真的只是由於位圖?

我創建了一個簡單的可視化Visual Studio 2008的解決方案,展示了兩個問題: https://www.dropbox.com/s/mckmgysjxm0o9e0/WinFormsControlsTest.zip(289,3 KB)

有目錄bin\Debug的可執行文件。

感謝您的幫助。

+0

我不能進入所有的細節了,但你需要看看雙緩衝。幸運的是,它在2008年非常簡單,你在這裏進行刷新的方式也很糟糕。 – Ian

+0

如果您的應用程序可能在遠程桌面上使用,請不要雙緩衝 - 請參閱[此鏈接](http://blogs.msdn.com/b/oldnewthing/archive/2006/01/03/508694.aspx) 。你可以檢查當前會話是否是遠程桌面:'if(SystemInformation.TerminalServerSession)...' – Bridge

回答

4

第一次收到,每LarsTech的答案,你應該使用在PaintEventArgs提供的Graphics上下文。通過在Paint處理程序中調用CreateGraphics(),可以防止OptimizedDoubleBuffer正常工作。

其次,在你的SetStyle塊,添加:

SetStyle(ControlStyles.Opaque, true); 

...防止基類的控件在背景色填充呼喚你的油漆處理程序之前。

我在您的示例項目中測試了它,它似乎消除了閃爍。

2

在畫面上繪製Paint事件是一項沉重的操作。在內存緩衝區上繪畫比較快。

Double buffering將提高性能。不要使用繪圖繪製,而是繪製新圖形,並對其進行繪製。

一旦位圖繪製完成後,整個位圖複製到e.Graphics從PaintEventArgs的

Flicker free drawing using GDI+ and C#

4

不要使用CreateGraphics,而是使用傳遞給你從油漆事件的圖形對象:我改變了它,像這樣

,並增加了一個清晰的,因爲調整將呈現重影:

private void Draw(float rotorAngle, Graphics graphics) 
{ 
    graphics.Clear(SystemColors.Control); 
    graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; 

    // yada-yada-yada 

    // do not dispose since you did not create it: 
    // graphics.Dispose(); 
} 

從名爲:

private void StatorAndRotor2_Paint(object sender, PaintEventArgs e) 
{ 
    Draw((float)mRotorAngle, e.Graphics); 
} 

在構造函數中,打開雙緩衝,但我認爲是沒有必要的透明度:

SetStyle(ControlStyles.AllPaintingInWmPaint, true); 
SetStyle(ControlStyles.ContainerControl, false); 
SetStyle(ControlStyles.OptimizedDoubleBuffer, true); 
SetStyle(ControlStyles.ResizeRedraw, true); 
//SetStyle(ControlStyles.SupportsTransparentBackColor, true); 
0

對於我的回答,我從https://stackoverflow.com/a/2608945/455904獲取了靈感,我從LarsTechs上面找到了答案。

爲避免必須在所有OnPaints上重新生成完整圖像,可以使用變量來保存生成的圖像。

private Bitmap mtexture; 

使用繪圖()來生成紋理

private void Draw(float rotorAngle) 
{ 
    using (var bufferedGraphics = Graphics.FromImage(mtexture)) 
    { 
     Rectangle imagePosition = new Rectangle(0, 0, Width, Height); 
     bufferedGraphics.DrawImage(mStator, imagePosition); 
     bufferedGraphics.DrawImage(RotateImage(mRotor, mRotorAngle), imagePosition); 

     float normedAngle = mRotorAngle % cDegreePerFullRevolution; 

     if (normedAngle < 0) 
      normedAngle += cDegreePerFullRevolution; 

     if (normedAngle >= 330 || normedAngle <= 30) 
      bufferedGraphics.DrawImage(mLED101, imagePosition); 
     if (normedAngle > 30 && normedAngle < 90) 
      bufferedGraphics.DrawImage(mLED001, imagePosition); 
     if (normedAngle >= 90 && normedAngle <= 150) 
      bufferedGraphics.DrawImage(mLED011, imagePosition); 
     if (normedAngle > 150 && normedAngle < 210) 
      bufferedGraphics.DrawImage(mLED010, imagePosition); 
     if (normedAngle >= 210 && normedAngle <= 270) 
      bufferedGraphics.DrawImage(mLED110, imagePosition); 
     if (normedAngle > 270 && normedAngle < 330) 
      bufferedGraphics.DrawImage(mLED100, imagePosition); 
    } 
} 

有OnPaint中的覆蓋繪製質感上的控制

protected override void OnPaint(PaintEventArgs e) 
{ 
    base.OnPaint(e); 
    Rectangle imagePosition = new Rectangle(0, 0, Width, Height); 
    e.Graphics.DrawImage(mtexture, imagePosition); 
} 

覆蓋OnInvalidated()來繪製()紋理當需要時

protected override void OnInvalidated(InvalidateEventArgs e) 
{ 
    base.OnInvalidated(e); 

    if (mtexture != null) 
    { 
     mtexture.Dispose(); 
     mtexture = null; 
    } 

    mtexture = new Bitmap(Width, Height); 
    Draw(mRotorAngle); 
} 

而不是調用繪製使圖像無效。這將導致它使用OnInvalidated和OnPaint重繪。

public float RotorAngle 
{ 
    get { return mRotorAngle; } 
    set 
    { 
     mRotorAngle = value; 
     Invalidate(); 
    } 
} 

我希望我得到了所有的它在那裏:)

0

非常感謝您的幫助。

閃爍解決。 :)

現在,我按照LarsTech's的建議和使用對象PaintEventArgs

謝謝lnmx暗示CreateGraphics()裏面Paint處理程序阻止OptimizedDoubleBuffer的正確功能。這解釋了閃爍問題,即使OptimizedDoubleBuffer已啓用。我不知道,我也沒有在MSDN Library找到這個。在我以前的控件中,我也使用了對象PaintEventArgs

感謝Sallow爲您的努力。我會在今天測試你的代碼,我會提供反饋。我希望這會提高性能,因爲仍然存在性能問題 - 儘管有正確的雙緩衝。

我的原始代碼還有另一個性能問題。

改變

graphics.DrawImage(mStator, imagePosition); 
graphics.DrawImage(RotateImage(mRotor, rotorAngle), imagePosition); 

graphics.DrawImage(mStator, imagePosition); 
Bitmap rotatedImage = RotateImage(mRotor, rotorAngle); 
graphics.DrawImage(rotatedImage, imagePosition); 
rotatedImage.Dispose(); // Important, otherwise the RAM will be flushed with bitmaps.