2

In a Delphi 10.4.2 Win32 VCL application running on Windows 10, in a dual-monitor setup, when I set my MainForm (or any other secondary form) to start maximized by setting WindowState := wsMaximized, then the form is maximized only on the Primary Monitor.

How can I maximize the Form to the whole Desktop instead, to set the Form size to include BOTH MONITORS? Is there a built-in method of the TForm class to achieve this?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
user1580348
  • 5,721
  • 4
  • 43
  • 105

3 Answers3

9

In general, this problem isn't as simple as you may think. I suppose you are imagining a desktop like this:

A simple desktop layout consisting of two monitors of the same size placed next to each other.

In this case, I assume you want the window to be placed like this:

A window spanning both screens.

However, what if the user has this layout:

Landscape + portrait + landscape screen setup

Do you want

Entire window visible, but some screen space unused

(entire window visible, but some screen space unused) or

No unused space, but some parts of the window not visible.

(no unused space, but some parts of the window not visible)?

If you want to use the full virtual desktop space -- the last case -- it is easy though:

BoundsRect := Screen.DesktopRect;

This will do the expected thing in a simple setup, and the "no unused space, but some parts of the window might not be visible" thing in general.

Also be aware that Windows doesn't like that windows behave like this, so the user might not get a nice experience using the app.

In general, don't do this.


Please note that even a two-monitor setup, in which both monitors are landscape, can be non-trivial:

One large monitor next to a small one.

The geometry may be non-trivial even if both monitors are the same size:

Two same-size monitors next to each other, both in landscape, but with displaced in the orthogonal direction.

Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
  • When I execute `Self.BoundsRect := Screen.DesktopRect;` in the form `OnCreate` handler then the form is still maximized on the primary monitor. – user1580348 Jul 27 '21 at 20:50
  • 1
    @user1580348: Make sure NOT to set `WindowState` to `wsMaximized`. A maximized window will only use a single screen. (In Windows, "maximized" roughly means "uses a (single) full screen", so if you want to use two or three screens, the window cannot, by definition, be "maximized".) – Andreas Rejbrand Jul 27 '21 at 20:51
  • I have given up the idea to extend my form to both monitors. Instead, I will switch my maximized form from one monitor to the other monitor. I will have to find out how to do this. – user1580348 Jul 27 '21 at 21:26
  • Maybe it works on Windows 7 but not on Windows 10? – user1580348 Jul 27 '21 at 21:48
  • Unlikely. Are you absolutely sure you have `WindowState = wsNormal`? Do you use DPI scaling (like one monitor on 100% and the other on 120%)? That can be an issue. Then it might depend on your app's DPI awareness. – Andreas Rejbrand Jul 27 '21 at 21:50
  • Yes, `WindowState = wsNormal`. – user1580348 Jul 27 '21 at 21:51
  • And yes, the form is extended to both monitors - but the selection does not work on this second monitor. So I will accept your answer - but I have to find out why the selection does not work on the second monitor. – user1580348 Jul 27 '21 at 21:56
  • Okay, but then the problem is not related to the "make form span entire desktop" thing, but to some other code which we cannot see here! Likely some coordinate transformation going wrong. In any case: an unrelated issue. – Andreas Rejbrand Jul 27 '21 at 21:57
  • If you settle for the "cover the entire desktop with my own form on which I will paint the desktop" approach, a different subapproach would be to create one form per monitor. Then you don't waste bitmap space for the unused regions. You can iterate `Screen.Monitors` (using `for i := 0 to Screen.MonitorCount - 1`) to get the bounds for each monitor. – Andreas Rejbrand Jul 27 '21 at 22:00
  • @So I would have to create **n** clones of my form, one for each monitor? – user1580348 Jul 27 '21 at 22:08
  • Yes, that was my idea. Some things might be simpler that way in your particular scenario. – Andreas Rejbrand Jul 27 '21 at 22:08
  • People use notebooks and plug monitors to it, using both screens for different tasks. Although both screens display one desktop they have different sizes and different positions, most likely not next to each other (notebook screens start right off the desk, monitors rarily are able to do so). This answer covers how different environments can look like. @user1580348 Let the user decide what he wants. – AmigoJack Jul 27 '21 at 22:19
3

Per MSDN:

Positioning Objects on Multiple Display Monitors

A window or menu that is on more than one monitor causes visual disruption for a viewer. To minimize this problem, the system displays menus and new and maximized windows on one monitor.

So, if you want the TForm window to stretch across the whole desktop, using WindowState=wsMaximize is not the way to go, as it will only work on the single monitor that the Form is being mostly displayed in.

To do what you ask, you will have to get the rectangle of the Virtual Screen from GetSystemMetrics() (or Vcl.Forms.TScreen), and then set the Form's Left/Top/Width/Height accordingly, eg:

if Screen.MonitorCount > 1 then
begin
  Form.WindowState := wsNormal;
  Form.Left := Screen.DesktopLeft;
  Form.Top := Screen.DesktopTop;
  Form.Width := Screen.DesktopWidth;
  Form.Height := Screen.DesktopHeight;
  // or:
  Form.SetBounds(Screen.DesktopLeft, Screen.DesktopTop, Screen.DesktopWidth, Screen.DesktopHeight);
  // or:
  Form.BoundsRect := Screen.DesktopRect;
