在我的Delphi / C ++ Builder应用程序中,我有一个OnMouseMove处理程序,允许用户通过拖动绘图元素与绘图进行交互。 (我们手动实现了必要的拖放逻辑,而不是使用VCL的OnDragOver等。)
OnMouseMove事件根据绘图的当前状态更新主窗体和多个子窗体。但是,只要我移动鼠标,主窗体和任何子窗体都不会实际重绘其更新状态,除非我在窗体及其每个子窗体上手动调用Repaint。这有点脆弱,因为很容易错过需要重新绘制的子表单。
当我停止移动鼠标时,表单按预期重新绘制,因此看起来控件按预期无效,只要OnMouseMove事件/ WM_MOUSEMOVE消息进入,它们就不会重新绘制。(如果我慢慢拖动非常,然后屏幕也会按预期重新绘制。)
即使在每个表单上手动调用Repaint也不够,因为除非我单独重绘它们,否则单个子表单的控件可能不会重绘。 (例如,如果我调用其父TForm的Repaint,TEdit会显示其新值,但是我禁用的TRadioButton不会显示为禁用,除非我调用它自己的重绘。)
为什么有必要打电话给Repaint?当我拖动鼠标时,为什么Windows不会自动重新绘制我应用程序的窗口?是否有更好的重绘窗口的方法,而不是尝试手动枚举哪些窗口需要调用Repaint?
从一个简短的测试应用程序开始,我想知道问题是我的OnMouseMove事件是否足够慢,因为应用程序太忙于WM_MOUSEMOVE而没有调度WM_PAINT消息?我不确定这是否确实如此,如果是的话,该怎么办呢。
这里有一些(希望不会过于简化)代码来说明我在做什么。 GraphArea是一个TImage,其Canvas包含图。
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 :(得分:4)
有一次,我注意到在我的应用程序中使用VCL拖放的相同行为。以某种方式,发布自己或调用WM_PAINT
导致的Invalidate
消息无法到达消息队列的顶部。
而不是Repaint
,我建议使用Update
来更好地处理儿童重绘。
答案 1 :(得分:2)
Repaint()
立即重新调用它所调用的控件。有可能你的调整逻辑正在改变GraphicArea和ChildForms的方式,要求他们用新值重绘自己,但他们实际上并不知道需要重绘它们,所以他们不这样做。这可以解释为什么除非你手动触发重新绘制,否则你没有看到任何变化。
我建议使用Invalidate()
代替Repaint()
。 Invalidate()
向操作系统发出信号,表明控件需要重新绘制,但实际上并没有执行绘制。这使操作系统可以自己管理绘画,控件将直接从操作系统接收绘制请求,而不是直接从您那里接收。
答案 2 :(得分:2)
Raymond Chen explains WM_PAINT消息的工作原理。使窗口无效(无论是通过设置导致窗口无效的VCL方法或属性,还是通过调用Invalidate
手动设置)都会导致设置一个标志,说明应该传递WM_PAINT
消息下次调用GetMessage
并且没有消息可用。
据我所知,如果消息生成得足够快(例如,WM_MOUSEMOVE
消息)并处理这些消息需要足够长的时间,那么消息队列可能永远不会为空,所以{{ 1}}消息永远不会传递。
解决方案是手动调用WM_PAINT
(其执行情况应优于Update
)或类似情况。其他注意事项:
RedrawWindow
,而不是调用Repaint
或Repaint
,这可以指示孩子窗户也重绘自己。
RedrawWindow(ChildForm1->Handle, NULL, NULL, RDW_UPDATENOW | RDW_ALLCHILDREN);