tags:

views:

646

answers:

3

I'm using Peter Below's PBThreadedSplashForm to display during application startup. It gets updated as various databases are opened during the creation of the data module (just before the main form is created).

The problem is that we have a check for the existence of certain things that is done during the creation of the data module. If any of those items are missing, a modal dialog is shown so that either the item can be created or the application closed. (The missing items are typically indexes, and the dialog is primarily used when we reindex databases, so it's aimed at the developers and not normal users.) However, the modal dialog is displayed behind the splash screen; it appears to be the focused window, as the caption changes, but it displays behind the splash screen.

The splash screen is created using the Windows API CreateWindowEx() from a different thread than the main process, using the following code:

procedure TPBSplashThread.CreateSplashWindow;
const
  TopmostStyle: Array [Boolean] of DWORD = (0, WS_EX_TOPMOST );
  NoActivateStyle : Array [Boolean] of DWORD = (0, WS_EX_NOACTIVATE );
var
  wsize: TSize;
begin
  wsize.cx := FSurface.Width + GetSystemMetrics( SM_CXEDGE ) * 2;
  wsize.cy := FSurface.Height + GetSystemMetrics( SM_CYEDGE ) * 2;
  FWnd := CreateWindowEx(
            TopmostStyle[ FTopmost ] or WS_EX_TOOLWINDOW
            or WS_EX_STATICEDGE or WS_EX_CLIENTEDGE
            or NoActivateStyle[ Win32MajorVersion >= 5 ],
            MakeIntResource( FWndClass ),
            nil,
            WS_POPUP or WS_BORDER,
            Forigin.x, Forigin.y,
            wsize.cx, wsize.cy,
            0, 0, hInstance, self );
  If FWnd = 0 Then
    raise exception.create('TPBSplashThread.CreateSplashWindow: CreateWindowEx failed');
end;

FTopmost is a property (with the obvious meaning) that is never set, so it's False due to the default initialization during class construction. (I've also tried explicitly setting it to False, just in case, with no effect.) I've also tried using SetForegroundWindow() and SetWindowPos() during the OnShow event of the modal dialog with no effect.

Does anyone have any idea what might be causing this?

A: 

If you're looking for an explanation, I can't help you.

If you're looking for a solution, you're not the first. Eddie Shipman encountered this same problem in May. His proposed solution was to make the other dialog (the one hidden by the splash screen) be a topmost window, but he ultimately avoided the issue by hiding the splash screen before the application needed to display any other windows.

Another suggestion was to post commands to the splash screen and have it display the message box. The dialog could be parented to the splash screen. It's tricky, though, because you're not afforded any of the luxuries of the VCL anymore since it doesn't work outside the main thread.

Rob Kennedy
I saw Eddie's post. The problem there is that the dialog is actually created by a TDataSet descendent that has no knowledge of the splash form. The splash form is created in the DPR using ShowSplashScreen(), Application.CreateForm() is called twice (once for the datamodule, once for the main form), and then the splash form is freed using HideSplashForm(). In the datamodule's OnCreate event, the TDataSet.Open method checks for the missing indexes and calls a method that displays the modal dialog; at that point, it has no idea of who called Open or where in the parent process it is happening.
Ken White
A: 

You should be able to fix this by parenting the dialog to the splash screen. Assign the splash screen's HWND to a global variable, and override the dialog's CreateParams method. If the global HWND has a value assign it to the Params.WndParent variable that CreateParams passes in. The fact that they're from different threads shouldn't matter, since it's just dealing with the HWND, not a VCL object, and Windows will handle the synchronization.

Craig Peterson
Thanks for the idea. That would probably work well, except for the fact that the problem dialog is being displayed by a *component*. The component knows nothing about the splash screen; in fact, most of the apps that use the component don't display a splash screen. This one does because it opens quite a few tables (the main part of the UI is a DBGrid that has lookup fields and some other tables connected via MasterSource/MasterFields). So the component can't be modified much; I tried changing the dialog's FormStyle to fsStayOnTop, but that did nothing.
Ken White
+1  A: 

Ok. Finally got the problem solved. It appears to be caused by the WS_EX_NOACTIVATE flag in the call to CreateWindowEx(). Changing it to remove that seems to solve the problem; the modal dialog displays above the splash screen, and because it's modal the splash screen can't be brought above it.

The working code is:


  FWnd := CreateWindowEx(
            TopmostStyle[ FTopmost ] or WS_EX_TOOLWINDOW
            or WS_EX_STATICEDGE or WS_EX_CLIENTEDGE,
            MakeIntResource( FWndClass ),
            nil,
            WS_POPUP or WS_BORDER,
            Forigin.x, Forigin.y,
            wsize.cx, wsize.cy,
            0, 0, hInstance, self );

Thanks, Rob and Craig, for the efforts.

Ken White