I am developing an application that remembers the user's preferences as to where the form was last located on the screen. In some instances the user will have it on a secondary screen, and then fire the app up later without the second screen (sometimes having the form appear off screen). Other times the user will change their resolution resulting in a similar effect.
I was hoping to do this checking in the Form_Shown event handler. Basically I want to determine whether the form is completely off screen so I can re-position it.
Any advice?
Check with this function if Form is fully on screen:
public bool IsOnScreen( Form form )
{
Screen[] screens = Screen.AllScreens;
foreach( Screen screen in screens )
{
Rectangle formRectangle = new Rectangle( form.Left, form.Top,
form.Width, form.Height );
if( screen.WorkingArea.Contains( formRectangle ) )
{
return true;
}
}
return false;
}
Checking only top left point if it's on screen:
public bool IsOnScreen( Form form )
{
Screen[] screens = Screen.AllScreens;
foreach( Screen screen in screens )
{
Point formTopLeft = new Point( form.Left, form.Top );
if( screen.WorkingArea.Contains( formTopLeft ) )
{
return true;
}
}
return false;
}
Combining all the solutions above with the "IntersectsWith"-method and LINQ-extensions give us in short:
public bool IsOnScreen(Form form)
{
// Create rectangle
Rectangle formRectangle = new Rectangle(form.Left, form.Top, form.Width, form.Height);
// Test
return Screen.AllScreens.Any(s => s.WorkingArea.IntersectsWith(formRectangle));
}
Complete solution here (based on all answers). I have added a parameter MinPercentOnScreen where at least this % of pixels must be visible across all screens/displays. So if this returns false you will need to move the window's position back to default.
// Return True if a certain percent of a rectangle is shown across the total screen area of all monitors, otherwise return False.
public bool IsOnScreen(System.Drawing.Point RecLocation, System.Drawing.Size RecSize, double MinPercentOnScreen = 0.2)
{
double PixelsVisible = 0;
System.Drawing.Rectangle Rec = new System.Drawing.Rectangle(RecLocation, RecSize);
foreach (Screen Scrn in Screen.AllScreens)
{
System.Drawing.Rectangle r = System.Drawing.Rectangle.Intersect(Rec, Scrn.WorkingArea);
// intersect rectangle with screen
if (r.Width != 0 & r.Height != 0)
{
PixelsVisible += (r.Width * r.Height);
// tally visible pixels
}
}
return PixelsVisible >= (Rec.Width * Rec.Height) * MinPercentOnScreen;
}
Implementation:
return IsOnScreen(this.Location, this.Size);
Old thread, but still helpful!
Cody and Andrija- thanks for the code. I had to make a couple of minor adjustments:
Instead of screen.WorkingArea.Intersect(formRectangle); I used formRectangle.Intersect(screen.WorkingArea); since Intersect() replaces its object with the intersection. If the form is completely off the screen, formRectangle after the intersection is (0,0,0,0), and Contains() returns true. So I also check to see if formRectangle Top, Left, Width and Height are not all 0 before returning true. Now the code returns true if any part of the form is on screen, and false if no part is on screen.
For WPF (based on Matthias Loerkes answer)
Add a reference to System.Windows.Forms and System.Drawing.
//using System.Windows.Forms;
public bool IsOnScreen(Window window)
{
var windowRect = new System.Drawing.Rectangle((int)window.Left, (int)window.Top, (int)window.Width, (int)window.Height);
return Screen.AllScreens.Any(s => s.WorkingArea.IntersectsWith(windowRect));
}
None of these work if a monitor happens to be off. The Screen.AllScreens function will always return the number of screens even if one is off.
Check the screens resolution before you position the window. That will allow you to figure out if you where going to place it outside the bounds of the resolution, before you actually do it.
Related
Is there a way (either C# or XAML) I can maximize a UWP app window even after I resized and closed it previously on desktop?
I have tried with ApplicationViewWindowingMode.FullScreen but this makes the app go entire full screen and covers the Windows Taskbar too.
You can use another value PreferredLaunchViewSize from ApplicationViewWindowingMode and then set ApplicationView.PreferredLaunchViewSize but the key is to find out what the size is going to be.
Theoretically, you could use a really big number and window would just extend to the max it could be. However, it's probably safer to just calculate the screen dimensions in effective pixels.
So if you just call the following method before InitializeComponent(); on your main Page, it should maximize the window on startup.
private static void MaximizeWindowOnLoad()
{
// Get how big the window can be in epx.
var bounds = ApplicationView.GetForCurrentView().VisibleBounds;
ApplicationView.PreferredLaunchViewSize = new Size(bounds.Width, bounds.Height);
ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.PreferredLaunchViewSize;
}
Note the app somehow remembers these settings even after you uninstalled it. If you ever want to change back to the default behavior (app starts up with the previous window size), simply call ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.Auto; once and remove all the code.
Update
Looks like in the latest Windows 10 build, ApplicationView.GetForCurrentView().VisibleBounds no longer returns the full window size in effective pixels anymore. So we now need a new way to calculate it.
Turns out it's quite straightforward since the DisplayInformation class also gives us the screen resolution as well as the scale factor.
The following is the updated code -
public MainPage()
{
MaximizeWindowOnLoad();
InitializeComponent();
void MaximizeWindowOnLoad()
{
var view = DisplayInformation.GetForCurrentView();
// Get the screen resolution (APIs available from 14393 onward).
var resolution = new Size(view.ScreenWidthInRawPixels, view.ScreenHeightInRawPixels);
// Calculate the screen size in effective pixels.
// Note the height of the Windows Taskbar is ignored here since the app will only be given the maxium available size.
var scale = view.ResolutionScale == ResolutionScale.Invalid ? 1 : view.RawPixelsPerViewPixel;
var bounds = new Size(resolution.Width / scale, resolution.Height / scale);
ApplicationView.PreferredLaunchViewSize = new Size(bounds.Width, bounds.Height);
ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.PreferredLaunchViewSize;
}
}
If you want to MAXIMISE your app on launch you can use the following:
ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.Maximized;
But be sure to put it into the Loaded Event for your Page or it will not work!
I've too few points to comment directly. None of the above resized to a maximized view for me (or the below single-line ApplicationViewWindowingMode.Maximized method), but I have used some of the answers to come up with something that worked for me. It is still very clunky however. The screen size given in 'DisplayInformation' is too big to allow the page to be resized directly to it. Trying to do it didn't work and I had to take 60 off height and width to get it to return 'true', therefore I have the following bit of nonsense which worked, maybe it will help someone else find a better answer. It goes in the page/window loaded event. Nothing else needs to be added elsewhere.
private void Page_Loaded(object sender, RoutedEventArgs e)
{
var view = ApplicationView.GetForCurrentView();
var displayInfo = DisplayInformation.GetForCurrentView();
double x = ActualWidth;
double y = ActualHeight;
bool answer = true;
// Get the screen resolution (APIs available from 14393 onward).
var resolution = new Size(displayInfo.ScreenWidthInRawPixels-60, displayInfo.ScreenHeightInRawPixels-60);
answer = view.TryResizeView(resolution); //This should return true if the resize is successful
if (answer)
{
x = displayInfo.ScreenWidthInRawPixels - 60;
y = displayInfo.ScreenHeightInRawPixels - 60;
}
answer = true;
while (answer == true)
{
x++;
answer = view.TryResizeView(new Size { Width = x, Height = y });
}
x = x - 1;
answer = true;
while (answer == true)
{
y++;
answer = view.TryResizeView(new Size { Width = x, Height = y });
}
Adding the following line to the OnLaunched event under App.xaml.cs did it for me.
ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.FullScreen;
NOTE: Make sure to add it before the following line
Window.Current.Activate();
If you like to go fullscreen at the runtime use the following line.
ApplicationView.GetForCurrentView().TryEnterFullScreenMode();
I have this one liner that works as I expected Justins code to, but for some reason, when using Justins answer, my window would not be maximized... But then I changed something that did make it maximized but I lost all my fluent design such as Acrylic and RevealHighlite...
So I came up with this one liner which keeps all of my fluent design principles happy:
ApplicationView.GetForCurrentView().TryEnterFullScreenMode();
Something to note:
I did try Justins answer, and I am using his method of MaximizeWindowOnLoad() which I have called straight after the initializeComponent();
Full overview:
public class()
{
this.InitializeComponent();
MaximizeWindowOnLoad();
}
private static void MaximizeWindowOnLoad()
{
ApplicationView.GetForCurrentView().TryEnterFullScreenMode();
}
I have found that this works PART of the time by inheriting the Windows Forms mouse point and subtracting out the height and width of my window to set the left and top (since my window's size is fixed):
MyWindowObjectThatInheritsWindow window = new MyWindowObjectThatInheritsWindow();
System.Windows.Point mouseLocation = GetMousePositionWindowsForms();
window.Left = mouseLocation.X - 300;
window.Top = mouseLocation.Y - 240;
window.Show();
Edit: Here is the code for getting the mouse position...
public System.Windows.Point GetMousePositionWindowsForms()
{
System.Drawing.Point point = System.Windows.Forms.Control.MousePosition;
return new System.Windows.Point(point.X, point.Y);
}
Note that this works by making the bottom right edge of the window touch the top left of your mouse cursor. But this breaks for different screen resolutions, or maybe multiple monitors with different resolutiosn? I haven't fully narrowed it down yet, but I just tried this same code on another PC, and it seems to spawn the window not to the top left of the mouse cursor, but to the bottom left of it, and a good distance past it...
I should probably add that my window sizes to content, width and height, so I can't just use the ActualWidth and ActualHeight properties since they're not available. Perhaps the issue is in getting that sizing right? Is there any way to do that? I know for sure the 300 and 240 is correct according to my main PC with two monitors running 1920x1080 resolutions, as I have calculated the widths and heights of all the objects in my window which I have explicitly sized. Edit: Just tried explicitly setting the height and width to 240/300, to ensure that the window is no longer sized to content, and I still have this issue when subtracting out the actual height and width!
Any ideas?
In the end, this did the trick:
protected override void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
MoveBottomRightEdgeOfWindowToMousePosition();
}
private void MoveBottomRightEdgeOfWindowToMousePosition()
{
var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;
var mouse = transform.Transform(GetMousePosition());
Left = mouse.X - ActualWidth;
Top = mouse.Y - ActualHeight;
}
public System.Windows.Point GetMousePosition()
{
System.Drawing.Point point = System.Windows.Forms.Control.MousePosition;
return new System.Windows.Point(point.X, point.Y);
}
Can you not use something like this?:
Point mousePositionInApp = Mouse.GetPosition(Application.Current.MainWindow);
Point mousePositionInScreenCoordinates =
Application.Current.MainWindow.PointToScreen(mousePositionInApp);
I haven't been able to test it, but I think it should work.
UPDATE >>>
You don't have to use the Application.Current.MainWindow as the parameter in these methods... it should still work if you have access to a Button or another UIElement in a handler:
Point mousePositionInApp = Mouse.GetPosition(openButton);
Point mousePositionInScreenCoordinates = openButton.PointToScreen(mousePositionInApp);
Again, I haven't been able to test this, but if that fails as well, then you can find one more method in the How do I get the current mouse screen coordinates in WPF? post.
You can also do this by slightly modifying your initial example and positioning the window before showing it.
MyWindowObjectThatInheritsWindow window = new MyWindowObjectThatInheritsWindow();
var helper = new WindowInteropHelper(window);
var hwndSource = HwndSource.FromHwnd(helper.EnsureHandle());
var transformFromDevice = hwndSource.CompositionTarget.TransformFromDevice;
System.Windows.Point wpfMouseLocation = transformFromDevice.Transform(GetMousePositionWindowsForms());
window.Left = wpfMouseLocation.X - 300;
window.Top = wpfMouseLocation.Y - 240;
window.Show();
Hello it might be a silly question but i can't figure out the problem here.. here is my code to fill a form with a single block:
private void drawBackground()
{
Graphics g = genPan.CreateGraphics();
Image Block = Image.FromFile(#"C:\Users\Administrator\Desktop\movment V1\movment V1\images\BrownBlock.png");
float recWidth = Block.Width;
// rectangle width didnt change the name from previous code, it's picture width.
float recHeight = Block.Height;
// rectangle Heightdidnt change the name from previous code, it's picture Height.
float WinWidth = genPan.Width; // genPan is a panel that docked to the form
float WinHeight = genPan.Height;
float curWidth = 0; //indicates where the next block will be placed int the X axis
float curHeight = 0;//indicates where the next block will be placed int the Y axis
while ((curHeight + recHeight) <= WinHeight)
{
if (curWidth >= WinWidth / 3 || curWidth <= WinWidth / 1.5 ||
curHeight >= WinHeight / 3 || curHeight <= WinHeight / 1.5)
{
g.DrawImage(Block, curWidth, curHeight, recWidth , recHeight );
}
curWidth += recWidth;
if ((WinWidth - curWidth) < recWidth)
{
curWidth = 0;
curHeight += 50;
}
}
}
If I launch this func through a button it will work perfectly fine. But if I launch the func after the InitializeComponent(); method in the constructor OR in a FORM shown event, while the button is still on the form it will execute the func however the block backgroud wont be visible but the grey color will be. but if i remove the button the background will be visible. =\
I cant understand why is it happening, how to fix it and what am I doing wrong.. can anyone explain please..?
You can't really do that using your current logic. The problem is that the control (genPan panel in your case) has its own Paint event that when called, overwriting any graphics you used on it.
Even when you draw in button click it works only until the form is repainted e.g. try focus other window and focus your form again: you will lose what you have drawn.
Proper way to do such things is to write your own class that inherit from some basic control (Panel in your case) then overriding its OnPaint event and draw whatever you want there.
So first, have such class:
public class BlockBackgroundPanel : Panel
{
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Image Block = Image.FromFile(#"C:\Users\Administrator\Desktop\movment V1\movment V1\images\BrownBlock.png");
float recWidth = Block.Width;
//rest of your code, replace "genPan" with "this" as you are inside the Panel
}
}
Then in your .Designer.cs file (you can open it in the Studio) change the code so that genPan will be of your new class instance:
private BlockBackgroundPanel genPan;
//...
this.genPan = new BlockBackgroundPanel ();
If you need to draw background only based on some condition/action/user interaction...
Put the call to this funciton into the forms OnPaint method and enable it only if some bollean variale equals true. And that boolean becomes true, only on button click.
Some hypothetical example:
protected override OnPaint(...) //FORMS ONPAINT OVERRIDE
{
if(needBackGround) //INITIAL VALUE AT STARTUP IS FALSE
drawBackground();
}
public void ButtonClickHandler(...)
{
needBackGround= !needBackGround; //INVERSE THE VALUE OF BOOLEAN
}
This is clearly just a sniplet to give you a hint and not a real code. There could be other problems you will need to face, like: flickering, handling resize, performance... but this is just a point to start.
I'm storing the last position for each window.
Next time the user opens the window, the last position is restored.
If the user changes his screen (from dual screen to one screen) or just to a smaller resolution, the form is nowhere to be seen...
How can I detect this? I don't like to store user settings, depending on his environment.
Thanks in advance
Let's start from scratch, you want two settings to store the state of the window. Let's call them Location (type Point, default = 0,0) and Size (type Size, default = 0, 0). You want to save them when the window is resized, avoiding storing state if the window is minimized:
protected override void OnResizeEnd(EventArgs e) {
if (this.WindowState != FormWindowState.Minimized) {
Properties.Settings.Default.Location = this.Location;
Properties.Settings.Default.Size = this.Size;
Properties.Settings.Default.Save();
}
base.OnResizeEnd(e);
}
Restore the state in the form's OnLoad method. You'll want to use Screen.FromPoint() to find the screen bounds back. Add extra code to ensure the window doesn't get too large and locates properly when the screen has disappeared:
protected override void OnLoad(EventArgs e) {
if (Properties.Settings.Default.Size != Size.Empty) {
Screen scr = Screen.FromPoint(Properties.Settings.Default.Location);
int width = Math.Min(Properties.Settings.Default.Size.Width, scr.WorkingArea.Width);
int height = Math.Min(Properties.Settings.Default.Size.Height, scr.WorkingArea.Height);
this.Size = new Size(width, height);
if (scr.WorkingArea.Contains(Properties.Settings.Default.Location))
this.Location = Properties.Settings.Default.Location;
else this.Location = new Point(scr.Bounds.Left + (scr.Bounds.Width - width) / 2,
scr.Bounds.Top + (scr.Bounds.Height - height) / 2);
}
base.OnLoad(e);
}
Use the Bounds property from System.Windows.Forms.Screen.PrimaryScreen to see what the bounds of the screen is, compare that to the position/size of your form and compensate where needed.
To get at the bounds of other screens, use the Screen.AllScreens property on the PrimaryScreen property to gain access to other Screen objects representing multiple screens.
For example, this may be as simple as checking that the Location you want to change to is on an available screen:
foreach (var screen in Screen.AllScreens)
{
if (screen.Bounds.Contains(this.Location))
{
return; // on a screen, so don't update location
}
}
// not found on a screen, so assume screen was removed and move to the primary screen
this.Location = Screen.PrimaryScreen.Bounds.Location;
You can, of course, make this more complicated by deciding which screen contains more of the form than any other (based on Bounds) and make a determination that way; but, without more details about exactly what you want, I can't suggest specifics.
Is there a way to check that a WinForm is fully visible on the screen (eg is not out of bounds of the screen?)
I've tried using SystemInformation.VirtualScreen for this, which works great as long as the virtual screen is a rectangle, but as soon as it's not (eg 3 screens in a L shape), SystemInformation.VirtualScreen returns the smallest rectangle containing all the visible pixels (so a window on the upper right corner of the L won't be visible although it's in the virtual screen)
The reason I'm trying to achieve this is that I'd like my program to open its child windows in the last location they were on, but I don't want those window to be out of view if the user changes is setup (eg unplugs the extra screen from his laptop)
Here's how I eventually did it :
bool isPointVisibleOnAScreen(Point p)
{
foreach (Screen s in Screen.AllScreens)
{
if (p.X < s.Bounds.Right && p.X > s.Bounds.Left && p.Y > s.Bounds.Top && p.Y < s.Bounds.Bottom)
return true;
}
return false;
}
bool isFormFullyVisible(Form f)
{
return isPointVisibleOnAScreen(new Point(f.Left, f.Top)) && isPointVisibleOnAScreen(new Point(f.Right, f.Top)) && isPointVisibleOnAScreen(new Point(f.Left, f.Bottom)) && isPointVisibleOnAScreen(new Point(f.Right, f.Bottom));
}
There might be some false positive if the user has a "hole" in his display setup (see example below) but I don't think any of my users will ever be in such a situation :)
[1]
[2][X][3]
Here's how I would do it:
This will move the control (form) inside the Display bounds as close as it can to the original location.
private void EnsureVisible(Control ctrl)
{
Rectangle ctrlRect = ctrl.DisplayRectangle; //The dimensions of the ctrl
ctrlRect.Y = ctrl.Top; //Add in the real Top and Left Vals
ctrlRect.X = ctrl.Left;
Rectangle screenRect = Screen.GetWorkingArea(ctrl); //The Working Area fo the screen showing most of the Ctrl
//Now tweak the ctrl's Top and Left until it's fully visible.
ctrl.Left += Math.Min(0, screenRect.Left + screenRect.Width - ctrl.Left - ctrl.Width);
ctrl.Left -= Math.Min(0, ctrl.Left - screenRect.Left);
ctrl.Top += Math.Min(0, screenRect.Top + screenRect.Height - ctrl.Top - ctrl.Height);
ctrl.Top -= Math.Min(0, ctrl.Top - screenRect.Top);
}
Of course to answer your original question instead of moving the control you could just check if any of the 4 Math.Min's returned something other than 0.
This is my solution. It solves the "hole" problem.
/// <summary>
/// True if a window is completely visible
/// </summary>
static bool WindowAllVisible(Rectangle windowRectangle)
{
int areaOfWindow = windowRectangle.Width * windowRectangle.Height;
int areaVisible = 0;
foreach (Screen screen in Screen.AllScreens)
{
Rectangle windowPortionOnScreen = screen.WorkingArea;
windowPortionOnScreen.Intersect(windowRectangle);
areaVisible += windowPortionOnScreen.Width * windowPortionOnScreen.Height;
if (areaVisible >= areaOfWindow)
{
return true;
}
}
return false;
}
I was trying to do this exact same thing detect if the window opened off screen then accordingly reposition it to the nearest location where it was previously found at. I look all over the internet and nothing worked from all the solutions people offered.
So i took it upon myself to make my own class that does just exactly this and it works 100%.
Here is my code
public static class ScreenOperations
{
public static bool IsWindowOnAnyScreen(Window Window, short WindowSizeX, short WindowSizeY, bool AutoAdjustWindow)
{
var Screen = System.Windows.Forms.Screen.FromHandle(new WindowInteropHelper(Window).Handle);
bool LeftSideTest = false, TopSideTest = false, BottomSideTest = false, RightSideTest = false;
if (Window.Left >= Screen.WorkingArea.Left)
LeftSideTest = true;
if (Window.Top >= Screen.WorkingArea.Top)
TopSideTest = true;
if ((Window.Top + WindowSizeY) <= Screen.WorkingArea.Bottom)
BottomSideTest = true;
if ((Window.Left + WindowSizeX) <= Screen.WorkingArea.Right)
RightSideTest = true;
if (LeftSideTest && TopSideTest && BottomSideTest && RightSideTest)
return true;
else
{
if (AutoAdjustWindow)
{
if (!LeftSideTest)
Window.Left = Window.Left - (Window.Left - Screen.WorkingArea.Left);
if (!TopSideTest)
Window.Top = Window.Top - (Window.Top - Screen.WorkingArea.Top);
if (!BottomSideTest)
Window.Top = Window.Top - ((Window.Top + WindowSizeY) - Screen.WorkingArea.Bottom);
if (!RightSideTest)
Window.Left = Window.Left - ((Window.Left + WindowSizeX) - Screen.WorkingArea.Right);
}
}
return false;
}
}
Usage: ScreenOperations.IsWindowOnAnyScreen(WPFWindow, WPFWindowSizeX, WPFWindowSizeY, true);
this will check if the window is offscreen at all, that being 1 pixel under the taskbar or 1 pixel off the users current monitor.
It detects which monitor the window if on first so it should work with multi-monitors.
this method returns true if the window is on the screen and false if its not.
The last parameter is for auto adjusting the window to the nearest part on the screen for you. if you put false for that parameter it will not adjust the window for you.
So this is a complete WPF solution to this issue, and WinForm converting should be easy if you know how to do it, Change Window to Form and FromHandle(Form.Handle) should work.
Check whether Screen.AllScreens.Any(s => s.WorkingArea.Contains(rect))