win32: detect if start menu is auto-hidden? - windows

I want to position a window at the bottom of the screen. If the start menu is present, I want it to lie along the top of the start menu. If it is not (or it is auto-hidden), I still want it to be in the same position as it would be if the start menu was there, meaning there will be a gap of a few pixels.
Currently I get the monitor work area, position the window at the bottom, and always offset by 20 pixels or so. If the start menu isn't there, this works well. If it is, however, the work area also shrinks (as it should), and I end up double-offsetting.
How would I fix the issue?

To get the work area of the screen not obscured by the system taskbar or by application desktop toolbars, you can use SystemParametersInfo() with SPI_GETWORKAREA as uiAction parameter. The pvParam parameter must point to a RECT structure that receives the coordinates of the work area, expressed in virtual screen coordinates. For example:
RECT rectWorkArea;
SystemParametersInfo(SPI_GETWORKAREA, 0, &rectWorkArea, 0);
As you said in the comment, to get the bounding rectangle of the taskbar, we can call SHAppBarMessage(ABM_GETTASKBARPOS, ...)
To determine the position of the taskbar (whether it is currently at the bottom, top, left, or right of the screen), you could use the following calculation:
type
TTaskBarPos = (_TOP, _BOTTOM, _LEFT, _RIGHT);
var
iScrW, iScrH: Longint;
iScrW := GetSystemMetrics(SM_CXSCREEN);
iScrH := GetSystemMetrics(SM_CXSCREEN);
if (rectTaskbar.Top > iScrH div 2) and (rectTaskbar.Right >= iScrW) then
Result := _BOTTOM
else if (rectTaskbar.Top < iScrH div 2) and (rectTaskbar.Bottom <= iScrW div 2) then
Result := _TOP
else if (rectTaskbar.Left < iScrW div 2) and (rectTaskbar.Top <= 0) then
Result := _LEFT
else
Result := _RIGHT;
They should be enough to solve your current problem. However, if you need to know (for another reason) the current taskbar settings of the autohide and always-on-top states, you can use SHAppBarMessage(ABM_GETSTATE, ...).
If you need to be notified that that the taskbar's autohide or always-on-top state has changed, you have to intercept ABN_STATECHANGE message.

Are you are using or have access to .NET in your project?
If so, you can use the Screen.PrimaryScreen.WorkingArea.Height property to determine the bottom of the screen excluding the task bar.
You can also grab the total screen height by getting the Screen.PrimaryScreen.Bounds.Height property (which includes the task bar in the total height value).
Comparing these values, if they're the same, the task bar isn't present. Otherwise, the task bar is and you can adjust accordingly.

Related

How to stick window to left half part of screen?

I am using xlib to set window to certain position using XMoveResizeWindow function
All works fine, but when I want to 'stick' my window to left part of screen, I get the wrong position and size: there is a space between window and left, top and bottom screen borders.
To find out 'right' coords & dimensions of window stuck to left manually, I have written a watcher program which continuously calls XGetGeometry and prints results to screen, so I got these results of a 'stuck' window:
X: -40
Y: 14
W: 2000
H: 2426
When I pass these values to XMoveResizeWindow function, I still get the wrong position.
I have extended my watcher program to print all available properties of the window to see which will be changed when I manually 'stick' the window, but nothing changed except max_width and max_height fields of WM_NORMAL_HINTS property.
If you have an idea what am I doing wrong, please let me know.

How to get window original size and position (wsNormal vs wsMaximized)

I'm looking to store user personalization of window size and position to use it when the application is reopened. That's actually very simple and I have the following code working:
OnCreate:
Width := wIni.ReadInteger('FORM', 'FORMW', 980);
Height := wIni.ReadInteger('FORM', 'FORMH', 500);
PnlXMLI.Width := wIni.ReadInteger('FORM', 'PNLXMLIW', 500);
WindowState := TWindowState(GetEnumValue(TypeInfo(TWindowState), wIni.ReadString('FORM', 'WINDOWSTATE', 'wsNormal')));
OnDestroy:
wIni.WriteInteger('FORM', 'FORMW', Width);
wIni.WriteInteger('FORM', 'FORMH', Height);
wIni.WriteInteger('FORM', 'PNLXMLIW', PnlXMLI.Width);
wIni.WriteString('FORM', 'WINDOWSTATE', GetEnumName(TypeInfo(TWindowState), Ord(WindowState)));
The problem is that when the user maximizes the window, and then restore it, it goes back to the size it was before maximizing. But if he maximizes, and then close and reopen the application and restore it, the application will not go back to original size before maximizing. It will be at the size of screen, because Width and Height properties gives the maximized size when read.
Question is: how can I get the original size of the window, i.e., the one it will go back to when the user restore the window? I tried setting WindowState to wsNormal right before reading the Width and Height, but it didn't work (maybe because the form is being destroyed?)... and I'd rather use a solution that doesn't do unnecessary GUI operations (for source code aesthetic reasons).
Thanks in advance.
The functions that you need are GetWindowPlacement and SetWindowPlacement. These operate on the WINDOWPLACEMENT struct. That struct has the rcNormalPosition member which is documented like this:
The window's coordinates when the window is in the restored position.
When you save away the window position and size you need to save away the values found in rcNormalPosition. In the opposite direction, when restoring the window position and size you must call SetWindowPlacement passing as rcNormalPosition the value stored in the user preference in the INI file.

