In my Delphi / C++Builder app, I have an OnMouseMove handler that lets the user interact with a plot by dragging plot elements. (We’ve manually implemented the necessary drag-and-drop logic instead of using the VCL’s OnDragOver et al.)
The OnMouseMove event updates both the main form and several child forms based on the current state of the plot. However, as long as I’m moving the mouse, neither the main form nor any of the child forms actually redraw their updated state unless I manually call Repaint on the form and each of its child forms. This is somewhat fragile, since it’s easy to miss a child form that needs to be repainted.
The instant I stop moving the mouse, the forms repaint as expected, so it appears that controls are being invalidated as expected, they’re just not getting repainted as long as OnMouseMove events / WM_MOUSEMOVE messages are coming in. (If I drag very slowly, then the screen will also repaint as expected.)
Even manually calling Repaint on each form isn’t always enough, because individual child forms’ controls may not redraw unless I repaint them individually. (For example, a TEdit displays its new value if I call its parent TForm’s Repaint, but a TRadioButton that I disable doesn’t appear disabled unless I call its own Repaint.)
Why is it necessary to call Repaint at all? Why isn’t Windows automatically repainting my app’s windows while I drag the mouse? Is there a better way of redrawing windows than trying to manually enumerate which windows need to have Repaint called?
From playing around with a brief test application, I’m wondering if the problem is that my OnMouseMove event is slow enough that WM_PAINT messages don’t get dispatched because the application is too busy with WM_MOUSEMOVE? I’m not sure if this is indeed the case or, if so, what to do about this.
Here’s some (hopefully not overly simplified) code to illustrate what I’m doing. GraphArea is a TImage whose Canvas contains the plot.
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;
}
Raymond Chen explains how WM_PAINT messages work. Invalidating a window (whether by setting VCL methods or properties that cause the window to be invalidated, or manually, by calling
Invalidate) effectively causes a flag to be set saying that aWM_PAINTmessage should be delivered the next timeGetMessageis called and no messages are available.As far as I can tell, if messages are being generated fast enough (for example, the
WM_MOUSEMOVEmessages) and processing these messages is taking long enough, then the message queue may never be empty, soWM_PAINTmessages are never delivered.The solution is to manually call
Update(which should perform a bit better thanRepaint) or similar. Additional considerations:RepaintorUpdate, useRedrawWindow, which can instruct child windows to redraw themselves as well.RedrawWindow(ChildForm1->Handle, NULL, NULL, RDW_UPDATENOW | RDW_ALLCHILDREN);