end else
begin
  Form.WindowState := wsMaximized;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • What's wrong with a simple `Self.BoundsRect := Screen.DesktopRect`? – Andreas Rejbrand Jul 27 '21 at 20:45
  • @AndreasRejbrand See my update. I don't generally work with multiple monitors, so I had to look it up – Remy Lebeau Jul 27 '21 at 20:51
  • This code seems to correctly extend the window to both monitors. However, the functionality of what I am doing works only on the primary monitor. What am I doing: I copy the whole desktop to a graphics control which is set to fill the whole client area of the form. This results in the graphics control to look identical as the desktop "behind" the form. Then I select a partial area of the graphics control and process it for my need. But as this code correctly shows the copy of the desktop in the graphics control, the selection only works on the primary monitor. – user1580348 Jul 27 '21 at 21:12
  • @user1580348 I really have no idea what you are trying to do. What is the point of copying the desktop into your UI? Why not just work with an in-memory bitmap instead? What are you REALLY trying to accomplish? – Remy Lebeau Jul 27 '21 at 21:17
  • This is the same as opening Notepad and drag its size manually to fill both monitors: The part of the Notepad window on the primary monitor takes my input and works as usual, but the part of the Notepad window on the second monitor is barely visible. From that I conclude: A window can be active only on one monitor and cannot be extended to multiple monitors for input/output. So it seems that what I tried is not possible: Having a fully working form extended to more than one monitor. – user1580348 Jul 27 '21 at 21:19
  • @user1580348: That is possible on my system. I just made a Delphi app with a TMemo and made it display on my full desktop consisting of four monitors, not all the same, and some invisible space. I am able to write text in it just fine. – Andreas Rejbrand Jul 27 '21 at 21:22
  • The point is to create an illusion for the user that what he sees is the real desktop. Then he can select a part of that image and copy it to get a "screenshot" from the selected area from the desktop. – user1580348 Jul 27 '21 at 21:22
  • Yes, I understand this Q is a continuation of https://stackoverflow.com/questions/68467642/problems-when-drawing-a-frame-around-a-window, https://stackoverflow.com/questions/68498524/correct-handling-when-drawing-on-the-desktop-and-final-cleanup, and https://stackoverflow.com/questions/68509311/partially-transparent-form-without-click-through. – Andreas Rejbrand Jul 27 '21 at 21:23
  • @AndreasRejbrand You are right. When doing this with only one monitor then it works perfectly. So my next step will be to just switch my form from one to the other monitor. – user1580348 Jul 27 '21 at 21:30
  • @user1580348 "*The point is to create an illusion for the user that what he sees is the real desktop. Then he can select a part of that image and copy it to get a "screenshot" from the selected area from the desktop*" - then why not just work with the REAL desktop? Use a mouse hook to track where the user is selecting, and then grab an actual screenshot of the real desktop? I'm confused... – Remy Lebeau Jul 27 '21 at 21:34
  • @RemyLebeau Believe me, I have tried that in many ways - but nothing works without problems. Look at the SO history of my recent postings... – user1580348 Jul 27 '21 at 21:40
  • 1
    Yes, it is better to use the real desktop and only use a transparent overlay window for the selector rectangle. For one thing, the desktop may change (e.g., a clock app might be redrawn every second, an animation/video many times per second). – Andreas Rejbrand Jul 27 '21 at 21:40
  • @AndreasRejbrand That is easy to say but I was not able to achieve that... – user1580348 Jul 27 '21 at 21:42
  • Did you try with a mouse hook? Well, copying a bitmap and displaying on your own form should work as well (but with more memory and CPU consumption and no real-time update). – Andreas Rejbrand Jul 27 '21 at 21:43
  • @AndreasRejbrand The problem is not the mouse hook but to draw a selection frame on a transparent layer... I've tried that - but it is not possible. – user1580348 Jul 27 '21 at 21:44
  • @user1580348 creating a standalone top-level semi-transparent window that the mouse can position and size over the desktop should be a very basic thing to accomplish, and then you can hide the window and screenshot what was underneath it as needed. – Remy Lebeau Jul 27 '21 at 21:45
  • @user1580348: Didn't you do that a few days ago? https://stackoverflow.com/questions/68509311/partially-transparent-form-without-click-through – Andreas Rejbrand Jul 27 '21 at 21:45
  • @AndreasRejbrand Yes, but it did not work without the problems I've pointed out. – user1580348 Jul 27 '21 at 21:47
  • You mean the click-through issue? That's what the mouse hook will solve. https://learn.microsoft.com/en-us/windows/win32/winmsg/about-hooks#wh_mouse – Andreas Rejbrand Jul 27 '21 at 21:49
0

This is not standard behaviour for a Windows application. Also note that as the desktop can have multiple monitors which do not need to be aligned so the desktop may not be a rectangle - which means that the bounding rectangle for the desktop may contain parts which are not visible.

If you want to do this you can use the Windows function GetDesktopWindow to get the desktop window, then get its size, and then set the size of the form to that.

procedure TMyForm.GoLarge();
var
  rctDesktop:   TRect;
  hDT:          HWND;
begin
  hDT:=GetDesktopWindow();
  if(hDT<>0) then
  begin
    GetWindowRect(hDT, rctDesktop);
    Self.SetBounds(rctDesktop.Left,  rctDesktop.Top, rctDesktop.Width, rctDesktop.Height);
  end;
end;

This makes no allowance for the task bar or anything else which is using some of the desktop space.

Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
Rob Lambden
  • 2,175
  • 6
  • 15