我终于想出了一个Form子类,它一劳永逸地解决了这个问题.要使用它:
继承自RestorableForm而不是Form.
在(ApplicationSettings) - >(PropertyBinding)中添加一个绑定到WindowRestoreState.
当窗口即将关闭时调用Properties.Settings.Default.Save().
现在会在会话之间记住窗口位置和状态.根据下面其他海报的建议,我添加了一个功能ConstrainToScreen,确保窗口在恢复时可以很好地适应可用的显示.
// Consider this code public domain. If you want, you can even tell // your boss, attractive women, or the other guy in your cube that // you wrote it. Enjoy! using System; using System.Windows.Forms; using System.ComponentModel; using System.Drawing; namespace Utilities { public class RestorableForm : Form, INotifyPropertyChanged { // We invoke this event when the binding needs to be updated. public event PropertyChangedEventHandler PropertyChanged; // This stores the last window position and state private WindowRestoreStateInfo windowRestoreState; // Now we define the property that we will bind to our settings. [Browsable(false)] // Don't show it in the Properties list [SettingsBindable(true)] // But do enable binding to settings public WindowRestoreStateInfo WindowRestoreState { get { return windowRestoreState; } set { windowRestoreState = value; if (PropertyChanged != null) { // If anybody's listening, let them know the // binding needs to be updated: PropertyChanged(this, new PropertyChangedEventArgs("WindowRestoreState")); } } } protected override void OnClosing(CancelEventArgs e) { WindowRestoreState = new WindowRestoreStateInfo(); WindowRestoreState.Bounds = WindowState == FormWindowState.Normal ? Bounds : RestoreBounds; WindowRestoreState.WindowState = WindowState; base.OnClosing(e); } protected override void OnLoad(EventArgs e) { base.OnLoad(e); if (WindowRestoreState != null) { Bounds = ConstrainToScreen(WindowRestoreState.Bounds); WindowState = WindowRestoreState.WindowState; } } // This helper class stores both position and state. // That way, we only have to set one binding. public class WindowRestoreStateInfo { Rectangle bounds; public Rectangle Bounds { get { return bounds; } set { bounds = value; } } FormWindowState windowState; public FormWindowState WindowState { get { return windowState; } set { windowState = value; } } } private Rectangle ConstrainToScreen(Rectangle bounds) { Screen screen = Screen.FromRectangle(WindowRestoreState.Bounds); 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 Rectangle(left, top, width, height); } } }
SettingsBindableAttribute
INotifyPropertyChanged的
RoadWarrior.. 5
Form.Size属性在设置绑定UI中不可用的原因是因为此属性标记为DesignerSerializationVisibility.Hidden.这意味着设计人员不知道如何序列化它,更不用说为它生成数据绑定了.而是Form.ClientSize属性是序列化的属性.
如果您尝试通过绑定Location和ClientSize来变得聪明,您将看到另一个问题.当您尝试从左侧或上边缘调整表单大小时,您会看到奇怪的行为.这显然与双向数据绑定在相互影响的属性集的上下文中的工作方式有关.这两个位置和ClientSize最终调用到一个共同的方法,SetBoundsCore() .
此外,绑定到位置和大小等属性的数据效率不高.每次用户移动或调整表单大小时,Windows都会向表单发送数百条消息,导致数据绑定逻辑进行大量处理,而您真正想要的只是在表单关闭之前存储最后的位置和大小.
这是我所做的非常简化的版本:
private void MyForm_FormClosing(object sender, FormClosingEventArgs e) { Properties.Settings.Default.MyState = this.WindowState; if (this.WindowState == FormWindowState.Normal) { Properties.Settings.Default.MySize = this.Size; Properties.Settings.Default.MyLoc = this.Location; } else { Properties.Settings.Default.MySize = this.RestoreBounds.Size; Properties.Settings.Default.MyLoc = this.RestoreBounds.Location; } Properties.Settings.Default.Save(); } private void MyForm_Load(object sender, EventArgs e) { this.Size = Properties.Settings.Default.MySize; this.Location = Properties.Settings.Default.MyLoc; this.WindowState = Properties.Settings.Default.MyState; }
为什么这是一个非常简化的版本?因为正确地做这件事比它看起来要复杂得多:-)
我终于想出了一个Form子类,它一劳永逸地解决了这个问题.要使用它:
继承自RestorableForm而不是Form.
在(ApplicationSettings) - >(PropertyBinding)中添加一个绑定到WindowRestoreState.
当窗口即将关闭时调用Properties.Settings.Default.Save().
现在会在会话之间记住窗口位置和状态.根据下面其他海报的建议,我添加了一个功能ConstrainToScreen,确保窗口在恢复时可以很好地适应可用的显示.
// Consider this code public domain. If you want, you can even tell // your boss, attractive women, or the other guy in your cube that // you wrote it. Enjoy! using System; using System.Windows.Forms; using System.ComponentModel; using System.Drawing; namespace Utilities { public class RestorableForm : Form, INotifyPropertyChanged { // We invoke this event when the binding needs to be updated. public event PropertyChangedEventHandler PropertyChanged; // This stores the last window position and state private WindowRestoreStateInfo windowRestoreState; // Now we define the property that we will bind to our settings. [Browsable(false)] // Don't show it in the Properties list [SettingsBindable(true)] // But do enable binding to settings public WindowRestoreStateInfo WindowRestoreState { get { return windowRestoreState; } set { windowRestoreState = value; if (PropertyChanged != null) { // If anybody's listening, let them know the // binding needs to be updated: PropertyChanged(this, new PropertyChangedEventArgs("WindowRestoreState")); } } } protected override void OnClosing(CancelEventArgs e) { WindowRestoreState = new WindowRestoreStateInfo(); WindowRestoreState.Bounds = WindowState == FormWindowState.Normal ? Bounds : RestoreBounds; WindowRestoreState.WindowState = WindowState; base.OnClosing(e); } protected override void OnLoad(EventArgs e) { base.OnLoad(e); if (WindowRestoreState != null) { Bounds = ConstrainToScreen(WindowRestoreState.Bounds); WindowState = WindowRestoreState.WindowState; } } // This helper class stores both position and state. // That way, we only have to set one binding. public class WindowRestoreStateInfo { Rectangle bounds; public Rectangle Bounds { get { return bounds; } set { bounds = value; } } FormWindowState windowState; public FormWindowState WindowState { get { return windowState; } set { windowState = value; } } } private Rectangle ConstrainToScreen(Rectangle bounds) { Screen screen = Screen.FromRectangle(WindowRestoreState.Bounds); 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 Rectangle(left, top, width, height); } } }
SettingsBindableAttribute
INotifyPropertyChanged的
Form.Size属性在设置绑定UI中不可用的原因是因为此属性标记为DesignerSerializationVisibility.Hidden.这意味着设计人员不知道如何序列化它,更不用说为它生成数据绑定了.而是Form.ClientSize属性是序列化的属性.
如果您尝试通过绑定Location和ClientSize来变得聪明,您将看到另一个问题.当您尝试从左侧或上边缘调整表单大小时,您会看到奇怪的行为.这显然与双向数据绑定在相互影响的属性集的上下文中的工作方式有关.这两个位置和ClientSize最终调用到一个共同的方法,SetBoundsCore() .
此外,绑定到位置和大小等属性的数据效率不高.每次用户移动或调整表单大小时,Windows都会向表单发送数百条消息,导致数据绑定逻辑进行大量处理,而您真正想要的只是在表单关闭之前存储最后的位置和大小.
这是我所做的非常简化的版本:
private void MyForm_FormClosing(object sender, FormClosingEventArgs e) { Properties.Settings.Default.MyState = this.WindowState; if (this.WindowState == FormWindowState.Normal) { Properties.Settings.Default.MySize = this.Size; Properties.Settings.Default.MyLoc = this.Location; } else { Properties.Settings.Default.MySize = this.RestoreBounds.Size; Properties.Settings.Default.MyLoc = this.RestoreBounds.Location; } Properties.Settings.Default.Save(); } private void MyForm_Load(object sender, EventArgs e) { this.Size = Properties.Settings.Default.MySize; this.Location = Properties.Settings.Default.MyLoc; this.WindowState = Properties.Settings.Default.MyState; }
为什么这是一个非常简化的版本?因为正确地做这件事比它看起来要复杂得多:-)