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

时间:2009-01-30 12:36:06

标签: c# winforms persistence

我创建了一个表可以继承的类,它处理表单Location,Size和State。而且效果很好。除了一件事:

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

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


对问题的完整解决方案

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

正在加载

  1. 首先从任何存储空间中存储BoundsWindowState
  2. 然后设置Bounds
  3. 确保BoundsScreen.AllScreens.Any(ø => ø.Bounds.IntersectsWith(Bounds))可以看到MdiParent.Controls.OfType<MdiClient>().First().ClientRectangle.IntersectsWith(Bounds)
    • 如果没有,请执行Location = new Point();
  4. 然后设置窗口状态。
  5. 关闭时

    1. 存储WindowState
    2. 如果WindowStateFormWindowState.Normal,则存储Bounds,否则存储RestoreBounds
    3. 就是这样! =)

      一些示例代码

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

      PersistentFormHandler

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

      public sealed class PersistentFormHandler
      {
          /// <summary>The form identifier in storage.</summary>
          public string Name { get; private set; }
      
      
          /// <summary>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)</summary>
          public int WindowState { get; set; }
      
      
          /// <summary>Gets and sets the window bounds. (X, Y, Width and Height)</summary>
          public Rectangle WindowBounds { get; set; }
      
      
          /// <summary>Dictionary for other values.</summary>
          private readonly Dictionary<string, Binary> otherValues;
      
      
          /// <summary>
          /// Instantiates new persistent form handler.
          /// </summary>
          /// <param name="windowType">The <see cref="Type.FullName"/> will be used as <see cref="Name"/>.</param>
          /// <param name="defaultWindowState">Default state of the window.</param>
          /// <param name="defaultWindowBounds">Default bounds of the window.</param>
          public PersistentFormHandler(Type windowType, int defaultWindowState, Rectangle defaultWindowBounds)
              : this(windowType, null, defaultWindowState, defaultWindowBounds) { }
      
          /// <summary>
          /// Instantiates new persistent form handler.
          /// </summary>
          /// <param name="windowType">The <see cref="Type.FullName"/> will be used as base <see cref="Name"/>.</param>
          /// <param name="id">Use this if you need to separate windows of same type. Will be appended to <see cref="Name"/>.</param>
          /// <param name="defaultWindowState">Default state of the window.</param>
          /// <param name="defaultWindowBounds">Default bounds of the window.</param>
          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<string, Binary>();
          }
      
      
          /// <summary>
          /// Looks for previously stored values in database.
          /// </summary>
          /// <returns>False if no previously stored values were found.</returns>
          public bool Load()
          {
              // See Note 1
          }
      
          /// <summary>
          /// Stores all values in database
          /// </summary>
          public void Save()
          {
              // See Note 2
          }
      
          /// <summary>
          /// Adds the given <paramref key="value"/> to the collection of values that will be
          /// stored in database on <see cref="Save"/>.
          /// </summary>
          /// <typeparam key="T">Type of object.</typeparam>
          /// <param name="key">The key you want to use for this value.</param>
          /// <param name="value">The value to store.</param>
          public void Set<T>(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());
              }
          }
      
          /// <summary>
          /// Same as <see cref="Get{T}(string,T)"/>, but uses default(<typeparamref name="T"/>) as fallback value.
          /// </summary>
          /// <typeparam name="T">Type of object</typeparam>
          /// <param name="key">The key used on <see cref="Set{T}"/>.</param>
          /// <returns>The stored object, or the default(<typeparamref name="T"/>) object if something went wrong.</returns>
          public T Get<T>(string key)
          {
              return Get(key, default(T));
          }
      
          /// <summary>
          /// Gets the value identified by the given <paramref name="key"/>.
          /// </summary>
          /// <typeparam name="T">Type of object</typeparam>
          /// <param name="key">The key used on <see cref="Set{T}"/>.</param>
          /// <param name="fallback">Value to return if the given <paramref name="key"/> could not be found.
          /// In other words, if you haven't used <see cref="Set{T}"/> yet.</param>
          /// <returns>The stored object, or the <paramref name="fallback"/> object if something went wrong.</returns>
          public T Get<T>(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:在加载方法中,您必须查找以前存储的WindowStateWindowBounds和其他值。我们使用SQL Server,并且Window表包含IdNameMachineName(适用于Environment.MachineName),UserId,{的列{1}},WindowStateXYHeight。因此,对于每个窗口,每个用户和计算机都会有一行WidthWindowStateXYHeight。另外,我们有一个Width表,其中只有WindowValues的外键,WindowIdKeyStringValue 1}}。如果有找不到的东西,我只是保留默认值并返回false。

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

      PersistentFormBase

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

      WindowValues

      这就是它。要使用它,表单只会从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<EventArgs> ValuesLoaded; protected event EventHandler<EventArgs> StoringValues; protected void StoreValue<T>(string key, T value) { if (!handlerReady) throw new InvalidOperationException(); PersistenceHandler.Set(key, value); } protected T GetValue<T>(string key) { if (!handlerReady) throw new InvalidOperationException(); return PersistenceHandler.Get<T>(key); } protected T GetValue<T>(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<MdiClient>().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(); } } ValuesLoaded个事件,并使用StoringValuesGetValue方法。

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

4 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:3)

我通过编写一个小函数找到了解决问题的方法,如果某个点位于连接的屏幕上,则会测试。 主要想法来自 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;
    }

答案 2 :(得分:1)

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

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

它应该使用包含(...),而不是 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<MdiClient>().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;
}

答案 3 :(得分:0)

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

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