C#:如何使表单记住其Bounds和WindowState(考虑双显示器设置)

我创建了一个表可以inheritance的类,它处理表单位置,大小和状态。 而且效果很好。 除了一件事:

当您在与主要屏幕不同的屏幕上最大化应用程序时,位置和大小(在最大化之前)被正确存储,但是当它最大化时(根据其先前的状态),它在我的主监视器上最大化。 当我将其恢复到正常状态时,它会转到之前的其他屏幕。 当我再次最大化它时,它当然会在正确的屏幕上最大化。

所以我的问题是……我怎样才能制作一个表格,当它最大化时,记住它最大化的屏幕是什么? 当表单再次打开时,如何恢复?


一种完整的问题解决方案

我接受了一个答案,该答案对如何在屏幕上有一个非常好的提示。 但这只是我问题的一部分,所以这是我的解决方案:

负载

  1. 首先从任何存储中获取存储的BoundsWindowState
  2. 然后设置Bounds
  3. 通过Screen.AllScreens.Any(ø => ø.Bounds.IntersectsWith(Bounds))MdiParent.Controls.OfType().First().ClientRectangle.IntersectsWith(Bounds)确保Bounds可见。
    • 如果没有,只需做Location = new Point();
  4. 然后设置窗口状态。

关闭时

  1. 存储WindowState
  2. 如果WindowStateFormWindowState.Normal ,则存储Bounds ,否则存储RestoreBounds

这就是它! =)

一些示例代码

因此,正如Oliver所建议的,这里有一些代码。 它需要充实,但这可以作为任何想要的人的开始:

PersistentFormHandler

负责在某处存储和获取数据。

 public sealed class PersistentFormHandler { /// The form identifier in storage. public string Name { get; private set; } /// Gets and sets the window state. (int instead of enum so that it can be in a BI layer, and not require a reference to WinForms) public int WindowState { get; set; } /// Gets and sets the window bounds. (X, Y, Width and Height) public Rectangle WindowBounds { get; set; } /// Dictionary for other values. private readonly Dictionary otherValues; ///  /// Instantiates new persistent form handler. ///  /// The  will be used as . /// Default state of the window. /// Default bounds of the window. public PersistentFormHandler(Type windowType, int defaultWindowState, Rectangle defaultWindowBounds) : this(windowType, null, defaultWindowState, defaultWindowBounds) { } ///  /// Instantiates new persistent form handler. ///  /// The  will be used as base . /// Use this if you need to separate windows of same type. Will be appended to . /// Default state of the window. /// Default bounds of the window. public PersistentFormHandler(Type windowType, string id, int defaultWindowState, Rectangle defaultWindowBounds) { Name = string.IsNullOrEmpty(id) ? windowType.FullName : windowType.FullName + ":" + id; WindowState = defaultWindowState; WindowBounds = defaultWindowBounds; otherValues = new Dictionary(); } ///  /// Looks for previously stored values in database. ///  /// False if no previously stored values were found. public bool Load() { // See Note 1 } ///  /// Stores all values in database ///  public void Save() { // See Note 2 } ///  /// Adds the given  to the collection of values that will be /// stored in database on . ///  /// Type of object. /// The key you want to use for this value. /// The value to store. public void Set(string key, T value) { // Create memory stream using (var s = new MemoryStream()) { // Serialize value into binary form var b = new BinaryFormatter(); b.Serialize(s, value); // Store in dictionary otherValues[key] = new Binary(s.ToArray()); } } ///  /// Same as , but uses default() as fallback value. ///  /// Type of object /// The key used on . /// The stored object, or the default() object if something went wrong. public T Get(string key) { return Get(key, default(T)); } ///  /// Gets the value identified by the given . ///  /// Type of object /// The key used on . /// Value to return if the given  could not be found. /// In other words, if you haven't used  yet. /// The stored object, or the  object if something went wrong. public T Get(string key, T fallback) { // If we have a value with this key if (otherValues.ContainsKey(key)) { // Create memory stream and fill with binary version of value using (var s = new MemoryStream(otherValues[key].ToArray())) { try { // Deserialize, cast and return. var b = new BinaryFormatter(); return (T)b.Deserialize(s); } catch (InvalidCastException) { // T is not what it should have been // (Code changed perhaps?) } catch (SerializationException) { // Something went wrong during Deserialization } } } // Else return fallback return fallback; } } 

注1:在load方法中,您必须查找以前存储的WindowStateWindowBounds和其他值。 我们使用SQL Server,并有一个Window表,其中包含IdNameMachineName (对于Environment.MachineName ), UserIdWindowStateXYHeightWidth 。 因此,对于每个窗口,每个用户和机器都有一行具有WindowStateXYHeightWidth 。 另外,我们有一个WindowValues表,它只有一个WindowId的外键,一个String类型的Key列和一个Binary类型的Value列。 如果有找不到的东西,我只是保留默认值并返回false。

注意2:在保存方法中,您当然要执行与Load方法相反的操作。 为WindowWindowValues创建行(如果它们对于当前用户和计算机尚不存在)。

PersistentFormBase

该类使用前一个类,并为其他表单形成一个方便的基类。

 // Should have been abstract, but that makes the the designer crash at the moment... public class PersistentFormBase : Form { private PersistentFormHandler PersistenceHandler { get; set; } private bool handlerReady; protected PersistentFormBase() { // Prevents designer from crashing if (LicenseManager.UsageMode != LicenseUsageMode.Designtime) { Load += persistentFormLoad; FormClosing += persistentFormFormClosing; } } protected event EventHandler ValuesLoaded; protected event EventHandler StoringValues; protected void StoreValue(string key, T value) { if (!handlerReady) throw new InvalidOperationException(); PersistenceHandler.Set(key, value); } protected T GetValue(string key) { if (!handlerReady) throw new InvalidOperationException(); return PersistenceHandler.Get(key); } protected T GetValue(string key, T fallback) { if (!handlerReady) throw new InvalidOperationException(); return PersistenceHandler.Get(key, fallback); } private void persistentFormLoad(object sender, EventArgs e) { // Create PersistenceHandler and load values from it PersistenceHandler = new PersistentFormHandler(GetType(), (int) FormWindowState.Normal, Bounds); PersistenceHandler.Load(); handlerReady = true; // Set size and location Bounds = PersistenceHandler.WindowBounds; // Check if we have an MdiParent if(MdiParent == null) { // If we don't, make sure we are on screen if (!Screen.AllScreens.Any(ø => ø.Bounds.IntersectsWith(Bounds))) Location = new Point(); } else { // If we do, make sure we are visible within the MdiClient area var c = MdiParent.Controls.OfType().FirstOrDefault(); if(c != null && !c.ClientRectangle.IntersectsWith(Bounds)) Location = new Point(); } // Set state WindowState = Enum.IsDefined(typeof (FormWindowState), PersistenceHandler.WindowState) ? (FormWindowState) PersistenceHandler.WindowState : FormWindowState.Normal; // Notify that values are loaded and ready for getting. var handler = ValuesLoaded; if (handler != null) handler(this, EventArgs.Empty); } private void persistentFormFormClosing(object sender, FormClosingEventArgs e) { // Set common things PersistenceHandler.WindowState = (int) WindowState; PersistenceHandler.WindowBounds = WindowState == FormWindowState.Normal ? Bounds : RestoreBounds; // Notify that values will be stored now, so time to store values. var handler = StoringValues; if (handler != null) handler(this, EventArgs.Empty); // Save values PersistenceHandler.Save(); } } 

这就是它。 要使用它,表单只会从PersistentFormBaseinheritance。 这将自动处理边界和状态。 如果还应该存储其他任何内容,比如分割器距离,您将监听ValuesLoadedStoringValues事件,并使用GetValueStoreValue方法。

希望这可以帮助别人! 如果有,请告诉我。 而且,如果您认为可以做得更好或者其他什么,请提供一些反馈。 我想学习=)

