I have a Delphi project and the main form is a child window of another application. The parent application (a 3D game coded in C++) has two window states, windowed and full screen. In windowed mode the Delphi form floats on top of the parent application perfectly. It can be clicked on, dragged around, no problem (using params.Style := WS_POPUP; in the CreateParams procedure of my main form):
procedure TMyForm.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
HandleToTheOtherApp := FindWindow('THE_OTHER_APP', nil);
if HandleToTheOtherApp = 0 then
begin
ShowMessage('The parent app was not found');
exit;
end;
params.Style := WS_POPUP;
params.WndParent := HandleToTheOtherApp;
params.WinClassName := 'MyAppClassName';
end;
However if the parent application is put into full screen mode (it uses DirectX 9/10) my Delphi form is hidden from view. It appears to still be on the screen as I can see if flashing occasionally, but is not being redrawn.
If I click on the window (or where it appears to be) it re-appears for a second and then dumps the game out of full screen mode and back into windowed mode. Clearly there is something it doesn't like about the form. The form used to appear in previous editions of this game, but perhaps due to changes in the DirectX version (my guess) it no longer appears.
The form doesn't display anything complicated like 3D graphics, just basic controls like buttons and list boxes. I have tried various Window styles and made it Doublebuffered, all to no avail. Perhaps there is someone out there who has been successful in getting a window to show in these circumstances using a particular function, Window style or header?
Is there some way to make a Delphi form appear correctly within a DirectX environment?
I don't think this can be done.
When a DirectX Video window becomes full screen, then DirectX takes over the complete screen drawing (similar to when you make a Windows Media Player full screen: all other apps disappear, and sometimes even the resolution changes).
Related
I have an application which is designed for multiple monitors. It starts up, and we try to avoid activating windows that do not need to be activated, because the user only does keyboard input in one place, and each time we Activate a new form on a secondary monitor, it grabs keyboard focus, something we wish to avoid.
Our in-house base TForm class has a method like this, which is using the Win32 ShowWindow function directly, avoiding the VCL framework's internal visibility change system which grabs focus:
procedure TOurForm.ShowWithoutActivate;
begin
ShowWindow(Self.Handle, SW_SHOWNOACTIVATE);
Self.Visible := true;
end;
If I just did this, it would grab focus:
Self.Visible := true; // TWindow.Visible = true, will grab focus, plus make window visible.
This works, but the next thing I'd like to be able to do is set Maximized state so that the
form will maximize itself on the Monitor that it is currently on. How do we get it onto a particular monitor? The same way it always worked, with modification of the Left and Top properties of the Form. We have to take care that if we store Left/Top/Width/Height on form, and then restore it, that the results are still valid when we reload it. That is NOT what I'm asking about.
I'm specifically asking about how to maximize the form now that I have "showed" it using the above custom function. One hack begets another hack. Here is how far down this rabbit hole I've gone:
When a TForm, which is also a TWinControl's private field FShowing is false, setting Form.Maximized has no effect.
When a TForm has its TWinControl.FShowing field set true, setting the windowState to wsMaximized also causes the form to activate.
Is it possible to both make this form visible and make it take the window state I want it to take without activating? If I can't do this, then users are going to lose their keyboard focus when I show this form on a secondary monitor, something that I really want to avoid.
What I tried is to use Win32 ShowWindow API to do SW_SHOWMAXIMIZED:
ShowWindow(Self.Handle, SW_SHOWMAXIMIZED);
The above seems to grab focus (activate).
When you create the top-level window set the extended window style to
WS_EX_NOACTIVATE | WS_EX_APPWINDOW
WS_EX_NOACTIVATE stops the window activating. This also makes it disappear from the taskbar, so you need WS_EX_APPWINDOW to fix that problem.
Call ShowWindow(hWnd, SW_MAXIMIZE) and the window will be maximized but not activated.
You need to be able to activate the window once it is visible, so in the WM_ACTIVATE handler (the irony!) you need to clear the WS_EX_NOACTIVATE flag thus:
case WM_ACTIVATE:
{
DWORD exstyle = GetWindowLong(hWnd, GWL_EXSTYLE);
if (exstyle & WS_EX_NOACTIVATE)
{
SetWindowLong(hWnd, GWL_EXSTYLE, exstyle & ~(DWORD)WS_EX_NOACTIVATE);
}
}
Apologies for the C++. This should be simple to translate into Delphi.
EnumDisplayMonitors API enumerates the monitors and their coordinates on the joint desktop, area into which a particular monitor maps to its specific position.
To find out which monitor is "current" you would want to compare the current window position against monitor coordinates rcMonitor/rcWork. Or, you have MonitorFromPoint and friends to help you.
Once to decided which monitor you want, you can either move your window (MoveWindow, SetWindowPos) to monitor's work area, or use this rectangle in response to WM_GETMINMAXINFO message to send the window to this position as a part of standard maximization.
To add to this, this small C++ application [1, 2, 3] demos the concept mentioned above, shows monitor information, and changes position where the window would be maximized to.
My application have several MDI forms and one of this form have child modal form with detailed information. So, when I open this modal form from my MDI form, I click 'browse' button and create OpenFileDialog. Everything works fine, except when I ALT+TAB.
When I ALT+TAB and then ALT+TAB back to my application I see that OpenFileDialog (messageboxes too) is BEHIND my modal window, but in fron of MDI window.
There is no StayOnTop or something like that.
Only way to bring back OpenDialog in front of all windows is to make second ALT+TAB to my application. This causes Dialog to pop in front of all other windows.
What can I do to prevent Dialog from hiding behind my Modal form? Any suggestion?
I use delphi7 and can't use greater version
I'm hypothesising that the issue is related to window ownership. In Delphi 7, file dialogs have the hidden application window as their window owner. But the window owner really needs to be the window of the active form.
There are plenty of ways to fix this, but perhaps the simplest is to subclass TOpenDialog and override its TaskModalDialog like this:
function TMyOpenDialog.TaskModalDialog(DialogFunc: Pointer;
var DialogData): LongBool;
var
hwndOwner: HWND;
begin
hwndOwner := Screen.ActiveForm.Handle;
if hwndOwner = 0 then
hwndOwner := Application.MainForm.Handle;
if hwndOwner = 0 then
hwndOwner := Application.Handle;
TOpenFilename(DialogData).hwndOwner := hwndOwner;
Result := inherited TaskModalDialog(DialogFunc, DialogData);
end;
I don't have Delphi 7 at hand to test this, but I'm reasonably confident that something along these lines (with perhaps some tweaking of the hwndOwner choice) will sort it out.
I have a largish Delphi 6 app that I have ported to Delphi XE3. At one point the main form launches another non-modal form. Sometimes (say 50%) after a second or two the newly created form moves behind the main form. Even thought it is now at the back, the newly created form still has focus so there are no activate/deactivate events. There are a few Timer controls and I have disabled them. It still happens.
I can accept my code is doing this -- but how can I find out what is happening? Is there a way to intercept when then new form moves to the back?
Just to be clear: I want both forms to be used separately. Any of them can appear behind the other. What is happening at the moment is that the z-order seems to be changing.
I have found the answer to this. I discovered I had added a CreateParams override that did this:
// make a taskbar window
inherited CreateParams( params );
params.ExStyle := params.ExStyle or WS_EX_APPWINDOW;
params.WndParent := GetDesktopwindow; // this line caused the problem
Commenting the WndParent solved it. The effect is bizarre though. It is as if there is a timer that goes off about a second after any key or mouse event that forces the window behind others. Thanks to David Heffernan whose comments about stepping CreateParam made me notice it.
In a Delphi 2010 application with themes enabled I have two TPageControls (one inside the other) in a Form with a clMoneyGreen Background:
The outer page control correctly draw its background with the color of the parent component, the inner page control however draw its background with clBtnFace (red ellipsis in the image) instead of its parent control (TTabSheet)'s white. Is there a way to fix this without returning the page control to its windows classic appearance (lose windows theme)?
All fixes that I've found in Google and here on Stack Overflow involves OwnerDraw which make the page control lose its theming.
I have tried creating new page control component inherited from TPageControl with a method to handle the WM_ERASEBKGND windows message:
procedure TMyPageControl.WMEraseBkGnd(var Msg: TWMEraseBkGnd);
begin
if Parent is TCustomPageControl then
begin
Brush.Color := clWhite;
Windows.FillRect(Msg.dc, ClientRect, Brush.Handle);
Msg.Result := 1;
end
else
inherited;
end;
It paints the background white but some other method called after WM_ERASEBKGND (I'm guessing TWinControl's WM_PAINT method) repaints gray over the white background.
Note: I'm researching this because I'm implementing theming on a large application ported from Delphi 7, that's why I'm trying to solve this problem through a derived component: I can easily search and replace all the 207 TPageControl occurrences with my new class, but placing panels behind some of them would require way more time.
That's a known bug in Delphi 2010. You can work around it by adding a panel that is a child of the outermost tabsheet, and is the parent of the inner page control.
I've no idea why this fixes the problem. I only discovered the workaround myself because I noticed that in my app, the only nested tab/page controls that rendered properly were those with a panel suitably interposed.
If I recall correctly, the defect is not present in the latest Delphi versions.
I have a Delphi 6 application that launches a Wizard after the main form appears. The Wizard is a modal form. One of my users has their Windows desktop extended to more than one monitor. In their case the main form appears on the primary monitor and the Wizard appears on the Extended monitor. This creates confusion because they think the app has frozen when they try to click on the main form. Since the Wizard is open and modal, nothing happens except they hear the warning "ding" tone that tells you a form is not able to receive input.
What can I do to make sure the Wizard form appears on the same monitor as the main form, in this case the primary monitor? I have the Wizard form set to poDesktopCenter.
Manual theory:
Use poMainFormCenter when you want your form to be centered by the Application.MainForm. The application main form is, in short, the first form you can see when you run your application, and you should consider that this main form can be on a different monitor than the active window from which you create and center a new form.
Or if you want to center your form by its Owner, use the poOwnerFormCenter which is IMHO better for user's experience because when you have more than two windows opened by each other, you can move the window to another monitor and create the new window on the monitor where user currently works on.
Practical usecase:
User ran your application on the 1st monitor. The application created the Form2 from its MainForm. User moved that Form2 on the 2nd monitor and from there pressed the button which created another form, Form3.
If you designed your Form3 to use the poMainFormCenter position, the Form3 will be centered by the MainForm which is at this time on a different monitor, what is IMHO confusing.
If you would use code like this for creating and showing Form3:
procedure TForm2.Button1Click(Sender: TObject);
begin
// the Owner parameter Self (or Form2 here) in the Form3 constructor along
// with the Position set to poOwnerFormCenter will ensure you that the form
// will be centered by the current form position, so on the current monitor
// where the user works on as well
Form3 := TForm3.Create(Self);
try
Form3.Position := poOwnerFormCenter;
Form3.ShowModal;
finally
Form3.Free;
end;
end;
You will get Form3 centered by the Form2 but mainly on the same monitor as the Form2 currently lies on, as you currently work on: