I try to check if current active window is Desktop do something , i wrote below code in a timer but the handle value returned by GetDektopWindow & GetForegroundWindow is not same value :
if GetForegroundWindow = GetDesktopWindow then
// Do something
How do this ?
// not defined in D2007
function GetShellWindow: HWND; stdcall; external user32;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
if GetForegroundWindow = GetShellWindow then
[..]
end;
With the only non-explorer shell I use (sharpe) it fails though.
update:
Sometimes the window hierarchy of the desktop is different (see Andreas' comments). The below shot is Spy++'s take when Windows 7's desktop picture rotation functionality is activated. Some 'WorkerW' window takes over the screen and it is the one that gets activated when clicked on the desktop. Since GetShellWindow returns the 'Progman's handle, the above test fails.
At this point it might seem reasonable to test if the foreground window has the shell's default view window as its immediate child, however I saw multiple references that indicate multiple 'WorkerW' windows might get nested. So I think the below would be a more fail-safe approach:
procedure TForm1.Timer1Timer(Sender: TObject);
function HasDefViewChild(Wnd: HWND): Boolean;
begin
Result := Wnd <> 0;
if Result then begin
Result := FindWindowEx(Wnd, 0, 'SHELLDLL_DefView', nil) <> 0;
if not Result then
Result := HasDefViewChild(FindWindowEx(Wnd, 0, 'WorkerW', nil));
end;
end;
begin
if HasDefViewChild(GetForegroundWindow) then
[...]
end;
This will work when the foreground window is 'Progman', because then the 'DefView' is 'Progman's child. OTOH when 'WorkerW' is the active window, the code will iterate if the first child is not 'DefView' and yet another 'WorkerW' instead.
A great tool for figuring out the structure of window parent/child relationships, window classes, etc., is WinDowse by Greatis Software.
http://www.greatis.com/delphicb/windowse/
I would start there. And I'd output the values of GetForegroundWindow and GetDesktopWindow (etc.,) onto labels in your test app. So you can see what those values are, as you poke around with WinDowse, in real time.
I search about this , GetWindowDesktop return the handle of desktop window but the desktop window is under another window called shell , so when you switch to dektop really you switch to shell window and must get shell handle , if you terminate process of shell window ( explorer.exe ) then you can see the real dektop window .
Related
Setting a form to WindowState = wsMaximized will sometimes cause the form to be maximized but not:
Long-time bug: this is a question I first asked in the Borland newsgroups in 2003:
Accepted fix for WindowState = wsMaximized?
and then again in 2006:
wsMaximized breaks it, NOT caused by Position=poScreenCenter, reproducible dfm
and then again in 2008:
Forms not starting maximized
Someone asked it on the Embarcadero forums in 2012:
Thread: Application not starting with maximized window
Now it's time to port the 18 year old bug to Stackoverflow. Maybe someone's finally figured out a workaround.
Steps to reproduce:
My posts contained half a dozen failure modes, but the easiest is:
Drop a Label and an Edit on a form:
Add an OnEnter event for the TEdit:
procedure TForm1.Edit1Enter(Sender: TObject);
begin
Label1.Font.Style := Label1.Font.Style + [fsBold];
end;
and set the form:
WindowState to wsMaximized
AutoScroll to False
And bazinga, fails.
One of the other set of steps from the 2008 post:
Create a new app and a form.
Set the form to maximized (WindowState = wsMaximized) at design time.
Drop a ListView control on the form
During OnShow, add 20 empty items to the list view:
procedure TForm1.FormShow(Sender: TObject);
var
i: Integer;
begin
for i := 1 to 20 do
ListView1.Items.Add;
end;
Set the form's AutoScroll property to false (AutoScroll = False) at design time
Of course what I'm not after is "fixed in version n of RadStudio. Just use that". I'm looking for an actual fix (if there is one); which could include quoting relevant changes to the VCL source when CodeGear finally did fix it. (If it is even fixed).
Note: Changing Position from poDesigned to anything else doesn't fix it.
Workaround
A horrible, ugly, awful, disgusting, workaround I had been using was to start a timer during OnShow, and then when the timer fires, maximize the form:
procedure TForm1.tmrVclMaximizeHackTimer(Sender: TObject);
begin
Self.WindowState := wsMaximized;
end;
I later improved this hack to post a message during OnShow; which is essentially the same as a timer message, without having to use a timer:
const
WM_MaximizeWindow = WM_APP + $03;
procedure TForm1.FormShow(Sender: TObject);
begin
if (Self.WindowState = wsMaximized) then
begin
Self.WindowState := wsNormal;
PostMessage(Self.Handle, WM_MaximizeWindow , 0, 0);
end;
end;
private
procedure WMMaximizeWindow(var Message: TMessage); message WM_MaximizeWindow;
procedure TForm1.WMMaximizeWindow(var Message: TMessage);
begin
Self.WindowState := wsMaximized;
end;
Sometimes I invent the OnAfterShow event that Delphi never did:
const
WM_AfterShow = WM_APP + $02;
procedure TForm1.FormShow(Sender: TObject);
begin
PostMessage(Self.Handle, WM_AfterShow, 0, 0);
if (Self.WindowState = wsMaximized) then
begin
Self.WindowState := wsNormal;
FMaximizeNeeded := True;
end;
end;
private
procedure WMAfterShow(var Message: TMessage); message WM_AfterShow;
procedure TForm1.WMAfterShow(var Message: TMessage);
begin
if FMaximizeNeeded then
begin
FMaximizeNeeded := False;
Self.WindowState := wsMaximized;
end;
end;
But no hacks are better than hacks.
I Can reproduce with D7/Win7.
I don't use wsMaximized at all (similar random problems as you describe).
Workaround: use OnActivate -> ShowWindow(Handle, SW_MAXIMIZE) e.g.:
procedure TForm1.FormActivate(Sender: TObject);
begin
// Maximize only once when the Form is first activated
if not FMaxsimized then
begin
FMaxsimized := True;
ShowWindow(Handle, SW_MAXIMIZE);
end;
end;
This method will not work during OnShow.
Better Workaround: use ShowWindowAsync during OnShow or OnCreate e.g:
procedure TForm1.FormCreate(Sender: TObject);
begin
ShowWindowAsync(Handle, SW_MAXIMIZE);
end;
This sets the show state of a window without waiting for the operation to complete.
I only tested the first reproduction case (with D7, D2007, XE2), and am able to duplicate the problem with D7 and D2007 but not with XE2.
The problem, as I see it, is that the label, having its font changed, requests its parent to re-align itself. This eventually leads to a SetWindowPos call on the form (in TWinControl.AdjustSize) with restored width/height even though the form is already maximized - which leads to the strange, behaviorally maximized but not visually maximized, form sitting on the screen.
I traced the code in D2007 and XE2 to be able to come up with what is different. The code in TWinControl.AlignControls is different between the two versions. What specifically matters is the last statement.
D2007:
procedure TWinControl.AlignControls(AControl: TControl; var Rect: TRect);
..
{ Apply any constraints }
if Showing then AdjustSize;
end;
XE2:
procedure TWinControl.AlignControls(AControl: TControl; var Rect: TRect);
..
// Apply any constraints
if FAutoSize and Showing then
DoAdjustSize;
end;
I hope this, somehow, helps you devising/deciding what workaround to use.
The workaround I could suggest (although I haven't tested it throughly) is to force show the form maximized early:
procedure TForm1.FormCreate(Sender: TObject);
var
wplc: TWindowPlacement;
begin
if not AutoScroll and (WindowState = wsMaximized) then begin
wplc.length := SizeOf(wplc);
GetWindowPlacement(Handle, #wplc);
wplc.rcNormalPosition.Right := wplc.rcNormalPosition.Left + Width;
wplc.rcNormalPosition.Bottom := wplc.rcNormalPosition.Top + Height;
wplc.showCmd := SW_MAXIMIZE;
SetWindowPlacement(Handle, #wplc);
end;
end;
The above works because it forces to set the focus to the edit control (OnEnter event) before the VCL sets the visible flag for the form. In turn, the label's alignment request does not result with form size adjustment. Also, since, by the time VCL calls ShowWindow the form's window is already visible, it doesn't cause the form to be shown in a restored state at any stage.
However, I don't know if it would help with different reproduction scenarios.
Finally, although I can see that the behavior is corrected in newer Delphi versions, I wouldn't consider this to be a bug in the VCL. In my opinion, user code should be responsible not to cause window adjustment while window showing state is changing. The course of action I'd take for the specific scenario would be to defer to modify label's font until the VCL is done displaying the form.
I don't think this is a bug in Delphi but rather a bug (or just odd behavior) in the Windows CreateWindow function. If you search for CreateWindow and WS_MAXIMIZE not working you'll find similarly very old threads and discussions from people calling CreateWindow or CreateWindowEx passing WS_MAXIMIZE in the style parameter and not seeing a maximized window when they run the application.
Excerpt from an old gamedev.net thread
the problem is that WS_MAXIMIZE apparently does not apply when using WS_OVERLAPPEDWINDOW. if you replace WS_OVERLAPPEDWINDOW with WS_POPUP you will get a maximized window. of course this may not apply to all versions of Windows, or even all versions of the Windows shell UI for that matter.
WS_OVERLAPPEDWINDOW is MS's old default window "type" and they apparently coded CreateWindow/Ex to ignore certain styles, thinking that ShowWindow would be called with SW_SHOWDEFAULT, which causes the window to be displayed according to the CreateProcess startup info parms. this ultimately gives the user the control of how an app's main window would be displayed by using the shell's shortcut settings.
The workaround is just to call ShowWindow. It should work in Delphi, too:
procedure TForm1.FormShow(Sender: TObject);
begin
ShowWindow(Handle, SW_MAXIMIZE);
end;
Hope the solution i use helps others (i known window is first shown with design time size):
Add a timer with interval as less as just 1 (do not put 0).
Code for it: theTimer.Enabled:=False;WindowState:=wsMaximized;
It never fail to me.
As soon as form is shown and all pending tasks for such show are finished, the timer triggers and window is maximized.
Some time ago, i was using the Trick of sending a mouse click where maximize button was, but i discovered checking Windows OS version, plugins (on Linux), etc makes the thing so hard. That one worked exactly as if the user asks for maximize the window.
The Timer i now use does exactly the same, but avoid OS checking, etc.
Not to mention: Put WindowState to wsNormal on DesignTime (do not set it to wsMinimized, neither to wsMaximized).
Wow! i did not see on the post:
ShowWindowAsync(Handle,SW_MAXIMIZE);
Thanks for that, with it my problem is solved much better than with a Timer, but not perfect, it still causes a little flicker (window is shown on incomplete render, then it goes to maxized state), it is much better than the timer, less time shown in non maximized state.
And it is compatible with a prior SetBounds() on the OnShow method.
I wish: Set initial size (Width, Height) and maybe also initial position (Left, Top) of a form prior to show it, but that form must be shown maximized; such position and sizes are for when the user un-maximize the window.
That ShowWindowAsync works perfect for such objective, and no need to add an ugly timer (with interval=1 and .Enabled=False on its code as first sentence).
How could i miss it when i read the post!
So, now on i will use (just as example os initial size relative to monitor):
procedure TtheForm.FormShow(Sender: TObject);
var
theInitialDefaultWidth,theInitialDefaultHeight:Integer;
begin
theInitialDefaultWidth:=Round(Screen.Width*3/5);
theInitialDefaultHeight:=Round(Screen.Height*3/5);
WindowState:=wsNormal; // So it can still have at design time wsMaximized, this is for the SetBounds to work on a non maximized state
SetBounds((Screen.Width-theInitialDefaultWidth)div 2,(Screen.Height-theInitialDefaultHeight)div 2,theInitialDefaultWidth,theInitialDefaultHeight); // Set default position and default size as i wish
ShowWindowAsync(Handle,SW_MAXIMIZE); // Make the window to be shown maximized when it will be visible
// ... // Rest of actions for the FormShow method
end;
Works perfect! I do not need to touch design time properties, i can let them as they are (WindowState=wsMaximized, Position=poScreenCenter, etc).. 100% code solution for the problem.
Thanks a lot!
P.D.: Will it work on Linux? I mean when code is compiled for Linux (in Lazarus), i must test it and see, if it does work it will be a great immprove on what i was using till now.
I need to send several virtual keys (VK_RETURN) from my delphi application (myapp.exe) into another application (target.exe).
Eg : Send VK_RETURN twice , from myapp.exe , into target.exe
The OS that I use are Windows 7 64 bit and Windows XP.
I read : How to send an "ENTER" key press to another application? , Send Ctrl+Key to a 3rd Party Application (did not work for me) and other previous asked question.
But still I'm getting confused.
How to set the focus to the target application ?
How to send the virtual keys to the targeted application ?
Simple example : I want to send VK_RETURN twice into notepad.exe or calc.exe (already loaded) or any other program from my delphi application. How to do that ?
The simplest way to do this in Delphi 2010, please...
PS :
I tried SndKey32.pass from http://delphi.about.com/od/adptips2004/a/bltip1104_3.htm
And got error : [DCC Error] SndKey32.pas(420): E2010 Incompatible types: 'Char' and 'AnsiChar'
If (Length(KeyString)=1) then MKey:=vkKeyScan(KeyString[1])
If your target application isn't the foreground window, you need to use PostMessage to send keystrokes to its window handle. You can get that window handle using FindWindow. The code below sends the Enter key to a the text area in a running instance of Notepad (note it uses an additional FindWindowEx to locate the memo area first). It was tested using both Delphi 2007 and Delphi XE4 (32-bit target) on Windows 7 64.
uses Windows;
procedure TForm1.Button1Click(Sender: TObject);
var
NpWnd, NpEdit: HWnd;
begin
NpWnd := FindWindow('Notepad', nil);
if NpWnd <> 0 then
begin
NpEdit := FindWindowEx(NpWnd, 0, 'Edit', nil);
if NpEdit <> 0 then
begin
PostMessage(NpEdit, WM_KEYDOWN, VK_RETURN, 0);
PostMessage(NpEdit, WM_KEYUP, VK_RETURN, 0);
end;
end;
end;
To find the window by title (caption) instead, you can just use the second parameter to FindWindow. This finds a new instance of Notepad with the default 'Untitled' file open:
NpWnd := FindWindow(nil, 'Untitled - Notepad');
Note that this requires as exact match on the window title. An extra space before or after the -, for instance, will cause the match to fail and the window handle to not be retrieved.
You can use both the window class and title if you have multiple instances running. To find the copy of Notepad running with Readme.txt loaded, you would use
NpWnd := FindWindow('Notepad', 'Readme.txt - Notepad');
To find other applications, you'll need to use something like WinSpy or WinSight to find the window class names. (There are others also, such as Winspector or WinDowse (both of which are written in Delphi).)
Your comment mentions Calculator; according to Winspector, the Calculator main window is in a window class called CalcFrame on Windows 7, and the area the numbers are displayed in is a Static window (meaning it doesn't seem to receive keystrokes directly). The buttons are simply called Button, so you'd have to loop through them using EnumChildWindows looking for the individual buttons to identify them in order to obtain their handles.
(How to enumerate child windows is a separate question; you can probably find an example by searching here or via Google. If you can't, post a new, separate question about that and we can try to get you an answer.)
Here's a quick example of sending keys to Calculator after finding it by window class. It doesn't do anything useful, because it needs some time spent to identify different buttons and the keys that each responds to (and the proper combination of messages). This code simply sends 11Numpad+22 to the calculator window (a quick test showed that they were properly received and displayed, and that's about all the time I wanted to spend on the process).
uses Windows;
procedure TForm1.Button1Click(Sender: TObject);
var
NpWnd: HWnd;
begin
NpWnd := FindWindow('CalcFrame', nil);
if NpWnd <> 0 then
begin
PostMessage(NpWnd, WM_KEYDOWN, VK_NUMPAD1, 0);
PostMessage(NpWnd, WM_KEYDOWN, VK_ADD, 0);
PostMessage(NpWnd, WM_KEYDOWN, VK_NUMPAD2, 0);
end;
end;
I am handling from my Application associated extension files from Windows. So when you double click a file from Windows it will execute my program, and I handle the file from there, something like:
procedure TMainForm.FormCreate(Sender: TObject);
var
i: Integer;
begin
for i := 0 to ParamCount -1 do
begin
if SameText(ExtractFileExt(ParamStr(i)), '.ext1') then
begin
// handle my file..
// break if needed
end else
if SameText(ExtractFileExt(ParamStr(i)), '.ext2') then
begin
// handle my file..
// break if needed
end else
end;
end;
That works pretty much how I want it to, but when I was testing I realised it does not consider using only one instance of my program.
So for example, if I selected several Files from Windows and opened them all at the same time, this will create the same number of instances of my program with the number of Files being opened.
What would be a good way to approach this, so that instead of several instances of my program being opened, any additional Files from Windows being opened will simply focus back to the one and only instance, and I handle the Files as normal?
Thanks
UPDATE
I found a good article here: http://www.delphidabbler.com/articles?article=13&part=2 which I think is what I need, and shows how to work with the Windows API as mentioned by rhooligan. I am going to read through it now..
Here is some simple example code that gets the job done. I hope it is self-explanatory.
program StartupProject;
uses
SysUtils,
Messages,
Windows,
Forms,
uMainForm in 'uMainForm.pas' {MainForm};
{$R *.res}
procedure Main;
var
i: Integer;
Arg: string;
Window: HWND;
CopyDataStruct: TCopyDataStruct;
begin
Window := FindWindow(SWindowClassName, nil);
if Window=0 then begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end else begin
FillChar(CopyDataStruct, Sizeof(CopyDataStruct), 0);
for i := 1 to ParamCount do begin
Arg := ParamStr(i);
CopyDataStruct.cbData := (Length(Arg)+1)*SizeOf(Char);
CopyDataStruct.lpData := PChar(Arg);
SendMessage(Window, WM_COPYDATA, 0, NativeInt(#CopyDataStruct));
end;
SetForegroundWindow(Window);
end;
end;
begin
Main;
end.
unit uMainForm;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, Forms, StdCtrls;
type
TMainForm = class(TForm)
ListBox1: TListBox;
procedure FormCreate(Sender: TObject);
protected
procedure CreateParams(var Params: TCreateParams); override;
procedure WMCopyData(var Message: TWMCopyData); message WM_COPYDATA;
public
procedure ProcessArgument(const Arg: string);
end;
var
MainForm: TMainForm;
const
SWindowClassName = 'VeryUniqueNameToAvoidUnexpectedCollisions';
implementation
{$R *.dfm}
{ TMainForm }
procedure TMainForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.WinClassName := SWindowClassName;
end;
procedure TMainForm.FormCreate(Sender: TObject);
var
i: Integer;
begin
for i := 1 to ParamCount do begin
ProcessArgument(ParamStr(i));
end;
end;
procedure TMainForm.ProcessArgument(const Arg: string);
begin
ListBox1.Items.Add(Arg);
end;
procedure TMainForm.WMCopyData(var Message: TWMCopyData);
var
Arg: string;
begin
SetString(Arg, PChar(Message.CopyDataStruct.lpData), (Message.CopyDataStruct.cbData div SizeOf(Char))-1);
ProcessArgument(Arg);
Application.Restore;
Application.BringToFront;
end;
end.
The logic goes something like this. When you start your application, you iterate through the list of running processes and see if your application is already running. If it is running, you need to activate the window of that instance and then exit.
Everything you need to do this is in the Windows API. I found this sample code on CodeProject.com that deals with processes:
http://www.codeproject.com/KB/system/Win32Process.aspx
On finding and activating a window, the basic approach is to find the window of interest using the window class name then activate it.
http://www.vb6.us/tutorials/activate-window-api
Hopefully this gives you a good starting point.
There are many answers here that show how to implement this. I want to show why NOT to use the FindWindow approach.
I am using FindWindow (something similar with the one shown by David H) and I have seen it failed starting with Win10 - I don't know what they changed in Win10.
I think the gap between the time when the app starts and the time when we set the unique ID via CreateParams is too big so another instance has somehow time to run in this gap/interval.
Imagine two instances started at only 1ms distance (let's say that the user click the EXE file and then presses enter and keeps it pressed by accident for a short while). Both instances will check to see if a window with that unique ID exists, but none of them had the chance to set the flag/unique ID because creating the form is slow and the unique ID is set only when the form is constructed. So, both instances will run.
So, I would recommend the CreateSemaphore solution instead:
https://stackoverflow.com/a/460480/46207
Marjan V already proposed this solution but didn't explained why it is better/safer.
I'd use mutexes. You create one when your program starts.
When the creation fails it means another instance is already running. You then send this instance a message with your command line parameters and close. When your app receives a message with a command line, it can parse the parameters like you are already doing, check to see whether it already has the file(s) open and proceed accordingly.
Processing this app specific message ia also the place to get your app to the front if it isn't already. Please do this politely (SetForegroundWindow) without trying to force your app in front of all others.
function CreateMutexes(const MutexName: String): boolean;
// Creates the two mutexes to see if the program is already running.
// One of the mutexes is created in the global name space (which makes it
// possible to access the mutex across user sessions in Windows XP); the other
// is created in the session name space (because versions of Windows NT prior
// to 4.0 TSE don't have a global name space and don't support the 'Global\'
// prefix).
var
SecurityDesc: TSecurityDescriptor;
SecurityAttr: TSecurityAttributes;
begin
// By default on Windows NT, created mutexes are accessible only by the user
// running the process. We need our mutexes to be accessible to all users, so
// that the mutex detection can work across user sessions in Windows XP. To
// do this we use a security descriptor with a null DACL.
InitializeSecurityDescriptor(#SecurityDesc, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(#SecurityDesc, True, nil, False);
SecurityAttr.nLength := SizeOf(SecurityAttr);
SecurityAttr.lpSecurityDescriptor := #SecurityDesc;
SecurityAttr.bInheritHandle := False;
if (CreateMutex(#SecurityAttr, False, PChar(MutexName)) <> 0 )
and (CreateMutex(#SecurityAttr, False, PChar('Global\' + MutexName)) <> 0 ) then
Result := True
else
Result := False;
end;
initialization
if not CreateMutexes('MyAppNameIsRunningMutex') then
//Find and SendMessage to running instance
;
end.
Note: above code is adapted from an example on the InnoSetup site. InnoSetup creates installer applications and uses this approach in the installer to check whether (a previous version of) the application being installed is already running.
Finding the other instance and sending it a message, I'll leave for another question (or you can use the WM_COPYDATA approach from David's answer). Actually, there is a StackOverflow question that deals exactly with this: How to get the process thread that owns a mutex Getting the process/thread that owns the mutex may be a bit of a challenge, but the answers to this question do address ways to get the information from one instance to the other.
Windows has different ways to handle file associations to executable.
The "command line" approach is only the simplest one, but also the most limited one.
It also supports DDE (it still works although officially deprecated) and COM (see http://msdn.microsoft.com/en-us/library/windows/desktop/cc144171(v=vs.85).aspx).
If I recall correctly both DDE and COM will let your application receive the whole list of selected files.
I used window/message approach by myself with addition of events for tracking if the other instance is running:
Try to create event "Global\MyAppCode" (the "Global" namespace is used for handling various user sessions as I needed single instance system-wide; in your case you'll probably prefer "Local" namespace which is set by default)
If CreateEvent returned error and GetLastError = ERROR_ALREADY_EXISTS then the instance is running already.
FindWindow/WM_COPYDATA to transfer data to that instance.
But the drawbacks with messages/windows are more than significant:
You must always keep your window's Caption constant. Otherwise you'll have to list all the windows in the system and loop through them for partial occurrence of some constant part. Moreover the window's caption could be easily changed by a user or 3rd part app so the search would fail.
Method requires a window to be created so no console/service apps, or they must create a window and perform message loop especially for handling the single instance.
I'm not sure FindWindow could find a window that is opened in another user session
For me, WM_COPYDATA is rather awkward method.
So currently I'm a fan of named pipe approach (haven't implemented it yet though).
On launch, app tries to connect to "Global\MyAppPipe". If successed, other instance is running. If failed, it creates this pipe and finishes instance check.
2nd instance writes the required data to pipe and exits.
1st instance receives data and does some stuff.
It works through all user sessions (with namespace "Global") or just a current session; it doesn't depend on strings used by UI (no localization and modification issues); it works with console and service apps (you'll need to implement pipe reading in a separate thread/message loop though).
I have the following issue: we're building a rather large application (win32, Delphi 6 Enterprise). In several part of the application, modal windows are used, usually containing the detail of the selection of the main window.
We included a modification of the handling of the WM_SYSCOMMAND messages so that, if the window is modal, then a SW_SHOWMINNOACTIVE message will be sent to the application's main window. This causes the whole application to be minimized instead of just the modal form.
There is, however, an issue happening in a specific case: if the calling window is set to full screen, then upon restoration, the modal window will appear UNDER the (disabled) maximized main window (this seems to happen on Windows 7)
My problem is two fold:
First, I don't seem to get any syscommand message when the application is restored any more so I cannot introduce code to restore the Z-Order because I don't know where to put it.
Second, it seems to me that, if the whole application is minimized, clicking on the app's button in the task bar should restore it in the same state, not with a modal window under it. Is there a way to fix that ?
Edit: we did some additional testing and it seems we can actually detect the problem in the WM_ACTIVATE handler for the main form. We can also identify the modal window at that stage. I cannot, however, find a way to restore it to the top of the Z-Order.
Edit2: here is the code that minimizes the application when the modal form is minimized:
procedure TfmGITForm.WMSysCommand(var Message: TWMSysCommand);
begin
if (fsModal in FormState) or
not Application.MainForm.Visible then
begin
case Message.CmdType of
SC_MINIMIZE:
begin
ShowWindow(Application.Handle, SW_SHOWMINNOACTIVE);
end;
SC_RESTORE:
begin
ShowWindow(Application.Handle, SW_SHOWNORMAL);
inherited;
end;
else
inherited;
end; // case
end
else
inherited;
end;
All our forms descend from that one.
Override the dialog's CreateParams function and set Params.WndParent to the full-screen window (or Owner.Handle if you're owning things properly). The default is Application.Handle, which will cause these kinds of problems. The PopupParent properties introduced in the later Delphi releases does the exact same thing.
This has to do with the Window ghosting by Windows which was introduced in (I think) XP. I have the same issues in a D5 app on these operating systems. Peter Below offered the following work around at the time and it still serves me well:
procedure DisableProcessWindowsGhosting;
type
TDisableProcessWindowsGhostingProc = procedure; stdcall;
const
sUser32 = 'User32.dll';
var
ModH: HMODULE;
_DisableProcessWindowsGhosting: TDisableProcessWindowsGhostingProc;
begin
ModH := GetModuleHandle(sUser32);
if ModH <> 0 then begin
#_DisableProcessWindowsGhosting := nil;
#_DisableProcessWindowsGhosting := GetProcAddress(ModH,
'DisableProcessWindowsGhosting');
if Assigned(_DisableProcessWindowsGhosting) then begin
_DisableProcessWindowsGhosting;
end;
end;
end;
I call it at the beginning of the app's main form's OnCreate handler.
This is approach I found for Tray ... :
http://www.programmersheaven.com/mb/delphikylix/257563/257563/how-can-i-make-a-system-tray-flash/
Does the same technique works for Dialogs ( as they are forms with addition params, in fact )?
Or I can do it with way faster methods like getting handle / address / interface and overload or overdrive the function with FlashWindow(Ex) method?
I mean - can I make, for example ShowMessage(), window / dialog flash using FlashWindowEx() method and if I can, can it be done using the example in link given above?
Please, point to best direction or clarify my doubts ..
Thanks.
Sorry for bad formulation of question.
The same technique applies to any top-level window, including dialog boxes. If you can get the window's handle, you can pass it to FlashWindowEx.
There are many ways a modal form or dialog (both VCL or native from the system) can be shown from a Delphi program, so you need to somehow hook into message processing and catch messages that are sent when a modal form or dialog is shown.
For that a message hook can be set using the SetWindowsHookEx() API function. Since you need this only while the application is inactive you could set it in the handler of the OnDeactivate application event, and reset it in the handler for the OnActivate application event:
var
gNextHook: HHOOK;
procedure TForm1.AppActivate(Sender: TObject);
begin
if gNextHook <> 0 then
UnhookWindowsHookEx(gNextHook);
gNextHook := 0;
end;
procedure TForm1.AppDeactivate(Sender: TObject);
begin
gNextHook := SetWindowsHookEx(WH_CALLWNDPROC, #WndProcHook, 0,
GetCurrentThreadId);
end;
The hook function would watch for messages that are sent when a modal dialog or form is shown, and call FlashWindowEx() with the correct parameters:
function WndProcHook(nCode: integer; AWParam: WPARAM; ALParam: LPARAM): LRESULT; stdcall;
var
DataPtr: PCWPStruct;
Fwi: TFlashWInfo;
begin
DataPtr := PCWPStruct(ALParam);
if (DataPtr^.message = WM_INITDIALOG)
or ((DataPtr^.message = CM_ACTIVATE) and (DataPtr^.lParam = 0) and (DataPtr^.wParam = 0))
then begin
Fwi.cbSize := SizeOf(TFlashWInfo);
// flash caption of new modal window
Fwi.hwnd := DataPtr^.hwnd;
Fwi.dwFlags := FLASHW_ALL or FLASHW_TIMERNOFG;
Fwi.uCount := 0;
Fwi.dwTimeout := 0;
FlashWindowEx(Fwi);
// uncomment this to flash task bar button as well
(*
Fwi.hwnd := Application.MainForm.Handle;
Fwi.dwFlags := FLASHW_TRAY or FLASHW_TIMERNOFG;
FlashWindowEx(Fwi);
*)
end;
Result := CallNextHookEx(gNextHook, nCode, AWParam, ALParam);
end;
I chose WM_INITDIALOG which is sent for native dialogs like the open or save dialogs, and CM_ACTIVATE which is sent when a VCL form is shown modally. There may be more such messages that need to be caught. Above code works for the MessageDlg() function, the Application.MessageBox() function and TOpenDialog at least.
Since these dialogs don't have their own taskbar button I added (commented out) code to flash the taskbar button of the main form as well. This isn't optimal, as they flash out of sync.
Tested with Delphi 2009 on Windows XP, all error handling omitted, use it as a starting point only.