Anchor Window to Screen Edges in WPF - c#

I've decided to teach myself C# by writing a music player in Visual Studio 2010. I went with WPF because from what I hear it sounds like it will be a good base to skin from.
I want to program my window with the behavior where if the window comes to the edge of a screen (within 10px or so) it will snap to the screen edge. What's the best way to go about this?

Well there are a few areas you need to address. First to get notifications that the edge is coming close to the screen:
Get notifications that window's size is changing. This one is easy - just use Window.SizeChanged event.
Get notifications that window position is changing. This one is a bit tricky and I am not sure how to achieve it, might need to P/Invoke into Win32 API.
Then, there is a list of TODOs to work out if the window edge is close to the screen edge.
Whether or not there are multiple monitors and if the window is solely contained within on monitor. This answer will help you get the monitor information.
Handle the action of snapping the edge. Will need a bit of rect arithmetic acrobatics for this one. Then you either set Window.Top, Window.Left, Window.Height or Window.Width.
You will need conditional code for each edge but it will look something like this:
void SnapWindow(Window window, Size monitorSize) {
if (window.Left < c_SnapThreshold && window.Left > 0)
window.Left = 0;
if (window.Left + window.Width > (monitorSize.Width - SnapThreshold) && window.Left + window.Width < monitorSize.Width)
window.Width = monitorSize.Width - window.Left; //docks the right edge
//..etc
}
}

Related

Is there a proper way of opening a window in the center of the screen that contains the mouse cursor?

I've seen a lot of good solutions to this problem, but none of them seam to work for all my cases.
My biggest problem is that the WindowStartupLocation property of the window doesn't seam to work correctly (.NET 4.7.2). In the description of the enum it says that the value of CenterScreen opens the window in the center of the screen that contains the mouse cursor.
In my case it seams to be opening in the center of the screen that contains the owner window of the window I try to open.
I checked a bit what the WindowManager from Caliburn Micro does and it indeed sets the owner of the window to the main window of the application if I don't specify an owner. This main window is opened on the correct screen according to the startup location description, but I want to open message boxes and popups anywhere, not just on the first screen.
If I explicitly set the owner to null, it's no longer centered in a lot of cases related to different DPI monitors.
I also tried to move the main window of the application, which is invisible, to the cursor location and that partially solved the problem on my dev machine, indicating that CenterScreen indeed goes to the screen where the owner is, not where the mouse cursor is. But on the tester's machine, it wasn't centered at all on the last 2 of 3 screens (1 laptop 125% and 2 monitors 100%).
What else can I try? Maybe related to owner relations, maybe there is something that I missed in the Caliburn WindowManager?
what about a helper window? I am using it myself in a case where I couldnt find any other solutions. Also it proved having other benefits, I remember I had also had to use its onactivate / deactitave events for something useful.
This is my init, together with System.Windows.Forms.Screen.AllScreens > screen.getworkingarea > it should do the trick
:
helperW.ShowInTaskbar = false;
helperW.Top = -10000000; // Location of new window is outside of visible part of screen
helperW.Left = -10000000;
helperW.Width = 1; // size of window is enough small to avoid its appearance at the beginning
helperW.Height = 1;
helperW.WindowStyle = WindowStyle.ToolWindow; // Set window style as ToolWindow to avoid its icon in AltTab
helperW.Show(); // We need to show window before set is as owner to our main window
this.Owner = helperW;
together with something like:
internal ScreenInfo GetScreenUnderCursor()
{
foreach (var screen in WorkingAreas)
{
System.Windows.Point pos = AddonInterface.Peripherals.Mouse.GetMousePosition();
var b = screen.Bounds;
if (b.X < pos.X && b.X + b.Width > pos.X &&
b.Y < pos.Y && pos.Y < b.Y + b.Height)
{
return screen;
}
}
return null; //not possible???>>>??!/ my screens are more limited than what c# thinks
}

Windows desktop coordinate system is off for certain windows

