3

I open Form2.ShowModal in FormMain. I want the application to show Form2 intact while doing some database access (this is not about the new data to be shown). However, while FormShow is executed, just the outer border and some broken parts are displayed, some broken parts of FormMain show through. It's ugly.

I have not been able to find a way to make Delphi repaint the Form immediately and then doing the time-consuming MyOpenData procedure. After concluding MyOpenData everything is fine.

procedure TForm2.FormShow(Sender: TObject);
begin
  Invalidate; 
  Refresh;
  MyOpenData; { needs some seconds of database accesses }
end;

Alternative:

procedure TForm2.FormShow(Sender: TObject);
begin
  Invalidate;
  Refresh;
  SendMessage(Handle, wm_paint, 0, 0);
  PostMessage(Handle, wm_OpenMyData, 0, 0); { executes well, but no solution)
end;

This doesn't work either. I thought SendMessage() waits for the message being done. But no Paint is done before MyOpenData. The form always looks broken till the procedures finishes. Besides this, the routines are executed fine. I tried all these commands combined or separately.

What am I missing? Thanks in advance!

How do you start time-consuming routines that need to run when opening a form?

(Delphi XE7 on Windows 7 64 bit)

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
HJay
  • 193
  • 5
  • 13
  • 2
    You should use a `class(TThread)` to prepare this data in background without breaking the user's interation with the form. I don't do Delphi in many years so I'll just leave you to that. Here are a couple examples: http://stackoverflow.com/a/3456816/156811 – Havenard Jul 21 '15 at 23:30
  • 1
    In the *alternative*, remove the first three lines and it's your solution. What do you mean by *no solution*? – Sertac Akyuz Jul 21 '15 at 23:34
  • @Havenard: MyOpenData() is not thread-safe, but accesses the Form and several objects quite a lot. However, it is supposed to run AFTER the form has repainted itself to look intact. That's what my question is about. – HJay Jul 21 '15 at 23:35
  • @Sertac: I tried all the attempts combined and separately. It just doesn't work. The form looks broken until MyOpenData finishes. It should be possible to force the form to repaint itself before even starting MyOpenData. – HJay Jul 21 '15 at 23:38
  • 3
    @HJay - Deferring processing is the correct course of action, your form isn't even visible in OnShow. Remove the first three lines in the second snippet, in procedure OpenMyData call 'Update' as the first statement. Then open your data. – Sertac Akyuz Jul 21 '15 at 23:44
  • @HJay Threads are thread-safe as long as you make them be this way. Threads in Delphi are particularly easy to be made safe, with the method `Synchronize`. Check http://delphi.about.com/od/kbthread/a/thread-gui.htm – Havenard Jul 22 '15 at 00:29
  • 1
    Deferring execution will allow the window to be painted. Of course, you'll still be blocking the main thread and so your window will be unresponsive and likely ghosted. Move the long running code to a thread. Don't ever try to synthesise paint messages like that. You can't. Only the system can. – David Heffernan Jul 22 '15 at 06:56
  • Our preferred solution is to add a custom message WM_AFTERSHOW that is posted within the OnShow event. Do the data open stuff there. The better solution of course is doing that in a thread which never blocks! – mrabat Jul 22 '15 at 07:29
  • @Sertac: Well, moving Update to the message procedure improved the situation drastically. Not perfect, but quite well. – HJay Jul 22 '15 at 08:22
  • @mrabat: Do you mean like in my alternative code example? That didn't work either. – HJay Jul 22 '15 at 08:23
  • @David: Like in my alternative code example or what do you mean? -- Yes, it appears that it's really not possible to get the window shown and then do some stuff. Strange, because very many people need such routines to be exuted after FormShow? – HJay Jul 22 '15 at 08:25
  • Its certainly possible to get the window painted, and then launch an activity. Deferring with a posted message is the way. But you are going to block your UI thread. Don't do that. – David Heffernan Jul 22 '15 at 08:29
  • @David: So what is the best way to repaint and then launch an acitivity in your experience (without using threads)? – HJay Jul 22 '15 at 08:33
  • @David: Using threads is difficult if the data are meant to be displayed one after the other. Avoiding VCL access is in many cases a lot of efforts. -- Blocking the UI is not the worst, if he user cannot do anything without the data being ready anway, I believe. But of course, you are right, in a perfect world, the UI would not block but being disabled in some way. – HJay Jul 22 '15 at 08:35
  • Threads are the answer. Stop hiding from the truth. – David Heffernan Jul 22 '15 at 08:37
  • @HJay, what do you mean by "moving Update to the message procedure improved the situation drastically". What more do you need? the same could be done OnActivate event (followed by Update and MyOpenData) – kobik Jul 22 '15 at 08:37
  • @kobik: Yes, I am satisfied so far. This will have to do. Thanks a lot to all participants. -- OnAcitivate is called much to often than to handle initial routine. What's missing is an "OnFormShowFinished" event... after the form is painted, activated... then call a routine. – HJay Jul 22 '15 at 10:28
  • @HJay, You would set an initial flag OnAcitivate on the first time. the form becomes fully visible on this event. – kobik Jul 22 '15 at 10:55
  • @HJay, I think your problem is something else. The answer by charles should work. We never have any issues using this method. Note that if the form is busy and it is obscured and then made visible again, it will go back to partially (or not) painted. – Rohit Gupta Jul 22 '15 at 21:32
  • @HJay: Yes but without any of your refreshes. For me this aftershow mechanism works - it shows the underlying form and then the message is processed. But of course it's only painted once and the freezes for the longer processing (is that what you want to avoid???) Anyway - I think threads are the answers. – mrabat Jul 23 '15 at 13:37
  • Three answers with all negative votes. Very interesting. You'd think the people here know a thing or two, but it looks like egos are running the show instead of experience. – David Schwartz Jul 25 '15 at 08:43

