关于恢复WinForm位置和大小的许多帖子。
示例:
但是我还没有找到使用多个监视器执行此操作的代码。
也就是说,如果我使用监视器2上的窗口关闭我的.NET Winform应用程序,我希望它将窗口大小,位置和状态保存到应用程序设置,以便以后重新启动时可以恢复到监视器2该应用程序。如果在上面的代码项目示例中它包含一些健全性检查,那将是很好的,因为如果保存的位置大部分是在屏幕外,它“修复”它。或者如果保存的位置在不再存在的显示器上(例如我的笔记本电脑现在没有我的第二台显示器),那么它会正确地将其移动到显示器1。
有什么想法吗?
我的环境:C#,. NET 3.5或更低版本,VS2008
答案 0 :(得分:36)
试试这段代码。兴趣点:
边界和状态存储在appsettings中,并带有相应的类型,因此不需要进行任何字符串解析。让框架做它的序列化魔术。
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
// this is the default
this.WindowState = FormWindowState.Normal;
this.StartPosition = FormStartPosition.WindowsDefaultBounds;
// check if the saved bounds are nonzero and visible on any screen
if (Settings.Default.WindowPosition != Rectangle.Empty &&
IsVisibleOnAnyScreen(Settings.Default.WindowPosition))
{
// first set the bounds
this.StartPosition = FormStartPosition.Manual;
this.DesktopBounds = Settings.Default.WindowPosition;
// afterwards set the window state to the saved value (which could be Maximized)
this.WindowState = Settings.Default.WindowState;
}
else
{
// this resets the upper left corner of the window to windows standards
this.StartPosition = FormStartPosition.WindowsDefaultLocation;
// we can still apply the saved size
this.Size = Settings.Default.WindowPosition.Size;
}
}
private bool IsVisibleOnAnyScreen(Rectangle rect)
{
foreach (Screen screen in Screen.AllScreens)
{
if (screen.WorkingArea.IntersectsWith(rect))
{
return true;
}
}
return false;
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// only save the WindowState if Normal or Maximized
switch (this.WindowState)
{
case FormWindowState.Normal:
case FormWindowState.Maximized:
Settings.Default.WindowState = this.WindowState;
break;
default:
Settings.Default.WindowState = FormWindowState.Normal;
break;
}
// reset window state to normal to get the correct bounds
// also make the form invisible to prevent distracting the user
this.Visible = false;
this.WindowState = FormWindowState.Normal;
Settings.Default.WindowPosition = this.DesktopBounds;
Settings.Default.Save();
}
}
供参考的设置文件:
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="ScreenTest" GeneratedClassName="Settings">
<Profiles />
<Settings>
<Setting Name="WindowPosition" Type="System.Drawing.Rectangle" Scope="User">
<Value Profile="(Default)">0, 0, 0, 0</Value>
</Setting>
<Setting Name="WindowState" Type="System.Windows.Forms.FormWindowState" Scope="User">
<Value Profile="(Default)">Normal</Value>
</Setting>
</Settings>
</SettingsFile>
答案 1 :(得分:27)
VVS提供的答案非常有帮助!我发现它有两个小问题,所以我将大部分代码重新发布到这些修订中:
(1)应用程序第一次运行时,窗体以Normal状态打开,但其大小应使其显示为标题栏。我在构造函数中添加了一个条件来修复它。
(2)如果应用程序在最小化或最大化时关闭,则OnClosing中的代码无法记住处于“正常”状态的窗口的尺寸。 (这三行代码 - 我现在已经注释掉了 - 似乎是合理的,但由于某种原因只是不起作用。)幸运的是,我之前已经解决了这个问题并将代码包含在代码末尾的新区域中跟踪窗口状态,而不是等待关闭。
有了这两个修复程序,我已经测试过了:
一个。在正常状态下关闭 - 恢复到相同的大小/位置和状态
B中。在最小化状态下关闭 - 使用上一个正常大小/位置恢复到正常状态
℃。在最大化状态下关闭 - 恢复到最大化状态并在后者调整到正常状态时记住它的最后大小/位置。
d。关闭监视器2 - 恢复监视2。
电子。关闭监视器2然后断开监视器2 - 恢复到监视器1上的相同位置
大卫:你的代码让我几乎毫不费力地获得了积分D和E - 你不仅为我的问题提供了一个解决方案,而且还提供了一个完整的程序,所以我几乎在几分钟的时间内完成了它进入Visual Studio。非常感谢你!public partial class MainForm : Form
{
bool windowInitialized;
public MainForm()
{
InitializeComponent();
// this is the default
this.WindowState = FormWindowState.Normal;
this.StartPosition = FormStartPosition.WindowsDefaultBounds;
// check if the saved bounds are nonzero and visible on any screen
if (Settings.Default.WindowPosition != Rectangle.Empty &&
IsVisibleOnAnyScreen(Settings.Default.WindowPosition))
{
// first set the bounds
this.StartPosition = FormStartPosition.Manual;
this.DesktopBounds = Settings.Default.WindowPosition;
// afterwards set the window state to the saved value (which could be Maximized)
this.WindowState = Settings.Default.WindowState;
}
else
{
// this resets the upper left corner of the window to windows standards
this.StartPosition = FormStartPosition.WindowsDefaultLocation;
// we can still apply the saved size
// msorens: added gatekeeper, otherwise first time appears as just a title bar!
if (Settings.Default.WindowPosition != Rectangle.Empty)
{
this.Size = Settings.Default.WindowPosition.Size;
}
}
windowInitialized = true;
}
private bool IsVisibleOnAnyScreen(Rectangle rect)
{
foreach (Screen screen in Screen.AllScreens)
{
if (screen.WorkingArea.IntersectsWith(rect))
{
return true;
}
}
return false;
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// only save the WindowState if Normal or Maximized
switch (this.WindowState)
{
case FormWindowState.Normal:
case FormWindowState.Maximized:
Settings.Default.WindowState = this.WindowState;
break;
default:
Settings.Default.WindowState = FormWindowState.Normal;
break;
}
# region msorens: this code does *not* handle minimized/maximized window.
// reset window state to normal to get the correct bounds
// also make the form invisible to prevent distracting the user
//this.Visible = false;
//this.WindowState = FormWindowState.Normal;
//Settings.Default.WindowPosition = this.DesktopBounds;
# endregion
Settings.Default.Save();
}
# region window size/position
// msorens: Added region to handle closing when window is minimized or maximized.
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
TrackWindowState();
}
protected override void OnMove(EventArgs e)
{
base.OnMove(e);
TrackWindowState();
}
// On a move or resize in Normal state, record the new values as they occur.
// This solves the problem of closing the app when minimized or maximized.
private void TrackWindowState()
{
// Don't record the window setup, otherwise we lose the persistent values!
if (!windowInitialized) { return; }
if (WindowState == FormWindowState.Normal)
{
Settings.Default.WindowPosition = this.DesktopBounds;
}
}
# endregion window size/position
}
答案 2 :(得分:8)
这里的大多数其他解决方案都依赖于手动计算每个监视器的当前位置。边缘情况非常难以弄清楚,并且很少有应用程序能够正确地完成它们。
Windows中的SetWindowPlacement函数正确处理所有边缘情况 - 如果窗口位于可见屏幕之外,则会相应地调整它。
我在C#中看到的最好的例子是David Rickard的博客。它不仅展示了如何使用SetWindowPlacement,还展示了如何序列化整个结果。 http://blogs.msdn.com/b/davidrickard/archive/2010/03/09/saving-window-size-and-location-in-wpf-and-winforms.aspx
答案 3 :(得分:3)
此解决方案是保存/恢复表单大小和位置,使用多显示器 + 多文档,多种形式或多主要表单支持。它是不是MDI 形式,而是Microsoft Word,就像具有不同主表单实例的多文档一样。
感谢VVS,msorens和Ian Goldby。我合并了来自VVS,msorens和MSDN Application.Run Method (ApplicationContext)示例的解决方案,以制作多主要方式而不是MDI。
此修复程序包含来自Ian Goldby的评论,该评论使用Form.RestoreBounds
来消除OnResize()
,OnMove()
和TrackWindowState()
。
我还修复了当Form移动到另一个Monitor并在退出之前获得最大化时记住Monitor,因为我没有跟踪OnResize,OnMove。 通过此修复,此解决方案支持Windows 7 Snap feature,您可以将标题栏或Win +箭头键拖动到任何监视器边缘,或使其最大化/正常以及最小化。
此解决方案在程序中实现,但不在主窗体中实现,以支持多主窗体。但是,您也可以使用单个主表格。
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using SimpleTestForm.Properties;
using System.Drawing;
namespace SimpleTestForm
{
static class Program
{
static MultiMainFormAppContext appContext;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
appContext = new MultiMainFormAppContext();
Application.Run(appContext);
}
/// <summary>
/// Create a new MainForm and restore the form size and position if necessary. This method can be called like from Menu File > New click event.
/// </summary>
/// <returns></returns>
public static MainForm createNewMainForm()
{
return appContext.createNewMainForm();
}
/// <summary>
/// Get the current active MainForm event if a dialog is opened. Useful to create Dictionary (MainForm, T) to store Form/document dependent field. Please set the Owner of child form to prevent null reference exception.
/// </summary>
/// <returns></returns>
public static MainForm GetCurrentMainFormInstance()
{
Form mainForm = Form.ActiveForm;
while (!(mainForm is MainForm) && mainForm.Owner != null)
mainForm = mainForm.Owner;
return mainForm as MainForm;
}
}
class MultiMainFormAppContext : ApplicationContext
{
List<MainForm> mainForms = new List<MainForm>();
Point newRestoredLocation = Point.Empty;
internal MultiMainFormAppContext()
{
createNewMainForm();
}
internal MainForm createNewMainForm()
{
MainForm mainForm = new MainForm();
mainForm.FormClosed += new FormClosedEventHandler(mainForm_FormClosed);
mainForm.LocationChanged += new EventHandler(mainForm_LocationChanged);
RestoreFormSizeNPosition(mainForm);
PreventSameLocation(mainForm);
mainForms.Add(mainForm);
mainForm.Show();
return mainForm;
}
private void PreventSameLocation(MainForm mainForm)
{
const int distance = 20;
foreach (MainForm otherMainForm in mainForms)
{
if (Math.Abs(otherMainForm.Location.X - mainForm.Location.X) < distance &&
Math.Abs(otherMainForm.Location.Y - mainForm.Location.Y) < distance)
mainForm.Location = new Point(mainForm.Location.X + distance, mainForm.Location.Y + distance);
}
}
/// <summary>
/// Restore the form size and position with multi monitor support.
/// </summary>
private void RestoreFormSizeNPosition(MainForm mainForm)
{
// this is the default
mainForm.WindowState = FormWindowState.Normal;
mainForm.StartPosition = FormStartPosition.WindowsDefaultBounds;
// check if the saved bounds are nonzero and visible on any screen
if (Settings.Default.WindowPosition != Rectangle.Empty &&
IsVisibleOnAnyScreen(Settings.Default.WindowPosition))
{
// first set the bounds
mainForm.StartPosition = FormStartPosition.Manual;
mainForm.DesktopBounds = Settings.Default.WindowPosition;
// afterwards set the window state to the saved value (which could be Maximized)
mainForm.WindowState = Settings.Default.WindowState;
}
else
{
// this resets the upper left corner of the window to windows standards
mainForm.StartPosition = FormStartPosition.WindowsDefaultLocation;
// we can still apply the saved size if not empty
if (Settings.Default.WindowPosition != Rectangle.Empty)
{
mainForm.Size = Settings.Default.WindowPosition.Size;
}
}
}
private void SaveFormSizeNPosition(MainForm mainForm)
{
// only save the WindowState as Normal or Maximized
Settings.Default.WindowState = FormWindowState.Normal;
if (mainForm.WindowState == FormWindowState.Normal || mainForm.WindowState == FormWindowState.Maximized)
Settings.Default.WindowState = mainForm.WindowState;
if (mainForm.WindowState == FormWindowState.Normal)
{
Settings.Default.WindowPosition = mainForm.DesktopBounds;
}
else
{
if (newRestoredLocation == Point.Empty)
Settings.Default.WindowPosition = mainForm.RestoreBounds;
else
Settings.Default.WindowPosition = new Rectangle(newRestoredLocation, mainForm.RestoreBounds.Size);
}
Settings.Default.Save();
}
private bool IsVisibleOnAnyScreen(Rectangle rect)
{
foreach (Screen screen in Screen.AllScreens)
{
if (screen.WorkingArea.IntersectsWith(rect))
return true;
}
return false;
}
void mainForm_LocationChanged(object sender, EventArgs e)
{
MainForm mainForm = sender as MainForm;
if (mainForm.WindowState == FormWindowState.Maximized)
{
// get the center location of the form incase like RibbonForm will be bigger and maximized Location wll be negative value that Screen.FromPoint(mainForm.Location) will going to the other monitor resides on the left or top of primary monitor.
// Another thing, you might consider the form is in the monitor even if the location (top left corner) is on another monitor because majority area is on the monitor, so center point is the best way.
Point centerFormMaximized = new Point (mainForm.DesktopBounds.Left + mainForm.DesktopBounds.Width/2, mainForm.DesktopBounds.Top + mainForm.DesktopBounds.Height/2);
Point centerFormRestored = new Point(mainForm.RestoreBounds.Left + mainForm.RestoreBounds.Width / 2, mainForm.RestoreBounds.Top + mainForm.RestoreBounds.Height / 2);
Screen screenMaximized = Screen.FromPoint(centerFormMaximized);
Screen screenRestored = Screen.FromPoint(centerFormRestored);
// we need to change the Location of mainForm.RestoreBounds to the new screen where the form currently maximized.
// RestoreBounds does not update the Location if you change the screen but never restore to FormWindowState.Normal
if (screenMaximized.DeviceName != screenRestored.DeviceName)
{
newRestoredLocation = mainForm.RestoreBounds.Location;
int screenOffsetX = screenMaximized.Bounds.Location.X - screenRestored.Bounds.Location.X;
int screenOffsetY = screenMaximized.Bounds.Location.Y - screenRestored.Bounds.Location.Y;
newRestoredLocation.Offset(screenOffsetX, screenOffsetY);
return;
}
}
newRestoredLocation = Point.Empty;
}
void mainForm_FormClosed(object sender, FormClosedEventArgs e)
{
MainForm mainForm = sender as MainForm;
SaveFormSizeNPosition(mainForm);
mainForm.FormClosed -= new FormClosedEventHandler(mainForm_FormClosed);
mainForm.LocationChanged -= new EventHandler(mainForm_LocationChanged);
mainForm.Dispose();
mainForms.Remove(mainForm);
if (mainForms.Count == 0) ExitThread();
}
}
}
编辑:添加了PreventSameLocation方法,以确保第二个表单不会完全打开第一个表单,用户会注意到新打开的表单。
答案 4 :(得分:1)
如果您有多台显示器,我相信屏幕UI尺寸会更大。因此,存储和恢复位置的正常“1监视器”方法将起作用。我没有尝试过这个,因为我离开了我的第二台显示器,但它不应该很难测试。您问问题的方式似乎没有经过测试。
您的第二个要求意味着您必须在恢复应用时检查最大尺寸尺寸,然后根据需要重新定位。为了做到这一点,我使用这段代码:
private System.Drawing.Rectangle ConstrainToScreen(System.Drawing.Rectangle bounds)
{
Screen screen = Screen.FromRectangle(bounds);
System.Drawing.Rectangle workingArea = screen.WorkingArea;
int width = Math.Min(bounds.Width, workingArea.Width);
int height = Math.Min(bounds.Height, workingArea.Height);
// mmm....minimax
int left = Math.Min(workingArea.Right - width, Math.Max(bounds.Left, workingArea.Left));
int top = Math.Min(workingArea.Bottom - height, Math.Max(bounds.Top, workingArea.Top));
return new System.Drawing.Rectangle(left, top, width, height);
}
我在恢复表单时调用此方法。我将屏幕几何存储在表单上的注册表中,然后在打开的表单上读取几何。我得到了边界,但是然后使用上面的方法将恢复的边界约束到实际的当前屏幕。
保存关闭:
// store the size of the form
int w = 0, h = 0, left = 0, top = 0;
if (this.Bounds.Width < this.MinimumSize.Width || this.Bounds.Height < this.MinimumSize.Height)
{
// The form is currently minimized.
// RestoreBounds is the size of the window prior to last minimize action.
w = this.RestoreBounds.Width;
h = this.RestoreBounds.Height;
left = this.RestoreBounds.Location.X;
top = this.RestoreBounds.Location.Y;
}
else
{
w = this.Bounds.Width;
h = this.Bounds.Height;
left = this.Location.X;
top = this.Location.Y;
}
AppCuKey.SetValue(_rvn_Geometry,
String.Format("{0},{1},{2},{3},{4}",
left, top, w, h, (int)this.WindowState));
在表单上恢复:
// restore the geometry of the form
string s = (string)AppCuKey.GetValue(_rvn_Geometry);
if (!String.IsNullOrEmpty(s))
{
int[] p = Array.ConvertAll<string, int>(s.Split(','),
new Converter<string, int>((t) => { return Int32.Parse(t); }));
if (p != null && p.Length == 5)
this.Bounds = ConstrainToScreen(new System.Drawing.Rectangle(p[0], p[1], p[2], p[3]));
}
答案 5 :(得分:0)
这是一个老问题,但这是基于之前答案的VB版本。
VVS和Michael Sorens提出的答案的一个问题是,在屏幕上仅显示几个像素的保存位置计为可见。在恢复之前的位置之前,此解决方案在交叉点中需要至少50x50像素。
设定:
<Settings>
<Setting Name="WindowState" Type="System.Windows.Forms.FormWindowState" Scope="User">
<Value Profile="(Default)">Normal</Value>
</Setting>
<Setting Name="WindowBounds" Type="System.Drawing.Rectangle" Scope="User">
<Value Profile="(Default)">10, 10, 800, 600</Value>
</Setting>
</Settings>
形式:
Partial Public Class MainForm
Private loadingComplete As Boolean = False
Public Sub New()
InitializeComponent()
RestoreWindowLocation()
End Sub
Private Sub MainForm_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
loadingComplete = True
End Sub
Private Sub MainForm_Resize(sender As System.Object, e As System.EventArgs) Handles MyBase.Resize
TrackWindowLocation()
End Sub
Private Sub MainForm_Move(sender As System.Object, e As System.EventArgs) Handles MyBase.Move
TrackWindowLocation()
End Sub
Private Sub MainForm_FormClosing(sender As System.Object, e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
SaveWindowLocation()
End Sub
Private Sub RestoreWindowLocation()
If IsRectangleVisible(My.Settings.WindowBounds) Then
Me.StartPosition = FormStartPosition.Manual
Me.DesktopBounds = My.Settings.WindowBounds
End If
If Not My.Settings.WindowState = FormWindowState.Minimized Then
Me.WindowState = My.Settings.WindowState
End If
End Sub
Private Sub TrackWindowLocation()
If loadingComplete Then
If Me.WindowState = FormWindowState.Normal Then
My.Settings.WindowBounds = Me.DesktopBounds
My.Settings.WindowState = Me.WindowState
End If
End If
End Sub
Private Sub SaveWindowLocation()
If Not Me.WindowState = FormWindowState.Minimized Then
My.Settings.WindowState = Me.WindowState
End If
If Me.WindowState = FormWindowState.Normal Then
My.Settings.WindowBounds = Me.DesktopBounds
End If
My.Settings.Save()
End Sub
Private Function IsRectangleVisible(Rectangle As Rectangle) As Boolean
For Each screen As Screen In screen.AllScreens
Dim r As Rectangle = Rectangle.Intersect(Rectangle, screen.WorkingArea)
If Not r.IsEmpty Then
If r.Width > 50 And r.Height > 50 Then Return True
End If
Next
Return False
End Function
End Class