2011-09-21 15 views
4

我有一個用戶控件,具有完全自定義繪製圖形的許多繪製自己的對象(從OnPaint調用),背景是一個大的位圖。我內置了縮放和平移功能,並且畫布上繪製的對象的所有座標都位於位圖座標中。OnPaint,失效,剪切和區域的最佳實踐

因此,如果我的用戶控件寬度爲1000像素,位圖寬度爲1500像素,並且我以200%縮放縮放,那麼在任何給定時間,我只會查看位圖寬度的1/3。並且在位圖上的點100,100處有一個矩形的對象將顯示在屏幕上的點200,200處,只要您將其滾動到最左側。

基本上我需要做的是創建一個有效的方式重繪只需要重繪。例如,如果我移動一個對象,則可以將該對象的舊剪輯矩形添加到某個區域,然後將該對象的新剪切矩形與同一區域合併,然後調用Invalidate(region)重繪這兩個區域。

但是這樣做意味着我必須在將對象位圖座標提供給Invalidate之前不斷地將其轉換爲屏幕座標。我必須始終假設PaintEventArgs中的ClipRectangle在屏幕座標中,以便其他窗口使我的作品失效。

有沒有一種方法可以利用Region.Transform和Region.Translate功能,以便我不需要從位圖轉換爲屏幕座標?在不妨礙在屏幕座標中接收PaintEventArgs的方式?我應該使用多個區域嗎?還是有更好的方法來做到這一點?

什麼,我現在做的示例代碼:

invalidateRegion.Union(BitmapToScreenRect(SelectedItem.ClipRectangle)); 

SelectedItem.UpdateEndPoint(endPoint); 

invalidateRegion.Union(BitmapToScreenRect(SelectedItem.ClipRectangle)); 

this.Invalidate(invalidateRegion); 

而且在OnPaint()...

protected override void OnPaint(PaintEventArgs e) 
{ 
    invalidateRegion.Union(e.ClipRectangle); 

    e.Graphics.SetClip(invalidateRegion, CombineMode.Union); 
    e.Graphics.Clear(SystemColors.AppWorkspace); 

    e.Graphics.TranslateTransform(AutoScrollPosition.X + CanvasBounds.X, AutoScrollPosition.Y + CanvasBounds.Y); 

    DrawCanvas(e.Graphics, _ratio); 

    e.Graphics.ResetTransform(); 

    e.Graphics.ResetClip(); 

    invalidateRegion.MakeEmpty(); 
} 
+3

您正在做的工作不需要完成。 Windows已經非常高效地裁剪,你不必提供幫助。如果您遇到perf問題,請重點關注位圖的像素格式。 32bppPArgb比其他任何產品快十倍。 –

+3

我不認爲你明白油漆裁剪是如何工作的。我使用GDI +繪製可以移動,調整大小,旋轉等的對象。我需要重繪像MouseMove這樣的事件,並且我需要確保只重繪實際需要更新的部分。例如,如果我更改一個對象,它不會自己重繪,我必須調用Invalidate()。如果我沒有指定要剪輯的剪輯區域,則會重新繪製整個場景。例如,您不想在MouseMove事件上重新繪製整個場景。 –

+9

哇!告訴Hans Passant,他不明白窗口剪裁的工作方式,就像告訴Jon Skeet他不瞭解C#是如何工作的! :-) –

回答

9

因爲很多人都在看這個問題,我會繼續前進,並盡我所能地回答這個問題。

隨PaintEventArgs提供的Graphics類始終由無效請求強制裁剪。這通常由操作系統完成,但可以通過代碼完成。

您無法重置此剪輯或從這些剪輯邊界轉義,但您不需要。繪畫時,除非您急需最大化表現,否則您通常不應該關注它如何被剪裁。

圖形類使用一堆容器來應用剪切和轉換。您可以使用Graphics.BeginContainer和Graphics.EndContainer自己擴展此堆棧。每次開始容器時,對變換或剪輯所做的任何更改都是臨時的,並且在BeginContainer之前配置的任何先前的變換或剪輯後應用。所以基本上,當你得到一個OnPaint事件時,它已經被剪輯,並且你在一個新的容器中,所以你看不到剪輯(你的剪輯區域或ClipRect將顯示爲無限),你不能擺脫那些剪輯邊界。

當您的視覺對象的狀態發生變化(例如,鼠標或鍵盤事件或對數據變化的反應)時,通常只需調用Invalidate()就可以了,這將重新繪製整個控件。 Windows將在CPU使用率較低的時刻調用OnPaint。每次調用Invalidate()通常不會總是對應一個OnPaint事件。在下一次繪製之前可以多次調用無效。因此,如果數據模型中的10個屬性一次全部更改,則可以在每次更改屬性時安全地調用10次失效,並且您可能只會觸發單個OnPaint事件。

我注意到你應該小心使用Update()和Refresh()。這些強制立即強制同步OnPaint。它們在單線程操作(可能更新進度條)時很有用,但在錯誤的時間使用它們可能會導致過度和不必要的繪製。

如果您想要在重畫場景時使用剪輯矩形來提高性能,則無需自己跟蹤聚合剪輯區域。 Windows會爲你做這個。只需使無效的矩形或區域失效並正常繪製即可。例如,如果您正在繪畫的對象被移動,每次您想使其舊界和它的新邊界無效,那麼除了將其繪製在新位置之外,還要重繪它原來的位置。您還必須考慮筆筆劃大小等。

而正如Hans Passant所述,請始終使用32bppPArgb作爲高分辨率圖像的位圖格式。以下是關於如何將圖像加載爲「高性能」的代碼片段:

public static Bitmap GetHighPerformanceBitmap(Image original) 
{ 
    Bitmap bitmap; 

    bitmap = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppPArgb); 
    bitmap.SetResolution(original.HorizontalResolution, original.VerticalResolution); 

    using (Graphics g = Graphics.FromImage(bitmap)) 
    { 
     g.DrawImage(original, new Rectangle(new Point(0, 0), bitmap.Size), new Rectangle(new Point(0, 0), bitmap.Size), GraphicsUnit.Pixel); 
    } 

    return bitmap; 
} 
+0

是設置分辨率需要? – GorillaApe

+0

如果要將原始位圖中的DPI信息傳輸到新位圖,則SetResolution是必需的。否則,任何DPI信息都將丟失。不需要複製原始圖像本身(像素數組)。 –