I've run into an issue when developing some software for windows, an issue related around moving windows on the screen.
Basically, I've found that specific windows will consitantly not follow the coordinate system. I have a function that takes a window and a ratio, then scales up the window to the max size it can be (in that ratio) and still be within the working area of the monitor (not going over the edges or the taskbar).
Example 1: The function works as intended, sizes the window (VSCode) to be the max size it can be in 4:3, and puts it in the corner of the screen (using SetWindowPos and passing in 0,0 for the coords).
Example 2: The exact same function is run on a different window (Firefox) and it is again resized to 4:3 and set to 0,0. However, this time you can see the window isn't quite in the corner, off by 8 pixels to be exact. If I fetch the window position with the api call for that, it returns 0,0, even though it clearly is not. If I manually snap it into the corner myself, then it returns -8,0.
I have no idea what's wrong or how to fix it. It always happens on the same windows, not exactly sure but I think it has something to do with the type of titlebar the window has.
The only other person I've ever seen with this problem is here: https://answers.microsoft.com/en-us/insider/forum/insider_wintp-insider_desktop/desktop-coordinate-system-is-broken/9e6fd9ab-6d27-45e0-bb55-4c868cd6ac45
Unfortunately, he never got a real answer, so I'm back to square one.
So, any ideas of some way to consistently set windows to actually be in the corner? I've come up with some janky solutions by like trying to dynamically find how much a window offsets from the corner but it's not really something I want to be relying on. Is this just a bug with windows and I'm stuck?
Thanks, let me know if you have any questions!
This is the design of windows system.
I saw similar questions long ago.
Windows 10 has thin invisible borders on left, right, and bottom, it is used to grip the mouse for resizing. The borders might look like this: 7,0,7,7 (left, top, right, bottom).
The borders are invisible in Windows 10, so it appears that the window is in the wrong place. Visual Studio IDE has its own tool-windows, when tool-window is docked, it uses no borders or custom NC_PAINT; and when its tool-window is floating, it uses default borders.
A workaroud:
RECT rect, frame;
GetWindowRect(hwnd, &rect);
DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frame, sizeof(RECT));
//rect should be `0, 0, 1280, 1024`
//frame should be `7, 0, 1273, 1017`
RECT border;
border.left = frame.left - rect.left;
border.top = frame.top - rect.top;
border.right = rect.right - frame.right;
border.bottom = rect.bottom - frame.bottom;
//border should be `7, 0, 7, 7`
Then offset the rectangle like so:
rect.left -= border.left;
rect.top -= border.top;
rect.right += border.left + border.right;
rect.bottom += border.top + border.bottom;
//new rect should be `-7, 0, 1287, 1031`
For more information, please refer #Barmak Shemirani's answer.
Similar case:
Retrieve Window Size without Windows Shadows
GetWindowRect returns a size including “invisible” borders
Note: DwmGetWindowAttribute() returns physical coordinates, but GetWindowRect() returns logical coordinates. So the border width will be wrong for a non-DPI aware application on a screen scaled to anything other than 100%.

Force external process to redraw borders after SetWindowPos

I am just trying to see if I can make a program that will sit along the left side of my monitor.
By doing so I am using a BackgroundWorker to loop through all of the users process (ones with a MainWindowTitle) and using SetWindowPos to move and resize them based upon my sidebar.
This all works fine except it causes the border to not draw (I guess that is a way to explain it).
I have attached 2 images and as you can see the borders don't seem to draw (and for Visual Studio it doesn't resize based upon the application BorderStyle)
This is the code I have so far:
foreach (Process p in Process.GetProcesses())
{
if (p.MainWindowTitle == "") continue;
if (p.MainWindowTitle.ToLower().Contains("studio"))
{
IntPtr i = p.MainWindowHandle;
RECT r;
GetWindowRect(i, out r);
if (r.Left <= -1608)
SetWindowPos(i, HWND.Top, Screen.AllScreens[1].Bounds.Left + 200, Screen.AllScreens[1].Bounds.Top, Screen.AllScreens[1].Bounds.Width - 200, Screen.AllScreens[1].Bounds.Height, SetWindowPosFlags.SWP_NOACTIVATE);
}
}
As you can see I am just trying to resize and reposition any (just Visual Studio at the moment) window on my second monitor (to the left of my first using a hackish kind of check :D)
I think you are misinterpreting what you see there: the borders do not draw, because (before you moved them) the windows where MAXIMIZED. Maximized windows typically don't have a border in Windows.
The solution is to first bring them back to normal state, before moving them to the desired coordinate, i.e.
ShowWindow(i, ShowWindowCommands.Normal);
That said, you'll still have to deal with multi-window processes and a myriad of fun little challenges. Enjoy the ride :-).

