Borderless TForm with drop shadow - windows

I have made a TForm derivative that acts like the drop down part of a combo, or a hint window, or a popup menu - a temporary thing. It has no caption - its BorderStyle is set to bsNone. The form is displayed non-modally using Show, having set its position.
To make it stand out, it needs a drop shadow around its border. However, a consequence of setting its border to bsNone is that the drop shadow disappears.
Various Google sources suggest variations of this:
procedure TdlgEditServiceTask.CreateParams(var Params: TCreateParams);
const
CS_DROPSHADOW = $00020000;
begin
inherited;
{ Enable drop shadow effect on Windows XP and later }
if (Win32Platform = VER_PLATFORM_WIN32_NT) and
((Win32MajorVersion > 5) or
((Win32MajorVersion = 5) and (Win32MinorVersion >= 1))) then
Params.WindowClass.Style := Params.WindowClass.Style or
CS_DROPSHADOW;
end;
but it doesn't work - the shadow is not displayed (unless I also set a resizable border with WS_THICKFRAME set, which looks terrible). This is a popup window, not a child window, so I don't see why it should fail.
Suggestions please?
NB: this is a similar question to this question, which remains unanswered.
NB2: There is an obscure VCL component called TShadowWindow that looks like it will do the right thing, but turns out to be too crudely written to be practical.
Update: Following Andreas' comments below, I have investigated this further, and found some niceties.
Under Windows 7, I discovered that the shadow does not appear when the popup window if it is over another window from the same application.
Here is a simple Delphi app, which uses CreateParams on a popup window to request a shadow as described above.
See how the drop shadow appears where it extends beyond the main window?
But I want to use the borderless window as a popup over the main window. The drop shadow distinguishes the popup from the window underneath. All my description up above refers to this circumstance. Obviously some Windows mechanism is interfering here.
I have also tried the same application under Windows XP. Here is how it looks.
This works correctly with shadow everywhere*. Gah!
So it would seem to be a Vista/W7 thing, as Andreas suggests.
(*An earlier version of this text and screendump suggested that no shadow appeared. However, this turned out to be because I had the Windows XP display option 'Shadows under menus' turned off. Duh.)

Found it! Here is the proof:
As you can see, the drop shadow now shows properly over the form.
The problem was one of Z-order. It turns out that the shadow is itself a separate window maintained by Windows itself. In Windows 7, it seems to show the shadow underneath the main window. In order to get it to display properly, one needs to move it up.
A genius called Łukasz Płomiński explained this in a thread in the Embarcadero newsgroup. Here is his code to sort it out:
procedure TForm1.FixSysShadowOrder;
function FindSysShadowOrderProc(WindowHandle: HWND; // handle to window
Form: TForm1 // application-defined value, 32-bit
): BOOL; stdcall;
var
Buffer: array [0 .. 255] of char;
Rect: TRect;
begin
Result := True;
if IsWindowVisible(WindowHandle) then
begin
// this code search for SysShadow window created for this window.
GetClassName(WindowHandle, Buffer, 255);
if 0 <> AnsiStrComp(Buffer, PChar('SysShadow')) then
Exit;
GetWindowRect(WindowHandle, Rect);
if (Rect.Left <> Form.Left) or (Rect.Top <> Form.Top) then
Exit;
Form.FSysShadowHandle := WindowHandle;
// stop enumeration
Result := False;
end;
end;
begin
if not(csDesigning in ComponentState) and
((GetClassLong(Handle, GCL_STYLE) and CS_DROPSHADOW) = CS_DROPSHADOW)
and IsWindowVisible(Handle) then
begin
// for speed, proper SysShadow handle is cached
if FSysShadowHandle = 0 then
EnumThreadWindows(GetCurrentThreadID(), #FindSysShadowOrderProc,
lParam(Self));
// if SysShadow exists, change its z-order, and place it directly below this window
if FSysShadowHandle <> 0 then
SetWindowPos(FSysShadowHandle, Handle, 0, 0, 0, 0,
SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOOWNERZORDER or SWP_NOSIZE);
end;
end;
You have to work out when to call FixSysShadowOrder(), because Z orders change, and it won't stay right. Łukasz suggested calling it in an idle routine (for example when updating an Action), and on receipt of WM_WINDOWPOSCHANGED message.