2 Answers2

0
uses
WinApi.Windows;

const
WM_AFTER_SHOW     = WM_USER + 1; // custom message
WM_AFTER_CREATE   = WM_USER + 2; // custom message

private

procedure WmAfterCreate(var Msg: TMessage); message WM_AFTER_CREATE;
procedure WmAfterShow(var Msg: TMessage); message WM_AFTER_SHOW;


procedure TForm1.WmAfterCreate(var Msg: TMessage);
begin
 DoSomeThingAfterCreate();
 ShowMessage('WM_AFTER_CREATE received!');
end;

procedure TForm1.WmAfterShow(var Msg: TMessage);
begin
  DoSomeThingAfterShow();
  ShowMessage('WM_AFTER_SHOW received!');
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
 // Some code...
PostMessage(Self.Handle, WM_AFTER_CREATE, 0, 0);
end;

procedure TForm1.FormShow(Sender: TObject);
begin
 // Some Code...
 PostMessage(Self.Handle, WM_AFTER_SHOW, 0, 0);
end;
charles
  • 21
  • 1
    'PostMessage(Self.Handle, WM_AFTER_SHOW, 0, 0);' is exactly what I do in my alternative code example. It did not solve the issue. However, including an 'Update;' in AfterShow solved it almost. – HJay Jul 22 '15 at 10:33
  • 1
    You don't need two of them, both messages will be retrieved consecutively. – Sertac Akyuz Jul 22 '15 at 11:34
-2

There simply isn't enough information given to make any specific recommendations here, IMO.

I'm going to guess that MyOpenData() sets up some kind of data state that Form2 relies upon. If this is the case, then you probably want to call it BEFORE calling Form2.ShowModal. In no case should you be calling either invalidate or refresh inside of the OnShow handler because they both trigger OnShow.

Watch the video I made for CodeRage 9 entitled, "Have You Embraced Your Software Plumber Lately?" (search YouTube for 'coderage software plumber') as this is the exact topic I'm addressing in this video -- the whole plethora of issues involved in initializing forms and objects, along with the timing issues specific to forms.

I don't discuss issues with data-aware controls specifically, but they're pretty much the same. It can be problematic setting up the DB state from inside the form that depends upon that state in order to properly initialize itself. There's an inherent race-condition that is easily avoided by doing your dependent initialization first, then injecting that dependency into the form.

If DBs are involved, you have to inject SOMETHING into the form: either the DB reference (usually through a global variable); a table (either a global variable or a form variable); or a current record (usually a form variable). The nice part about using DB-aware controls is that the initialization is always implicit, and you don't have to inject anything. The bad part about using DB-aware controls is, the initialization is ALWAYS IMPLICIT, and you have no EXPLICIT control over initialization sequences. By doing EXPLICIT injection of DB dependencies, you side-step the timing issues. It's a little more work (not much), but you don't have to deal with stuff like this.

In any case, if the form needs a current record to initialize its fields, you can't display the form until the record has been selected, and you can't make the record selection part of the form's initialization process without risking concurrency issues. It can be done, but you're making one heck of a mess out of it.

David Schwartz
  • 1,756
  • 13
  • 18
  • Why would Invalidate trigger OnShow. I'd expect it to trigger OnPaint. Also, the issue the asker faces seems unrelated to you video. The issue is nothing more than our old friend of a long running task blocking the GUI thread. – David Heffernan Jul 22 '15 at 06:59
  • The issue is not predominantly about blocking the GUI, but about how to get the form to paint itself completely before going to the next command. It's a pity there is no command to force the form to actually paint itself in exactly that moment. That is the issue. -- Of course you are right, time-consuming routines could be done in a separate thread if the necessary actions were thread-safe, which they are not in my case. That's why a thread is no solution here. – HJay Jul 22 '15 at 10:31
  • The thread is the solution. The GUI can't paint itself if its thread is blocked. – David Heffernan Jul 22 '15 at 13:26
  • "The issue is ... about how to get the form to paint itself completely before going to the next command." It would be really helpful to know how the form fields are getting initialized. Are they DB-aware components, or just plain components filled some other way? You have a classic race-condition where the fields on the form are being initialized at the same time as the form is trying to render them. OnShow is the wrong place to put code that INITIALIZES the fields! They need to be initialized BEFORE OnShow is called, IMHO. OnCreate may be the best place to do that. Where's the data from? – David Schwartz Jul 24 '15 at 19:59
  • A thread is certainly one solution, for sure. But restructuring your design slightly so things occur in the expected sequence is probably easier. – David Schwartz Jul 24 '15 at 20:00
  • @David No amount of restructuring can help if the UI thread is blocked – David Heffernan Jul 25 '15 at 06:58
  • I used to work on an editor that did a ton of string processing in the domain of genetic and genomic analysis. Some of the statistical functions would run for minutes. We used no threads anywhere, and had no problems with the UI hanging-up. It was 100% Delphi. Another big app I worked on did page layouts with re-flows that would take 15-30 seconds. No threads were used, and the UI never blocked. Another app would issue back-end queries prior to opening the data in a form; they'd take 10-15 secs. Nothing ever locked up. No data-aware controls used. It's all in how you architect the solution. – David Schwartz Jul 25 '15 at 08:35