我找到了一个解决问题的方法,通过编写一个小函数来测试,如果一个poitn在连接的屏幕上。 主要想法来自http://msdn.microsoft.com/en-us/library/system.windows.forms.screen(VS.80).aspx,但需要进行一些修改。

 public static bool ThisPointIsOnOneOfTheConnectedScreens(Point thePoint) { bool FoundAScreenThatContainsThePoint = false; for(int i = 0; i < Screen.AllScreens.Length; i++) { if(Screen.AllScreens[i].Bounds.Contains(thePoint)) FoundAScreenThatContainsThePoint = true; } return FoundAScreenThatContainsThePoint; } 

没有内置的方法来做到这一点 – 你必须自己编写逻辑。 其中一个原因是您必须决定如何处理上次显示窗口的监视器不再可用的情况。 例如,这对于笔记本电脑和投影仪来说非常普遍。 Screen类有一些有用的function可以帮助解决这个问题,尽管很难唯一且一致地识别显示。

上述解决方案存在一些问题。

在多个屏幕上以及恢复屏幕较小时。

它应该使用Contains (…),而不是IntersectsWith,因为窗体的控件部分可能会在屏幕区域之外。

我将根据这些思路提出一些建议

 bool TestBounds(Rectangle R) { if (MdiParent == null) return Screen.AllScreens.Any(ø => ø.Bounds.Contains(R)); // If we don't have an MdiParent, make sure we are entirely on a screen var c = MdiParent.Controls.OfType().FirstOrDefault(); // If we do, make sure we are visible within the MdiClient area return (c != null && c.ClientRectangle.Contains(R)); } 

并像这样使用。 (注意,如果保存的值不起作用,我让Windows处理它)

 bool BoundsOK=TestBounds(myBounds); if (!BoundsOK) { myBounds.Location = new Point(8,8); // then try (8,8) , to at least keep the size, if other monitor not currently present, or current is lesser BoundsOK = TestBounds(myBounds); } if (BoundsOK) { //Only change if acceptable, otherwise let Windows handle it StartPosition = FormStartPosition.Manual; Bounds = myBounds; WindowState = Enum.IsDefined(typeof(FormWindowState), myWindowState) ? (FormWindowState)myWindowState : FormWindowState.Normal; } 

尝试在恢复(非最大化)状态下在其保存位置生成主窗体,然后在最后一个状态最大化时最大化它。

正如Stu所说,在这种情况下要小心移除显示器。 由于保存的位置可能包含屏幕外坐标(甚至是负屏幕坐标),因此您可以有效地结束和不可见(实际上是屏幕外)窗口。 我认为在加载之前的状态之前检查桌面边界应该可以防止这种情况。