How do I change the viewport of a window in win32?

I have a window with child windows inside in it. The child windows take up about 1000 pixels of vertical space. However, our users don't always have 1000 pixels of vertical space available - they might have as little as 500 or 600 pixels.
I want to be able to display this window at a size of 500 pixels high, and have the user "scroll" up and down the window to see the full contents. The window should always be 500 pixels high, but the view within it should change.
Assume I can add a scroll bar somewhere so the user can choose which part of the window he wants to see. Windows will normally paint the window contents from height 0 to height 500; how do I tell it instead to "paint from height 250 to height 750", for example?
I know that I can set the viewport with functions like SetViewportOrgEx etc, but those functions require a device context - when do I call them if I want them to be "permanent"? Do I call them when I get the WM_PAINT message from windows? Or at some other time? And which functions from that family do I want to use?
Edit to add: I don't want to actually change the position of the child windows - they should stay at the same position, and the only thing that should change is the view into the window.
Thanks.
If (when you get messages about the scroll bars changing) you call ScrollWindowEx with the SW_SCROLLCHILDREN flag, the child windows should be told to scroll along with everything else. This ought to put them in the right position.

AutoHotkey MouseMove not centering properly

I'm running the below code and I expect the mouse to move to the center of the currently active window when I hit comma.....instead it is moving to different points on the screen, depending on where the window is on the screen. It only centers the mouse properly when the window is positioned at the top left (x=0, y=0).
#NoEnv
SendMode Input
#WinActivateForce
Sysget, Mon2, Monitor, 2
,::
WinGetActiveStats, Title, Width, Height, X, Y
{
MsgBox, The active window "%Title%" is %Width% wide`, %Height% tall`, and positioned at %X%`,%Y%.
;center_x:=X+(Width*.5)
;center_y:=Y+(Height*.5)
MouseMove, X+(Width*.5), Y+(Height*.5), 90
}
Return
Check out CoordMode in the documentation.
Sets coordinate mode for various commands to be relative to either the
active window or the screen.
CoordMode, ToolTip|Pixel|Mouse|Caret|Menu [, Screen|Window|Client]
The default CoordMode is Screen which is why you get two different locations. Set the CoordMode to Window to assure that your mouse centering works on the active window.
You can set it for the entire script by calling it during the Auto-Execute section of the script.
The following script will move mouse to Active Window on dual screen system.
I could not get it to work until I put in the sleep line, WinGetPos was getting info before the window had moved.
~#+right::
~#+left::
Sleep,1000
WinGetPos, X, Y, width, height, A
center_x:=width/2
center_y:=height/2
MouseMove,center_x,center_y,
return
I would be sure that Width and Height are the actual dimensions of the window and not the screen resolution. Then check X and Y to ensure they're the actual top-left corner of the active window.
If the width and height are not from the actual window, i.e. the screen size, then this is expected behaivor. Perhaps you could show us the calling function to get a better idea of where those parameters are coming from.
the problem was that MousMove uses the coordinates of the window by default, so I changed the MouseMove line to the following:
MouseMove, Width*.5, Height*.5
All is good.
Tried it all nothing works. The trick is to use DllCall("SetCursorPos", int, x, int, y).
Here is the code to move mouse to center of window. Works on multi-montior and non-fullscreen windows.
MoveMouseInCenterOfActiveWindow:
CoordMode,Mouse,Screen
WinGetPos, winTopL_x, winTopL_y, width, height, A
;MouseMove, X, Y, 0 ; does not work with multi-monitor (always off)
winCenter_x := winTopL_x + width/2
winCenter_y := winTopL_y + height/2
DllCall("SetCursorPos", int, winCenter_x, int, winCenter_y)
Tooltip winTopL_x:%winTopL_x% winTopL_y:%winTopL_y% winCenter_x:%winCenter_x% winCenter_y:%winCenter_y%
return

How do you get CreateWindowEx() to create the window on a specific monitor?

I've determined that I can use GetSystemMetrics(SM_CMONITORS) to query the number of attached monitors, but is there any way to control what monitor CreateWindowEx() uses for the window?
Yes, by the "x" and "y" arguments. Use EnumDisplayMonitors (pass two nulls) to enumerate the monitors. Your MonitorEnumProc callback gets a RECT* to the monitor's display rectangle. You'd get a negative RECT.right if a monitor is located at the left of your main one.
Each monitor simply displays some part of the desktop, so showing the window on a particular monitor is a matter of moving the window to the part of the desktop displayed by that monitor. When you call CreateWindowEx (or CreateWindow) you can specify x and y coordinates for the window, so displaying it on a particular monitor simply means specifying coordinates that fall within the area displayed by that monitor.
You can find the work areas for the monitors on a system with GetMonitorInfo.
The x and y parameters specify the location of the new window. This point can be anywhere on the virtual screen (all the monitor rectangles combined).
If you want to create the window on the same monitor as another window you can call MonitorFromWindow. Otherwise can enumerate all the monitors with EnumDisplayMonitors.
Either way, once you have a HMONITOR handle you must then call GetMonitorInfo. Your x and y parameters should be a value inside the bounds of the rcWork member in the monitor info struct. You would normally choose values that puts your window in the center of this rectangle.
It is important to use the workarea rectangle and not the full monitor rectangle because you don't want your window to appear underneath the Taskbar and other always-on-top appbars.

Resources