【发布时间】:2012-07-31 21:37:37
【问题描述】:
在我的 Delphi / C++Builder 应用程序中,我有一个 OnMouseMove 处理程序,它允许用户通过拖动绘图元素与绘图进行交互。 (我们手动实现了必要的拖放逻辑,而不是使用 VCL 的 OnDragOver 等。)
OnMouseMove 事件根据绘图的当前状态更新主窗体和几个子窗体。但是,只要我移动鼠标,主窗体和任何子窗体都不会真正重绘它们的更新状态,除非我在窗体及其每个子窗体上手动调用 Repaint。这有点脆弱,因为很容易错过需要重新绘制的子窗体。
在我停止移动鼠标的那一刻,表单按预期重新绘制,因此控件似乎按预期无效,只要 OnMouseMove 事件/WM_MOUSEMOVE 消息进入,它们就不会重新绘制。(如果我慢慢拖动非常,然后屏幕也会按预期重新绘制。)
即使在每个窗体上手动调用 Repaint 也总是不够的,因为除非我单独重绘它们,否则单个子窗体的控件可能不会重绘。 (例如,如果我调用其父 TForm 的 Repaint,TEdit 会显示其新值,但我禁用的 TRAdioButton 不会显示为禁用,除非我调用它自己的 Repaint。)
为什么需要调用 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;
}
【问题讨论】:
-
我猜这是因为拖动操作在一个特殊的消息循环中运行,而不是类似于模式消息循环。
-
@DavidHeffernan - 即使我从 OnMouseMove 处理程序手动实现自己的拖放操作,而不是调用任何 VCL 拖动操作?
-
如果不了解您手动实现拖放的任何内容,这真的很难说。通常,您不必调用
Repaint(并且不应该首先调用-父控件上的Invalidate通常足以更新它并且它是子控件)。 -
好吧,我假设你的意思是你打电话给
Repaint,因为Invalidate不好。如果您在窗口上进行自定义绘图,我希望您使用InvalidateRect来确保所有受拖动影响的区域都有机会自己绘制。在不调用Repaint的代码版本中,你叫什么?显示代码意味着我们不必猜测您在做什么。请记住,我们看不到您的屏幕。 -
仍然没有足够的代码。不清楚为什么 3 次
Repaint调用之前的代码会导致绘制周期。此外,您不需要捕获鼠标以确保您始终收到MouseUp。很可能鼠标移动事件的处理速度比它们发布的速度慢,因此 WM_PAINT 永远不会被处理。请记住,WM_PAINT 是一个低优先级事件,因此队列必须为空才能处理。如果你慢慢拖动会发生什么?
标签: delphi c++builder repaint