For making drop shadow to work we have to invoke SystemParametersInfo win32 API with SPI_SETDROPSHADOW parameter, to turn on the entire system's drop shadow effect, for more information, please refer to:
SystemParametersInfo

"It works on my computer."
(High-res)
But it is quite funny, for I have a faint memory of making the same conclusion as you make, that is, that CS_DROPSHADOW does not work without the thick, resizable, frame. Are you still running Windows Vista, perhaps?

Related

How to ungroup popup window from the application on the taskbar?

Is it possible to instruct the shell taskbar to exclude a certain hwnd popup window from the main application's button "group"?
I have a "stopwatch" popup window. On my machine, with taskbar button combining disabled, the windows appears exactly as i'd like it: a separate item on the taskbar:
But if the user uses (the default, and most corporations prevent users from altering their personal preferences), the separate window is not visible:
Now, i am using ITaskbarList3.SetOverlayIcon to specify an overlay icon for my popup window:
list: ITaskbarList3;
list := CoTaskbarList3.Create;
list.SetOverlayIcon(windowHandle, ico, '');
so Windows will at least do me the favour of picking the most recent overlay icon, and applying it to the combined visual group - which is nice.
But i'd still prefer to have this separate action in a separate item on the taskbar.
One horrible workaround would be to ship another executable with my application; one just to fool the grouper into putting this other hwnd in its own group. But that's not something i want to do.
Why would i think i'm allowed to do this?
I was surprised to learn that you are allowed to prevent the user from pinning an application to the taskbar.
And while the MSDN page on shell programming doesn't give an example specifically for what i want, that doesn't mean it's not out there. It just might mean that it's all poorly documented.
In my case, the "operator" has a stopwatch. When they start the stopwatch, i pop out the form of the application group by giving it a different User Model Application ID:
SetWindowAppModelUserID('Contoso.Frobber.Stopwatch');
and immediately the form pops out:
When the user stops (or pauses) the stopwatch, i let the window coalesce back into the application:
SetWindowAppModelUserID('');
Caveats
You don't actually set the AppModelUserID to an empty string; that is not a valid application identifer. Instead you set it to VT_EMPTY. My helpful wrapper function checks for '', and converts it into a VT_EMPTY value.
Another warning comes from the SDK:
A window's properties must be removed before the window is closed. If this is not done, the resources used by those properties are not returned to the system. A property is removed by setting it to the PROPVARIANT type VT_EMPTY.
This means that care needs to be taken to remove a custom property applied to an HWND before i destroy it. A somewhat daunting task; one that i will almost certainly screw up.
The Code
function TFormEx.SetWindowAppModelUserID(const AppModelUserID: WideString): HRESULT;
var
ps: IPropertyStore;
value: OleVariant;
const
PKEY_AppUserModel_ID: TPropertyKey = ( fmtid: '{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}'; pid: 5);
begin
Result := SHGetPropertyStoreForWindow(Self.Handle, IPropertyStore, {out}ps);
if Failed(Result) then Exit;
if (ps = nil) then
begin
Result := E_FAIL;
Exit;
end;
if AppModelUserID <> '' then
begin
value := AppModelUserID;
Result := ps.SetValue(PKEY_AppUserModel_ID, PROPVARIANT(value));
end
else
begin
{
A window's properties must be removed before the window is closed.
If this is not done, the resources used by those properties are not returned to the system.
A property is removed by setting it to the PROPVARIANT type VT_EMPTY.
}
value := Unassigned; //the VT_EMPTY type
Result := ps.SetValue(PKEY_AppUserModel_ID, PROPVARIANT(value));
end;
end;
Bonus Reading
What if my application is really two applications bundled into a single file, and I want them collected into two groups on the taskbar in Windows 7?
How do I prevent users from pinning my program to the taskbar?
SHGetPropertyStoreForWindow function
Note: Any code released into public domain. No attribution required.

Hide fsStayOnTop form when main form minimizes / prevent automatic restore

