tags:

views:

1293

answers:

6

In Delphi 2006, I am showing a modal form. User input in that form can change data that might be displayed currently on the parent form which is the mainform. To reflect those changes I need to force a repaint of some ownerdrawn components on the mainform. I tried to do that like this from the modal form:

MainForm := Application.MainForm;
MainForm.Invalidate;
MainForm.Update;

That did not change a bit. I always thought an "update" on the form would always repaint it right away - apparently not so. The painting code itself should be ok since I can move the modal form over those ownerdraw components to force a manual repaint.

But how can I force the repaint programmatically when the data changes?

Edit: I will try Application.ProcessMessages and Refresh next week, thanks for the suggestions.

Sorry for taking so long to answer and thanks to all who responded. Calling Refresh() was part of the solution but it had to be done on the custom draw components, not on the form they were on... Now I would like to accept more than one answer ;-)

A: 

Is it possible that you are doing something in the child form to block messages? Does adding:

Application.ProcessMessages;

to your code make any difference?

Kluge
+1  A: 

Update sends a WM_PAINT message. Refresh forces a repaint of the control by Perform'ing a paint message. Try a .Refresh instead.

Darian Miller
Sorry for answering that late. Unfortunately .Refresh did not work either. No effect. Still the data is changed and if I move the modal dialog in front I can force the repaint then.
deepc
+1  A: 

Are the edits on the modal form directly writing to the controls on the parent form? If so then they should update automatically.

Does the parent form "white-out" (i.e. stop painting all together) when you move the modal form over it? If so, then there is something else wrong with either the way you are calling the modal form, or as Kluge suggested, you are blocking messages from being sent (maybe a threading issue even.)

I tested this, and it works by default. You need two forms, one with an edit box, the other with a button. Then assign these event handlers:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Form2.ShowModal;
end;

and

procedure TForm2.Edit1Change(Sender: TObject);
begin
  Form1.Button1.Caption := Edit1.Text;
end;

And as you change Edit1 on the second form it will change the caption of the button on Form1.

Jim McKeeth
"Does the parent form "white-out" (i.e. stop painting all together) when you move the modal form over it?": no it does not, instead it paints the changed data - as I wrote in the question. Painting works, I just don't know how to trigger it.
deepc
A: 

I would guess your modal form is probably blocking messages. If you're in some kind of loop, doing some kind of processing trying to update the mainform with progress. Application.ProcessMessages is one way of getting those messages to be handled, but imho not very elegant. When I've had this type of problem before, I've implemented the processing in the Application.OnIdle event. Basically, what you need to do is split up your processing into small chunks. So let's say you are processing some loop. Make one iteration of the loop as one chunk of the task. put that code in a method with the following signature :

procedure DoIdle(Sender: TObject; var Done: Boolean);

The make sure Done is set to False. If your code was previously :

for i := 1 to ProcessCount do
  DoProcess(i);

this then becomes :

procedure MyDoIdle(Sender: TObject; var Done: Boolean);
begin
  Inc(TaskCount);
  If TaskCount <= ProcessCount then
    DoProcess(TaskCount);
end;

and set things up as follows :

TaskCount := 0;
Application.Idle := MyDoIdle;

The code is then run whenever the Application is Idle, and the message loop is handled as normal. Remember to set the Application.OnIdle to nil when done.

If DoProcess is a little too fast you can elect to do say 5 or 10 iterations per call to onidle.

Steve
Thanks - but this is not about some background tasks / threads. The user simply has changed global data and it becomes effective immediately because I do not have a way to cancel it when the modal form closes. The changed global data should repaint the mainform which currently displays it.
deepc
+1  A: 

Maybe worth mentioning that if you are using a skinning library that can affect things too. I found that I had to get the skinning library to refresh, not the form.

mj2008
I am not using a skinning library but the custom draw components might be comparable to this. I had to find those components "manually" and refresh them explicitly.
deepc
+1  A: 

Try the below code. Just insert this snippet into your own code, then call it from anywhere at any time (but probably not from a thread, that might get messy without synchronizing). It paints all windows in the app without relying on the message loop to do it.

procedure UpdateApplication;
// Updates (repaints where nesc) all windows of the app
  function UpdateWindow(hWnd: HWND; LParam: longint): bool; stdcall;
  begin
    Result := True;
    Windows.UpdateWindow(hWnd);
  end;
begin
  EnumWindows(@UpdateWindow, 0);
end;
Thanks - I'll keep that in mind. For now the problem was solved with .Refresh (and I had to execute it directly on the component to be refreshed, not on the form which contained the component).
deepc