2012-07-31 95 views
3

在我的Delphi/C++ Builder應用程序中,我有一個OnMouseMove處理程序,它允許用戶通過拖動陰謀元素與陰謀交互。 (我們手動實現了必要的拖放邏輯,而不是使用VCL的OnDragOver等)爲什麼在OnMouseMove中必須調用Repaint?

OnMouseMove事件根據圖的當前狀態更新主窗體和幾個子窗體。但是,只要我移動鼠標,主窗體和任何子窗體實際上都不會重繪它們的更新狀態,除非手動在窗體及其每個子窗體上調用Repaint。這有點脆弱,因爲很容易錯過需要重新繪製的兒童表格。

即時我停止移動鼠標,表單重新繪製按預期的方式,所以它看起來控件正在作爲預期失效,只要OnMouseMove事件/ WM_MOUSEMOVE消息進來,它們就不會重新繪製。(If我很慢地拖動非常,然後屏幕也會按預期重新繪製。) (例如,如果我調用其父母TForm的重畫,則TEdit會顯示其新值,但我禁用的TRadioButton不會顯示爲禁用,除非我將其稱爲它自己的重繪。)

爲什麼有必要根本打電話給Repaint ?爲什麼當我拖動鼠標時,Windows不會自動重新繪製應用程序的窗口?有沒有更好的方法來重繪窗口比嘗試手動枚舉哪些窗口需要重繪調用?

從一個簡短的測試應用程序,我想知道是否問題是我的OnMouseMove事件足夠慢,WM_PAINT消息不會得到調度,因爲應用程序是忙於WM_MOUSEMOVE?我不確定這是否確實如此,或者如果是這樣的話,該怎麼辦。

下面是一些(希望不是過於簡化)代碼來說明我在做什麼。 GraphArea是Canvas包含圖的TImage。

void __fastcall TMachineForm::GraphAreaMouseDown(TObject *Sender, 
    TMouseButton Button, TShiftState Shift, int X, int Y) 
{ 
    if (IsNearAdjustableObject(X, Y)) { 
     is_adjusting = true; 
    } 
} 

void TMachineForm::GraphAreaMouseMove(TObject *Sender, 
    TShiftState Shift, int X, int Y) 
{ 
    if (is_adjusting) { 
     AdjustObject(X, Y); 

     /* Draws to the GraphArea TImage by calling GraphArea->Canvas methods */ 
     RedrawGraphArea(); 

     /* Updates several standard VCL controls on ChildForm1 and ChildForm2; 
     * e.g., ChildForm1->Edit1->Text = CalculatedValue(); */ 
     NotifyChildForm1OfAdjustment(); 
     NotifyChildForm2OfAdjustment(); 

     /* This is where I have to manually call Repaint. I don't know why. */ 
     GraphArea->Repaint(); 
     ChildForm1->Repaint(); 
     ChildForm2->Repaint(); 
    } 
} 

void TMachineForm::GraphAreaMouseUp(TObject *Sender, 
    TMouseButton Button, TShiftState Shift, int X, int Y) 
{ 
    is_adjusting = false; 
} 
+0

我想這是因爲拖動操作在一個特殊的消息循環內運行,而不是類似於模態消息循環。 – 2012-07-31 21:46:10

+0

@DavidHeffernan - 即使我是從OnMouseMove處理程序手動實現自己的拖放操作,而不是調用任何VCL拖動操作? – 2012-07-31 21:47:39

+2

如果你不瞭解你的手動拖放操作,那真的很難說。通常情況下,您不必調用Repaint(並且不應該放在首位 - 父控件上的「Invalidate」通常足以更新它並且它是子控件)。 – 2012-07-31 21:48:12

回答

2

Raymond Chen explains WM_PAINT消息如何工作。使窗口無效(無論是通過設置VCL方法還是導致窗口無效的屬性,或通過調用Invalidate手動設置),都會有效地導致設置一個標誌,說明WM_PAINT消息應在下次調用GetMessage時傳遞並且沒有消息可用。

據我所知,如果消息生成速度足夠快(例如,WM_MOUSEMOVE消息)並且處理這些消息花費足夠長的時間,那麼消息隊列可能永遠不會爲空,因此WM_PAINT消息不會被傳遞。

解決的方法是手動調用Update(它應該比Repaint好一點)或類似的。其他注意事項:

  • 重繪擁有的所有形式:我還沒有找到一個乾淨的解決方案在這裏,所以我可能會繼續人工跟蹤資的形式和他們手動調用重繪。 (如果需要,我可以迭代TForm的組件來尋找TForms,但是這會增加可測量的開銷。)
  • 處理子控件(例如在我的示例中不會將自身重繪爲禁用的TRadioButton):而不是調用RepaintUpdate,使用RedrawWindow,它可以指示子窗口重繪自己。
     
    RedrawWindow(ChildForm1->Handle, NULL, NULL, 
        RDW_UPDATENOW | RDW_ALLCHILDREN); 
    
+0

如果只是使窗口的某些部分無效(而不是整個表面與無效),則更新只會比重新繪製好一點。 – 2012-08-01 15:41:50

2

Repaint()立即重新繪製所調用的控件。很可能您的調整邏輯正在對GraphicArea和ChildForms進行更改,以便需要用新值重新繪製自己,但實際上他們並不知道需要重繪它們,因此它們不會這樣做。這將解釋爲什麼你沒有看到任何改變,除非你手動觸發重繪。

我建議使用Invalidate()而不是Repaint()Invalidate()向操作系統發出信號,表明控件需要重新繪製,但實際上並未執行繪畫。這可讓操作系統在自己的時間管理繪畫,並且控件將正常接收來自操作系統的繪畫請求,而不是直接從您那裏接收繪畫請求。

+0

'Invalidate()'不起作用,顯然是因爲鼠標消息太快,操作系統無法發送'WM_PAINT'請求。 – 2012-08-01 14:34:23

+0

您可以調用每個控件的Update()方法來一次處理一個待處理的繪畫請求,或者您可以單獨調用Form的Update()方法來一次處理Form和Children的所有待處理的繪製請求。 – 2012-08-01 17:21:58

+0

我試圖避免必須手動列出每個需要更新的表單和控件;這似乎很難保持。正如我試圖在我的問題中解釋的,調用窗體的「更新」重新繪製了一些但不是所有的孩子。 (如果需要,'RedrawWindow'可以正確重新繪製所有的子項。) – 2012-08-01 17:34:34

4

有一次,我注意到我的應用程序中使用VCL拖放的相同行爲。不知怎的,WM_PAINT因發佈自己或致電Invalidate而產生的消息沒有達到消息隊列的頂部。

而不是Repaint,我建議使用Update應該更好地處理兒童重新粉刷。

相關問題