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?
In general, this problem isn't as simple as you may think. I suppose you are imagining a desktop like this:
In this case, I assume you want the window to be placed like this:
However, what if the user has this layout:
Do you want
(entire window visible, but some screen space unused) or
(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:
The geometry may be non-trivial even if both monitors are the same size:
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;
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.
Related
I already know and have used these methods to create a form without boders that can be moved.
Move form without border style
I'm using the WMNCHitTest override.
The MouseDown event don't work at all.
This form is very simple it is displaying a countdown, a number that changes very second and that's all. The number being painted using a big TLabel with big fonts.
But I also made this form transparent by using Delphi's standard form properties.
Now, if I try to click on the form to move it, the only area I can use is the lines drawing the changing numbers, even if they are not so thin, this is not practical.
I'd like the user to be able to move the numbers to any position of the screen by clicking anywhere near the numbers, let's say inside a "0" or an "8".
I'd think about drawing a transparent rectangle over the numbers and be that the clickable area, but the effect would be the same, the click would go throu.
I know an application that does this, so it is possible, but how?
procedure TfrmCountDown.Timer1Timer(Sender: TObject);
begin
iCount := iCount - 1;
lblTime.Caption := FormatFloat('00', iCount);
end;
procedure TfrmCountDown.FormCreate(Sender: TObject);
begin
iCount := 60;
BorderStyle:=bsNone;
Self.Color := clGray;
Self.TransparentColor := true;
Self.TransparentColorValue := clGray;
end;
procedure TfrmCountDown.WMNCHitTest(var Message: TWMNCHitTest);
var
Pt: TPoint;
begin
Pt := ScreenToClient(SmallPointToPoint(Message.Pos));
if Pt.Y < 160 then
Message.Result := HTCAPTION
else
inherited;
end;
VCL Form Transparency (by which presume you to mean the TransparentColor property, as opposed to the Alpha properties) uses Layered Windows to achieve the transparent drawing. Using this technique any transparent area of the window is not only transparent visually but also with respect to hit testing.
i.e. Using VCL form transparency, the transparent areas in your form may as well not exist at all.
What could work is to turn off the VCL form transparency and instead implement your form using the WS_EX_TRANSPARENT window style. This enables visual transparency but allows you to handle hit testing to make different areas of your form transparent, or not, with respect to clicks.
Unfortunately WS_EX_TRANSPARENT is not a complete "transparency" solution - it only tells Windows that your form is transparent, but you also then have to take additional steps to actually be properly transparent, or to interpret what "transparent" means for your specific form.
This means it complicates the visual rendering of your form and you will also have to override the paint mechanism to properly draw your form. From how you describe the content on your form this does not sound like it will be too arduous however, though it is far some straightforward (I do not have a working example unfortunately).
At the very least you will probably be best to replace your TLabel with calls to select an appropriate font and render text into the window client area with a transparent background. But there will be additional house keeping required.
In your WM_NCHITTEST handler, respond with HTNOWHERE for those areas of your form which you wish to be interpreted as "click through" areas, and HTCAPTION for the areas that you wish to support dragging (i.e. from what you describe, within a region defined by the bounds of your text).
Unfortunately I think you will find that there are lots of fiddly aspects to the implementation of painting a transparent window.
I'm building a "dashboard" application which is always visible along any edge of any given monitor, even when other applications are maximized. I don't necessarily need it "always on top" (although I will) but I need to make it a part of the screen as my own desktop toolbar, like the Windows Taskbar is. Even when applications are maximized, the windows are inside of this area, making this window always visible (and the desktop area smaller).
How can I make my application's main form align to the edge of a screen like this?
PS - I don't need an answer to all the extra gritty handling, such as screen resolution changes... I just need to know how to make it aligned as "part of the screen" in the first place.
You're looking for Application Desktop Toolbars, which is what the Windows task bar uses internally. It involves creating a window with specific styles, setting it up correctly, and then communicating with it using SHAppBarMessage.
It can get pretty complex, but there are some free components available with source (one at Torry, or another at DelphiPages) that have the basic shell to get you started.
An example from the AppBar.pas unit of the second link (which, according to the link's text, is freeware with source - I've used it to create an app launcher task bar, complete with buttons with application icons and descriptions read from .lnk files):
type
TAppBarMessage = (abmNew, abmRemove, abmQueryPos, abmSetPos, abmGetState,
abmGetTaskBarPos, abmActivate, abmGetAutoHideBar,
abmSetAutoHideBar, abmWindowPosChanged);
TAppBarEdge = (abeLeft, abeTop, abeRight, abeBottom, abeUnknown, abeFloat);
...
function TAppBar.AppBarMessage(abMessage: TAppBarMessage;
abEdge: TAppBarEdge; lParam: LPARAM; bRect: Boolean; var rc: TRect): UINT;
var
abd: TAppBarData;
begin
// Initialize an APPBARDATA structure
abd.cbSize := sizeof(abd);
abd.hWnd := Handle;
abd.uCallbackMessage := WM_APPBARNOTIFY;
abd.uEdge := Ord(abEdge);
if bRect then
abd.rc := rc
else
abd.rc := Rect(0, 0, 0, 0);
abd.lParam := lParam;
Result := SHAppBarMessage(Ord(abMessage), abd);
// If the caller passed a rectangle, return the updated rectangle
if bRect then
rc := abd.rc;
end;
If nothing else, you can determine this information manually. Have a look at the global Screen object in the Forms unit for information on the current resolution. (Make sure to check the MonitorCount and Monitors properties.)
Between that and a bit of basic arithmetic, it shouldn't be too hard to set up the form to align to the edge of a monitor.
I made a form using a laptop which has 15 inches screen laptop but when i transfer the application to a notebook, some button components are invisible especially those below the form which were visible in the laptop.
so is there a possibility of a code that makes the form adjust itself to fit in a notebook screen or how this can be done using properties?
What you're asking for is also called resolution independence which means to design your forms to make them usable on very small and very big screens (screen resolutions).
This is traditionally done using the Anchors and Align properties, so that controls can size and place themselves according to a certain layout.
Newer Delphi versions also have Margins and AlignWithMargins so that automatic alignment can reserve some space between controls.
Another way that is used by many other toolkits is to use an explicit layout concept. That can be done with TGridPanel and TFlowPanel in Delphi, but doesn't work very nice in my experience. There are better layout management systems out there (like DevExpress Layout Control).
You might also consider using TScrollBoxes, TSplitters and docking to allow users to customize their UI experience.
You can also consider putting some functionality in extra dialogs that are called by buttons or hiding some controls on TPageControl tab sheets.
Scaling is also possible (see Steves answer), but it makes forms appear odd and can drastically reduce the user experience, because controls become too small or users have hard time hitting the right control or fonts are too small, etc.
If the effort is to great or if you have totally different devices (like smart phones vs. workstations) it might even be necessary to have completly different forms or different apps that might use a client / server or multi tier architecture to share the same buisiness logic, but that's actually beyond the scope of this question...
You might read the article by Zarko Gajic at http://delphi.about.com/od/standards/a/aa030700a.htm to understand some of the pitfalls in scaling.
Here is a function that might help:
procedure ScaleForm(theF: TForm; ScreenWidth, ScreenHeight: LongInt) ;
begin
theF.Scaled := True;
theF.AutoScroll := False;
if (Screen.Width <> ScreenWidth) then
begin
theF.Height :=LongInt(theF.Height) * LongInt(Screen.Height) div ScreenHeight;
theF.Width := LongInt(theF.Width) * LongInt(Screen.Width) div ScreenWidth;
theF.ScaleBy(Round(Screen.Width,ScreenWidth)) ;
end;
{the following lines work on an Xp PC but seem to have no effect on Win 7
theF.Position := poScreenCenter; //poDefault, poDesigned,poDesktopCenter,poOwnerFromCenter,poMainFormcenter
theF.Font.Name := 'Arial'; //to scale properly, you have to use a scalable font.
}
end;
Call the function in your application's OnCreate handler ScaleForm(Form1,screen.width,screen.height); Form1 is the handle of your form. Place the function call in a MENU item or button on your form to call it manually, if needed.
Also, here is a simple procedure using the ScaleBy function. ScaleBy can be used to adjust the form's size downward (or upward) in increments
until the entire form fits on the Netbook. The example uses 10 percent increments. Depending on the controls you use in your application, this might be all you need. Many controls will scale automatically. There are more elegant and complicated solutions. In XE2 there is a function
called ChangeScale which may be useful however it might not be available in Delphi 7. Remember, not all controls scale gracefully. You may have more work to do.
procedure TPktForm1.ScaleDown1Click(Sender: TObject);
begin
ScaleBy(90,100); //changes this form where ScaleBy(percentage reduction of reduced form, percentage original form);
Form_A.ScaleBy(90,100); //changes other forms in the application
Form_B.ScaleBy(90,100);
Application.ProcessMessages;
end;
or you might add Scaleby(659, Screen.Height ) in the form's OnCreate where '659' is the programmed original form height to fill a screen or Scaleby(Screen.Height, 659); to make the form smaller. Yes, there are limits to what this technique can do as far as down scaling. Going from desktop to Netbook works fine here.
There are plenty of examples on the Web. Are you using a DBGrid? You will have issues, however you can code around them for that control.
On a multi-monitor system, a "blank" VCL application maximizes fine, but the same application with styles enabled (and one chosen as default) maximizes incorrectly. What I'm seeing is the right-hand edge of the window extend onto the 2nd monitor (my main is on the left). When I started comparing to other Windows apps, I noticed that under Windows 7 (at least), maximized windows do not even have non-client borders on the left, right or bottom sides. And indeed, the standard VCL (non-styled) app behaves this same way, without non-client borders.
How do I fix this? I notice that TFormStyleHook has a handler for WMNCCalcSize, which I haven't dissected yet, but makes me wonder if VCL might be incorrectly handling this message for a maximized window.
After fiddling some time on this, my take is, this is not a vcl-styles bug at all. This indeed is related with the behavior in the article mentioned in a comment to the question by mghie.
The specific behavior is that, the size of a maximized window is larger than the work area of the monitor that the window is maximized on. Supposedly, the window manager hides the overhang borders. Apparently, it doesn't quite do so with customized frames. Note that MSDN's own custom window frame example seems to suffer from the same problem (refer to the post titled "Bug when window is Maximized " in community content). VCL's application is different than the MSDN example in that it's not based on DWM, but I still think it is the same issue.
The overhang borders have the size of the system sizing border (SM_C[X|Y]SIZEFRAME), but this is irrelevant to the workaround below as it disregards the OS suggested size/position and uses the work area.
Unfortunately I don't think this workaround is usable at all. For one, the mentioned behavior is not documented, for two, the workaround is not perfect; there's still an odd pixel out. If you snap the window exactly on the work area, the window manager decides to offset the window to where it thinks the window (with hidden frames) should be placed. (The VCL could probably be modified to do what the window manager does, and take into account overhang and do not draw them or something similar, but it would be more work and it still would be to workaround undocumented behavior..)
Anyway;
type
TForm1 = class(TForm)
..
protected
// overriding styles is not necessary since TFormStyleHook.WMGetMinMaxInfo
// first calls the default window procedure
procedure WMGetMinMaxInfo(var Message: TWMGetMinMaxInfo);
message WM_GETMINMAXINFO;
..
procedure TForm1.WMGetMinMaxInfo(var Message: TWMGetMinMaxInfo);
var
R: TRect;
begin
// always arrives with MinMaxInfo.ptMaxPosition = (-SM_CXFRAME, -SM_CYFRAME)
// and MinMaxInfo.ptMaxSize = (PrimaryMonitor.Width (?) + 2 * SM_CXFRAME, ... )
inherited;
// should test for OS, styles etc. before running the below
R := Monitor.WorkareaRect;
InflateRect(R, -1, -1); // odd pixel
OffsetRect(R, -Monitor.Left, -Monitor.Top);
Message.MinMaxInfo.ptMaxPosition := R.TopLeft;
Message.MinMaxInfo.ptMaxSize := Point(R.Width, R.Height);
end;
The only way i found is to handle WM_SIZE event and modify the window region to chop the extra border.
Is there a way to specify which monitor a application appears on in Delphi or C++Builder?
I am developing a simple program for a customer, which displays kitchen orders on a secondary monitor, generated by a hospitality system. Currently they need to manually drag the window onto the second monitor after it starts.
The global Screen object (part of Forms) has the concept of Monitors. I think this was added circa Delphi 6 or 7. The following code will work:
// Put the form in the upper left corner of the 2nd monitor
// if more then one monitor is present.
if Screen.MonitorCount > 1 then
begin
Left := Screen.Monitors[1].Left;
Top := Screen.Monitors[1].Top;
end;
You could use any positive offset from that position to put it anywhere in that monitor. You can get the width and height from there too to know the dimensions.
Save the window position before program shutdown and restore them on startup. Multimonitor displays just increase the size of the desktop; other monitor surfaces just have a different section of the same X/Y plane with its origin at the top-left of the primary monitor.
This can be done automatically for you by any of several components.
BTW, the Screen variable in the Forms unit has a property called MonitorCount and another indexable property, Monitors[Index: Integer]: TMonitor. TMonitor has properties indicating the left, top, width, height etc., so all the information you need is there.
procedure TMDIChild.btnShowMonClick(Sender: TObject);
begin
if Screen.MonitorCount > 1 then
begin
FormShow.Left:=Screen.Monitors[1].Left;
FormShow.Top:=Screen.Monitors[1].Top;
FormShow.Width:=Screen.Monitors[1].Width;
FormShow.Height:=Screen.Monitors[1].Height;
end
else
begin
FormShow.Show;
end;
end;
Not really the answer your question implies, but couldn't you store the window settings (size, position, Maximized State) when ever the application is closed, and then apply them at startup?
I have done a similar thing a while ago in Delphi 5:
procedure TForm18.FormCreate(Sender: TObject);
var
Mon: TMonitor;
MonitorIdx: Integer;
begin
MonitorIdx := 1; // better read from configuration
if (MonitorIdx <> Monitor.MonitorNum) and (MonitorIdx < Screen.MonitorCount) then begin
Mon := Screen.Monitors[MonitorIdx];
Left := Left + Mon.Left - Monitor.Left;
Top := Top + Mon.Top - Monitor.Top;
end;
end;
Windows will let you specify the coordinates of a window in the CreateWindow API call. I don't know enough about Delphi or C++Builder to know if you have access to that part of the process.
You might also be able to move the window in a WM_CREATE handler.
EnumDisplayMonitors will give you the coordinates of each monitor in the system.
Evidently Delphi and C++ Builder have facilities that make this answer somewhat irrelevant. I'll leave it here in case someone comes across this question but needs it answered for a different environment.
I don't do much with windows systems, so I would suggest a hack like this.
Grab the width of the viewable desktop(both monitors combined), divide it by half and make that your starting position.
You may also look into what api tells you monitor2's dimensions.