I have a main form and a status form similar to the question asked here. However, my status window's FormStyle is fsStayOnTop, which causes some odd behavior when I try and minimize the main form when the status window is showing.
When I minimize the main form, both forms are hidden as expected, but when the status form is done completing its tasks when it is minimized, the main form automatically restores itself and its minimize button no longer works (but maximize/restore/close all still work). The only way to get the minimize function to work again is to restart the application. If I set the FormStyle to anything else, everything works fine, but my application requires the status form to stay on top when it is visible.
Is there a reason why the main form automatically restores like this? Is there a way to fix this?
procedure ButtonClick(sender:TObject);
begin
//Gather some data
ShowStatusWindow; // sets status window to visible, does its work,
// then sets invisible. (Usually visible for about 10 seconds)
//Gather more data
Windows.SendMessage(self.Handle, WM_SETREDRAW, 0, 0); //Freeze the main form for
//flickerless drawing
// Handle everything that would cause a redraw
Windows.SendMessage(self.Handle, WM_SETREDRAW, 1, 0); // thaw form
RedrawWindow(self.Handle, 0, 0, RDW_ERASE or RDW_FRAME or RDW_INVALIDATE or RDW_ALLCHILDREN);
end;
The only explanation I can come up with, is your application's MainFormOnTaskbar is set to false.
If this is the case, when your application is minimized, the main form is not actually minimized but hidden according to VCL's minimization mechanism. When you send a WM_SETREDRAW message with 'wParam' set to True, you're forcing the hidden (not minimized) main form's window to be shown.
At this stage the application is still minimized according to the VCL since it has never been restored. When you click the minimize button of the main form, VCL calls TApplication.Minimize and sees that the application window is already minimized and so returns without further action, hence the main form is not minimized.
Here's a simple sample that would duplicate the problem. Set MainFormOnTaskbar to false in project source. Run the application and then click the button:
procedure TForm1.Button2Click(Sender: TObject);
begin
Application.Minimize;
SendMessage(Handle, WM_SETREDRAW, 1, 0);
// ShowWindow(Handle, SW_SHOWNORMAL); // this will also do the same
end;
You won't be able to minimize the form after that.
To solve, you can check if the main form is visible f.i. before disabling drawing:
if IsWindowVisible(Handle) then begin
Windows.SendMessage(self.Handle, WM_SETREDRAW, 0, 0);
...

How can I stop my application showing on the taskbar?

