Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make a form visible and maximize it to fill a secondary monitor without it activating?

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).

like image 965
Warren P Avatar asked Dec 26 '22 16:12

Warren P


2 Answers

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.

like image 108
arx Avatar answered Dec 28 '22 08:12

arx


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.

like image 38
Roman R. Avatar answered Dec 28 '22 09:12

Roman R.