I currently have a way to do exactly what im asking, but the results are very in-efficient for some users.
Some users reported it making it as if it minimized (no window showing ever but still in taskbar), some reported for example in an ultrawide 21:9 it would only maximize from the left of a 1080p 16:9 width, yet I have a 32:9 super-ultrawise and have had no issues.
My current flow:
Get the Screen Dimensions excluding the taskbar on-load:
MaximizedBounds = Screen.FromHandle(mForm.Handle).WorkingArea; (mForm = Application.OpenForms[0], for support from any thread and such)
From another thread/function/class run:
Form1.mForm.Invoke(new MethodInvoker(() => Form1.mForm.WindowState = FormWindowState.Maximized));
This should result in a maximized window with the proper resolution, but it doesn't :/
What I would prefer:
NOT require to get the screen dimensions on-load, so use something from official Microsoft Code, either DllImport's, .NET Framework Code, or msapi to get the PROPER MaximizedBounds for Borderless Forms. (formborderstyle.none)
I set MaximizedBounds because if I don't, the application will fullscreen the entire screen, not "maximize" like traditional apps but would end up doing more of a Video Player style fullscreen.
Using my 32:9 screen and my 4k 16:9 laptop's screen on Extend Display mode, I managed to re-create the issue
Re-production Steps:
Open the application, leave it on the screen it first started in
Maximize the application (will work fine)
Unmaximize and move it to the other screen
Click maximize, your result should be like above.
This means, the on-load Maximize Bounds only gets the active bounds once which is expected, but due to me executing the Form Style change on a different class and different thread, I cant actually edit the MaximizedBounds property on it everytime I want to maximize, due to property not being public.
Thanks to HansPassant!
Found out exactly whats needed.
Problem:
When moving application to other monitor, it would use the same bounds. Causing it to not properly Maximize
When updating bounds via WndProc(), it would still try and maximize on Monitor 1 but outside its actual bounds, making it look like the application was hidden >:(
Updating the Bounds (put this in Form1):
protected override void WndProc(ref Message m) {
if(m.Msg == 0x02E0) {
Rectangle b = Screen.FromControl(mForm).WorkingArea; //where mForm is Application.OpenForms[0], this is specific to my applications use case.
b.X = 0; //The bounds will try to base it on Monitor 1 for example, 1920x1080, to another 1920x1080 monitor on right, will set X to 1080, making it show up on the monitor on right, outside its bounds.
b.Y = 0; //same as b.X
MaximizedBounds = b; //Apply it to MaximizedBounds
m.Result = (IntPtr)0; //Tell WndProc it did stuff
}
base.WndProc(ref m); //If any other messages were called, do the defaults this method does. Required even if you edited a msg.
}
Updating DPI Awareness for the WndProc message to fire (put this in app.manifest, make it if it doesnt exist):
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness> //Windows 10
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware> //Old-Compatibility
</windowsSettings>
</application>
Now it no longer shows up out of bounds, and is finally updating the MaximizeBounds when changing monitors!
Thanks #HansPassant for the tips!
Related
I have three monitors/screens of different sizes/resolutions setup as an extended view, all wired to the same NVIDIA graphic card. And from left to right:
Screen#0: 2400*1080
Screen#1: 1920x1080
Screen#2: 1920*1080
My application has three separate Windows, where I respectively position each Window to the corresponding monitor/screen with the following code
window00.Left = System.Windows.Forms.Screen.AllScreens[0].WorkingArea.Left;
window01.Left = System.Windows.Forms.Screen.AllScreens[1].WorkingArea.Left;
window02.Left = System.Windows.Forms.Screen.AllScreens[2].WorkingArea.Left;
window00.WindowState = System.Windows.WindowState.Maximized;
window01.WindowState = System.Windows.WindowState.Maximized;
window02.WindowState = System.Windows.WindowState.Maximized;
I only get the 3rd windows (window02) position to the right most screen properly (Screen#02), but both the 1st and 2nd windows (window00 & window01) stack and maximized to the first screen (Screen#00), leaving the middle Screen#01 empty displaying the background desktop environment, regardless if I set WindowsState.Maximized or Normal.
Even if I workaround the problem through offsetting window01 position by the width of Screen#00 to get all three windows position properly to their corresponding screens, if I maximize Windows.State, window01 still jumps back to Screen#00.
Why is this happening and why does the window not position or maximized to its WorkingArea it assigned to? Could it be due to WPF application using Forms properties?
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 :-).
How do I detect if window is not visible to the user and then move it to a position that is visible to allow the user to size it to their liking.
The problem:-
I have a program that save the position and size of the form this works perfectly.
Unfortunately, many users now have multiple screen graphics cards that can be switched between duplicate and extended.
Therefore, if the user closes the form (the program saves where it was) and the user switches his system to say extended from duplicate then opens the program.
The program form is now not visible because it has moved off the limits of the current screen system :(
The user can see it running on the task bar. However clicking the icon only gives a brief flash of minimising or maximising the program and right clicking only has the option to close.
I need to trap for not visible to the user and then auto resize to any active window. The user can then resize/move as they see fit.
I would welcome any ideas/pointers
This answer was provided for the same question on the MSDN forums - essentially, it's a quick check of the bounds of the screen, and checks to see if the program is within those bounds. If not, it moves it to 0, 0:
using System.Windows.Forms;
class Form1 : Form {
protected void EnsureVisible() {
foreach (Screen scrn in Screen.AllScreens) {
// You may prefer Intersects(), rather than Contains()
if (scrn.Bounds.Contains(this.Bounds)) {
return;
}
}
this.Location = new Point( 0, 0 );
}
}
You could make use of the AllScreens property to get a list of all the screens connected to the computer. Using that, you can check the bounding boxes of each screen and test if your window lies outside all of them. If so, then reset the position.
And Jared Harley beat me to it, while I was typing this.
Is there a way to make make a windows form application full screen and black out your secondary monitors? So the main display is on your primary display and all your other monitors are just completely black?
You can use the Screen class which give you informations about the current active screens.
// Form myFrm
Rectangle r = new Rectangle();
foreach (Screen s in Screen.AllScreens)
{
if ( s != Screen.CurrentScreen ) // Blackout only the secondary screens
r = Rectangle.Union(r, s.Bounds);
}
myFrm.Top = r.Top;
myFrm.Left = r.Left;
myFrm.Width = r.Width;
myFrm.Height = r.Height;
myFrm.TopMost = true; // This will bring your window in front of all other windows including the taskbar
I can think of one way, and that would be to find out how many monitors there are on the computer, and their layout relative to each other, then create your primary window at 0,0, maximize it and set it to be TopMost, then do the same for the other displays, placing them at the screen locations corresponding to the top left of each monitor of the computer.
The only thing I can think of that would benefit from this in a WinForms environment is an app designed to give a test; the app would cover the entire desktop (except the taskbar; you'd have to disable the Start menu) and pretty much ensure that the user couldn't look at anything except the testing program. It will give you a minimal performance advantage.
Most of the apps that black out all the monitors except the main display are basically using DirectX to control the screen directly (through the lower-level interface to the graphics card). If you're using WinForms to make your program, you're about 50 levels of abstraction above using DirectX.
One way would be to create a second form in your application. One that, when given a set of dimensions, will open without border or menu and with the background set to black.
When your application runs, enumerate your monitors (count them to see how many) and find their dimensions. Your primary one will start at 0,0.
Then spawn a copy of that second form for each of your monitors (except your primary one) and give that form the dimensions of the monitor. They will then turn each screen black.
You will need to remember to keep a handle to each of the forms and terminate them when you terminate your main form.
I want to draw directly on the desktop in C#. From searching a bit, I ended up using a Graphics object from the Desktop HDC (null). Then, I painted normally using this Graphics object.
The problem is that my shapes get lost when any part of the screen is redrawn. I tried a While loop, but it actually ends up drawing as fast as the application can, which is not the update rate of the desktop.
Normally, I would need to put my drawing code in a "OnPaint" event, but such thing does not exist for the desktop.
How would I do it?
Example code: https://stackoverflow.com/questions/1536141/how-to-draw-directly-on-the-windows-desktop-c
I posted two solutions for a similar requirement here
Basically you have two options.
1- Get a graphics object for the desktop and start drawing to it. The problem is if you need to start clearing what you have previously drawn etc.
Point pt = Cursor.Position; // Get the mouse cursor in screen coordinates
using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
{
g.DrawEllipse(Pens.Black, pt.X - 10, pt.Y - 10, 20, 20);
}
2- The second option that I provide in the link above is to create a transparent top-most window and do all your drawing in that window. This basically provides a transparent overlay for the desktop which you can draw on. One possible downside to this, as I mention in the original answer, is that other windows which are also top-most and are created after your app starts will obscure your top most window. This can be solved if it is a problem though.
For option 2, making the form transparent is as simple as using a transparency key, this allows mouse clicks etc. to fall through to the underlying desktop.
BackColor = Color.LightGreen;
TransparencyKey = Color.LightGreen;
When you draw to HDC(NULL) you draw to the screen, in an unmanaged way. As you've discovered, as soon as windows refreshes that part of the screen, your changes are overwritten.
There are a couple of options, depending upon what you want to achieve:
create a borderless, possibly
non-rectangular window. (Use
SetWindowRgn to make a window
non-rectangular.) You can make this a child of the desktop window.
subclass the desktop window. This is not straightforward, and involves
injecting a DLL into the
Explorer.exe process.
To get an OnPaint for the desktop you would need to subclass the desktop window and use your drawing logic when it receives a WM_PAINT/WM_ERASEBKGND message.
As the thread you linked to says, you can only intercept messages sent to a window of an external process using a hook (SetWindowsHookEx from a DLL).
As mentioned a transparent window is another way to do it, or (depending on the update frequency) copying, drawing and setting a temporary wallpaper (as bginfo does).
This is difficult to do correctly.
It will be far easier, and more reliable, to make your own borderless form instead.