My application has an option for the users to run it only in the system tray, and not in the task bar. This worked fine when my application was built by Delphi 6. After switching to Delphi XE2 it no longer functions.
I've messed with it some, and I have this working for Windows 7, but when running on Windows XP I still have a problem. The application correctly hides from the task bar, and shows in the system tray. But when I create and show any additional form, the icon shows up in Windows XP.
procedure TfrmAppointment.HideWindowFromTaskbar;
var
TaskbarList: ITaskbarList;
begin
Application.MainFormOnTaskBar := False;
// Windows 7 seems to behave differently. This seems to fix it.
if (CheckWin32Version(6, 1)) then
begin
// We are in Win7, and we requested the tray.
TaskbarList := CreateComObject(CLSID_TaskbarList) as ITaskbarList;
TaskbarList.HrInit;
TaskbarList.DeleteTab(Application.Handle);
end
else
begin
// Previous code from D6 days
ShowWindow(Application.Handle, SW_HIDE);
SetWindowLong(Application.Handle, GWL_EXSTYLE, GetWindowLong(Application.Handle, GWL_EXSTYLE) or WS_EX_TOOLWINDOW);
ShowWindow(Application.Handle, SW_SHOWNOACTIVATE);
end;
end;
That code is ran if the user chooses the option to show the application in the system tray. It works fine on all versions of Windows I've tested on. On Windows XP, however, when I show any child form, the application instantly shows up in the taskbar. In Windows 7 all is fine.
Any ideas what I'm missing?
I should add that I know this is likely the same question as Hide the Main Form in a Delphi 2009 Application, however I already have the MainFormOnTaskBar being set, so that answer does not seem to apply.
[EDIT:] To be more specific, I'm adding additional information here. This application has two modes: Show in task bar, and show in system tray.
The first mode is the same as any normal application. The application exists only in the task bar. It minimizes to the task bar. It restores from the task bar.
The second mode behaves exactly the same, BUT that task bar icon instead exists in the system tray only. So, when a user minimizes the application, I intercept that message, grab the TRect for 'Shell_TrayWnd'/'TrayNotifyWnd', and call DrawAnimatedRects() to simulate the minimize to the tray. Then I hide the main form. On message from the system tray I draw the same animation rects in reverse, and make it visible again. While the form is visible it does not show in the task bar.
This all works perfectly fine in all Windows versions.
The specific issue I am having is that when any other form gets shown, Windows XP is creating the application icon in the task bar. Windows 7 does not do this. So if a Windows XP user only uses the application main form, no problems arise and both viewing modes work fine. If they open another window, the application icon appears, and stays there even after that window closes. Windows 7 does not do this, and the icon stays gone.
You should set
Application.MainFormOnTaskBar := True;
in your .dpr file and then never modify that setting.
Then, when you want to remove the main form from the taskbar you simply write
MainForm.Hide;
When you need to bring the main form out of hiding again write
MainForm.Show;
And that's it.
Naturally you'll want to show and hide your notification area icon in concert with hiding and showing the main form.
The code in HideWindowFromTaskbar is not necessary and you should remove it. When you application is in MainFormOnTaskBar equals True mode, the main form is an un-owned top-level window. And so it appears on the taskbar whenever it is visible. So you can remove the main form from the taskbar simply my hiding it.
The other forms in your application will be owned top-level windows. Typically they will be owned by your main form. By virtue of being owner, they will not appear on the taskbar.
By and large you should try hard to avoid fiddling with window styles. You can usually make your application behave the way you need without doing so. What's more, if ever you have to adjust window styles, you must do it in CreateParams. That way the window style will persist when the window gets re-created. But I re-iterate, avoid modifying window styles where you can.
The key MSDN references are:
Window Features.
The Taskbar.
Here's the smallest program I can produce that proves the point:
program MainFormHiding;
uses
Forms, StdCtrls;
var
MainForm, OtherForm: TForm;
Button: TButton;
type
TEventHandlerClass = class
class procedure ToggleMainFormVisible(Sender: TObject);
end;
class procedure TEventHandlerClass.ToggleMainFormVisible(Sender: TObject);
begin
MainForm.Visible := not MainForm.Visible;
end;
begin
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm, MainForm);
OtherForm := TForm.Create(Application);
MainForm.Caption := 'Main Form';
OtherForm.Visible := True;
OtherForm.Caption := 'Other Form';
Button := TButton.Create(OtherForm);
Button.Caption := 'Toggle';
Button.Parent := OtherForm;
Button.OnClick := TEventHandlerClass.ToggleMainFormVisible;
Application.Run;
end.
In the comments you make it clear that you want to be able to hide the taskbar window without hiding the main form. In that case I suggest that you set MainFormOnTaskbar to False. That will mean that Application.Handle will be the window associated with the taskbar button. You can then hide that window to remove it from the taskbar.
You will now need to explicitly set PopupParent for any auxiliary forms. If you want those windows to be owned by the main form, then you can set it up.
Here's my example adjusted for this scenario:
program MainFormHiding;
uses
Forms, StdCtrls, Windows;
var
MainForm, OtherForm: TForm;
Button: TButton;
type
TEventHandlerClass = class
class procedure ToggleTaskbarButton(Sender: TObject);
end;
class procedure TEventHandlerClass.ToggleTaskbarButton(Sender: TObject);
begin
if IsWindowVisible(Application.Handle) then
ShowWindow(Application.Handle, SW_HIDE)
else
ShowWindow(Application.Handle, SW_SHOW);
end;
begin
Application.MainFormOnTaskbar := False;
Application.CreateForm(TForm, MainForm);
OtherForm := TForm.Create(Application);
OtherForm.PopupParent := MainForm;
MainForm.Caption := 'Main Form';
Application.Title := MainForm.Caption;
OtherForm.Visible := True;
OtherForm.Caption := 'Other Form';
Button := TButton.Create(OtherForm);
Button.Caption := 'Toggle';
Button.Parent := OtherForm;
Button.OnClick := TEventHandlerClass.ToggleTaskbarButton;
Application.Run;
end.
Run this program and click on the toggle button. Now you will see main form and other form showing. And nothing in the taskbar. I included the toggle button to show that you can switch between your two modes of operation whilst the program is running. No need to restart it.
The key here is to make a window other than your visible forms be the window associated with the taskbar. Once you do that you can once again control taskbar presence by showing and hiding that window. In this case that window is the application window, Application.Handle. Because that's the window on the taskbar, you need to set its Title property to control its text.
I stress finally, once again, that interaction with the taskbar is best controlled with window owner and visibility. Always search for solutions using those methods rather than ITaskbarList, extended window styles etc.
Update
Hopefully the last word on the subject. As you have noticed, the code directly above has poor behaviour when the main form is minimised. When that happens, the application window is made visible again and so appears once more in the taskbar.
I'm not so sure of myself when it comes to suppressing this behaviour. The behaviour comes about because of the code in TApplication.Minimize which shows the application handle when the main form is minimized. The best solution that I have is to convert a main form minimize into a hide.
procedure WMSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND;
....
procedure TMainForm.WMSysCommand(var Msg: TWMSysCommand);
begin
if (Msg.CmdType and $FFF0)=SC_MINIMIZE then
begin
Hide;
exit;
end;
inherited;
end;
Or another way would be to suppress the application window show by means of an OnMinimize event handler for TApplication.
class procedure TEventHandlerClass.ApplicationMinimize(Sender: TObject);
begin
ShowWindow(Application.Handle, SW_HIDE);
end;
David's answer is correct. There were a couple minor issues with it, but I ran with it and got everything working. He posted his last update while I was figuring this out. I am posting some additional code samples here, and accepted his answer. First I assigned:
Application.OnMessage := AppMessage;
Then the procedure is as follows:
procedure TfrmAppointment.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin
// This first check decides if we are minimizing via the upper right button OR
// The context menu in the upper left hand corner of the window.
// Minimizing twice restores, so this can be a restore as well.
if ((((Msg.message = WM_NCLBUTTONDOWN) and (Msg.wParam = HTMINBUTTON)) or
((Msg.message = WM_SYSCOMMAND) and (Msg.wParam = SC_MINIMIZE))) and
(Screen.ActiveForm = Self)) then
begin
// This function is defined as (bool, bool) where the variables are:
// Param1: Mimimizing (true), Restoring (false)
// Param2: Draw animation rectangles for doing this or not
Handled := MinimizeOrRestore(Self.WindowState <> wsMinimized, True);
end
else if ((Msg.message = WM_SYSCOMMAND) and
(Msg.wParam = SC_RESTORE) and
(Screen.ActiveForm = Self)) then
begin
// Specifically, restore has been asked for
Handled := MinimizeOrRestore(False, True); // Minimize with animation
end
else if ((Msg.message = WM_SYSCOMMAND) and (Msg.wParam = SC_CLOSE)) then
begin
// The user just used the system menu to close the application
ApplicationIsClosing := True; // see below for this
end
end;
Then in my FormCloseQuery, I check for "ApplicationIsClosing" to be true. If it's FALSE, then I know the user hit the X, and I simply minimize the application calling the other function referenced here. If it's true, I allow the close.
Finally, MinimizeOnRestore grabs the TRect for the form itself, as well as the system tray and then executes DrawAnimatedRects. This doesn't work always on Vista or higher, but it doesn't error either. Next, it hides the main application window or makes it visible. It always returns true unless it encounters an error. Then it returns false.