Set custom window position on wpf

I think there is a bug with the Window.Width and Window.Height of wpf. I need my window to cover my entire desktop (there are two monitors) but first let me show you why I think there is a bug.
First let me show you the resolution of my monitors: (both have the same resolution)
this is the window on visual studio that I am working with
I don't know if you guys know AutoIt but it is a nice program to automate simple stuff. so if I want to resize my MainWindow to cover all the space in my first monitor (same thing as if I where maximizing a window) I will execute this method on autoit:
and when I execute that code my window that I am creating in visual studio extends and it appears exactly as if it where to be maximized.
so far the coordinates seem to be working.
now when I do the same thing with c# on wpf:
public static void setWindowSize(System.Windows.Window w)
{
w.Left = 0;
w.Top =0;
w.Width = 1920;
w.Height = 1079;
}
The parameter w will be the MainWindow. When I execute that take a look how the window get's resized:
I placed the older image next to it so that you guys can compare it. Why are the dimensions not the same? I belive that the Window.Width and Window.Height properties do not work correctly. or what am I doing wrong?
If you want to cover your main monitor then
Width = SystemParameters.PrimaryScreenWidth, Height = SystemParameters.PrimaryScreenHeight
if you want to cover both monitors
Width = SystemParameters.VirtualScreenWidth, Height = SystemParameters.VirtualScreenHeight
try out this
Width = SystemParameters.PrimaryScreenWidth,
Height = SystemParameters.PrimaryScreenHeight,

creating resolution independent winform,set resolution for C# winform, get the screen resolution on which the winform is running

I made an application on 1366x768 resolution which is my laptop's current resolution, being a beginner, i din't care about the resolution issues.
When i took that application and executed it on a lower resolution PC , then all was messed up. The form was going somewhere else, most of its part hidden.
So, please help me to how to make my winform resolution independent, or how to get the current screen resolution and set it into my winform so that it changes itself accordingly.
The property
Screen.PrimaryScreen.WorkingArea
is very useful for form sizing and positioning.
For example this code:
this.Width = Screen.PrimaryScreen.WorkingArea.Width/2;
this.Height = Screen.PrimaryScreen.WorkingArea.Height/2;
this.Top = (Screen.PrimaryScreen.WorkingArea.Top + Screen.PrimaryScreen.WorkingArea.Height)/4;
this.Left = (Screen.PrimaryScreen.WorkingArea.Left + Screen.PrimaryScreen.WorkingArea.Width)/4;
(The Screen class is a member of System.Windows.Forms namespace)
will place the form in which it is executed in the middle of the screen and size it to half the screen.
The WorkingArea var is used to exclude stuff like the task bar and other docked items on the desktop when calculating the size of the screen.
Hope this helps.
See here: http://social.msdn.microsoft.com/forums/en-US/vbgeneral/thread/1be40692-80fa-42ba-b316-9fe13a935b4f/ and here: http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/d5f2cd8d-d24d-4574-ba3a-96e22e3d1b56/
General approach is to start with the min resolution you'll be working with. Then you can scale your forms up (using proper docking and anchors).

Categories

Resources