Why EM_SETMARGINS does not work under Windows 7?

I have a compound visual control which consists of edit box and a drop down button. The drop down button is not a windowed control and is drawn over edit box. I limit edit's width with following call:
SendMessage(Handle, EM_SETMARGINS, EC_RIGHTMARGIN,
(DropDownButtonWidth + 2) shl 16);
It works fine under Windows XP but doesn't work under Windows 7. In latter case, when focused edit box overlap dropdown button and erases its image.
What would be correct way of limiting edit box rect under both operation systems?
PS: I also tried another approach:
SendMessage(Handle, EM_GETRECT, 0, LongInt(#Loc));
Loc.Bottom := ClientHeight + 1;
Loc.Right := ClientWidth - FButton.Width - 2;
Loc.Top := 0;
Loc.Left := 0;
SendMessage(Handle, EM_SETRECTNP, 0, LongInt(#Loc));
But it doesn't work for Windows 7 either.
Your first code to set the margins is correct.
Things get tricky however because of the way the VCL works, the underlying window created for a given VCL control may be recreated in response to changes made to VCL properties at runtime (some property changes can only be applied at the Windows API level by destroying and recreating the window). Although the order of messages is (usually) not changed in different Windows versions, there can be additional messages introduced, some of which may alter the order in which the VCL code wrapped around those windows may fire, or interfere with the behaviour of that code.
It is also possible that a behaviour was introduced in the underlying Windows API windows that the VCL does not cater for (again, most likely to occur when mixing low level API calls as in this case).
This is especially the case when mixing VCL behaviours with lower level, direct API calls - as in this case.
There are also other things that can interfere with certain settings once applied, which require that you yourself destroy and recreate the window and re-apply your own settings.
I have seen other reports of problems in this area with the same code (not Delphi) on different versions of XP - there seems to have been a change introduced in SP2 that had some impact in this area.
In the case of EM_SETMARGINS I had the same problem you did and fixed it by looking at how the TButtonEdit control managed to apply the margins it required (which was working at least on my Windows 7 installation).
Things were perhaps made easier for me by the fact that I was implementing a custom control of my own, rather than trying to apply margins to some existing edit control. In the code snippets below, TCustomPickEdit is my custom control class, which incorporates an fButton object which holds all the settings relevant to the picker button. You will need to make suitable adjustments to apply this code in your particular situation.
What I found was the following:
Margins needed to be applied in at least 3 places. First, whenever the settings that might affect the margin were changed, second whenever the edit control window handle was created, and finally whenever the font on the edit control is changed:
Even with margins correctly set, the clipping rectangle of the control needed to be adjusted to ensure correct drawing. This required overriding the WndProc of the edit control and intercepting a couple of messages. This WndProc also needed to respond to a font change notification to reapply the margins in the event that the edit control font was changed.
The code for dealing with each of these in my case is shown below:
procedure TCustomPickEdit.ConfigureButton;
// 1. Apply margins when button settings are changed
begin
fButton.Caption := Button.Caption;
fButton.Flat := Button.Flat;
fButton.Glyph := Button.Glyph;
fButton.NumGlyphs := Button.NumGlyphs;
fButton.Visible := Button.Visible;
ApplyMargins;
end;
procedure TCustomPickEdit.CreateHandle;
// 2. Apply margins when underlying window handle is created
begin
inherited;
ApplyMargins;
end;
procedure TCustomPickEdit.WndProc(var aMessage: TMessage);
// 3. Adjust clipping rectangle for correct drawing
// 4. Apply margins when font is changed
var
top: Integer;
begin
case aMessage.Msg of
CN_CTLCOLORSTATIC,
CN_CTLCOLOREDIT : if Button.Visible then
begin
top := fButton.Top;
if ThemeServices.ThemesEnabled and Ctl3D then
Inc(top);
ExcludeClipRect(aMessage.WParam, fButton.Left,
top + 1,
fButton.Left + fButton.Width,
fButton.Height);
end;
end;
inherited;
case aMessage.Msg of
CM_FONTCHANGED : if NOT (csLoading in ComponentState) then
ApplyMargins;
end;
end;
I suspect you may need to read further into the documentation on EM_SETMARGINS. It states:
The HIWORD specifies the new width of
the right margin, in pixels. This
value is ignored if wParam does not
include EC_RIGHTMARGIN.
Edit controls and Rich Edit 3.0 and
later: The HIWORD can specify the
EC_USEFONTINFO value to set the right
margin to a narrow width calculated
using the text metrics of the
control's current font. If no font has
been set for the control, the margin
is set to zero.
Note the second paragraph regarding EC_USEFONTINFO to set a narrow width. This may be implying that this is the ONLY way to set a narrow width. I don't know for sure as I haven't tried it, but it may help.
Note also that Rich Edit Controls and regular Edit Boxes have different behaviour, so check which one you're using.

Delphi - How to prevent Forms/MsgBoxes to move under prior form?

Many times after the Windows 98 era we have experienced that some dialogs lose their Z-Order and move back to the prior form.
For example:
Dialog1.ShowModal;
Dialog1.OnClickButton() : ShowMessage('anything');
When MessageBox appears, it sometimes doesn't have focus and is moved under Dialog1.
The users are confused about it, they say: My application froze!!!
But if they use Alt+Tab to move to another app and back, the focus returns to the MessageBox and it will be the foreground window.
We have experienced this with ShowMessage, MessageBox, normal forms, and also QuickReport forms.
Does anyone know about this? Is it a Windows bug? How can you prevent it? How to catch this?
Thanks for your help:
dd
I really said that AFTER Win98, so all OSs (Win7 also) are affected by this problem.
We used Delphi 6 Prof, so the properties are not working with Default forms.
Somebody said that message dialogs are controllable with MessageBox + MB_APPLMODAL.
This is good news, but we have many old forms and components, third party tools.
So it is hard work to make a completely new application with substitution of the forms.
But we will try doing this.
I think the answer is this is a half application problem and half a Windows problem. If Windows sometimes handles this, and sometimes doesn't - that seems to be a Windows bug.
But if we can force good modal window making then it is a programming bug.
Can somebody explain to me what is the meaning of the WS_POPUP flag?
Does it have some side effect or not?
Thanks:
dd
That's what the PopupMode and PopupParent properties are for.
E.g., you can do:
Dialog1.PopupMode := pmExplicit;
Dialog1.PopupParent := self;
Dialog1.ShowModal;
This tells Windows the correct Z-order.
For old versions of delphi (prior to Delphi 2007), on forms OTHER than your main form:
interface
TMyForm = Class(TForm)
protected
procedure CreateParams(var Para: TCreateParams); override;
end;
...
implementation
...
procedure TMyForm.CreateParams(var Para: TCreateParams);
begin
inherited;
Para.Style := Para.Style or WS_POPUP;
{ WinXP Window manager requires this for proper Z-Ordering }
// Para.WndParent:=GetActiveWindow;
Para.WndParent := Application.MainForm.Handle;
end;
For message boxes include MB_TOPMOST in your flags:
Application.MessageBox(PChar(amessage), PChar(atitle), otherflags or MB_TOPMOST);
I looked at this page and the FAQ for half an hour and still can't find how to post a comment, so forgive me for this breach of protocol.
First of all I'd like to make clear that the poster, IMHO, is not using Windows 98. He writes "after Windows 98 era" which I understand means he is having this problem with Windows versions after 98.
As I am having this problem too (CB2009), I'd like to emphasize the poster's question "Is it Windows bug?", which I have not seen answered. If it's a Delphi/Builder bug, maybe there is a way to avoid it? I can't see how intercepting all potential dialogs is a workable solution, nor avoid using fsStayOnTop. I have a settings form that needs to stay on top of my main form, but the settings form can and will popup dialogs that under certain conditions will disappear under the settings form.
It would be very helpful if I would understand where the support of z-order goes wrong, as it may offer a clue on how to avoid it.
A trick I've used recently was to apply these two lines of code during the creation of each form:
SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) or
WS_EX_APPWINDOW or WS_EX_TOPMOST);
SetWindowLong(Handle, GWL_HWNDPARENT, GetDesktopWindow);
Handle is the handle of the form (Form1.Handle). The WS_EX_APPWINDOW part makes each window appear on the task bar, remove it if you don't want that additional effect.
For my main form I use this line:
SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) or
WS_EX_TOPMOST);
I also use this function to help build my custom dialogs (I created a new function for each style of dialog - error, confirmation, etc.):
function CustomDlg(const AMessage : string; const ADlgType: TMsgDlgType;
const AButtons: TMsgDlgButtons; const ADefaultButton: TMsgDlgBtn) : TForm;
begin
Result := CreateMessageDialog(AMessage, ADlgType, AButtons, ADefaultButton);
with Result do
begin
SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) or
WS_EX_APPWINDOW or WS_EX_TOPMOST);
SetWindowLong(Handle, GWL_HWNDPARENT, GetDesktopwindow);
FormStyle := fsStayOnTop;
BringToFront;
end;
end;
The FormStyle := fsStayOnTop; part is optional, of course, but I use it to make sure my confirmation and error dialogs are always visible to the user.
It seems like a bit of work but the net effect is that I no longer have to worry about forms accidentally hiding behind other forms.

Resources