【问题标题】:How to record window position in Windows Forms application settings如何在 Windows 窗体应用程序设置中记录窗口位置
【发布时间】:2010-09-11 11:31:01
【问题描述】:

这似乎是一个标准要求:下次用户启动应用程序时,以与之前相同的位置和状态打开窗口。这是我的愿望清单:

  • 窗口位置与原来相同
    • 除非屏幕已调整大小并且旧位置现在不在屏幕上。
  • 拆分器应保留其位置
  • 选项卡容器应保留其选择
  • 某些下拉菜单应保留其选择
  • 窗口状态(最大化、最小化、正常)与原来相同。
    • 也许你永远不应该开始最小化,我还没有决定。

我将添加我当前的解决方案作为答案以及限制。

【问题讨论】:

    标签: c# .net winforms settings


    【解决方案1】:

    我的另一个选择是围绕应用程序设置编写更多自定义代码并在 formLoad 和 formClosed 上执行它。这不使用数据绑定。

    缺点:

    • 要编写更多代码。
    • 非常繁琐。您在 formLoad 上设置属性的顺序令人困惑。例如,您必须确保在设置分隔器距离之前已设置窗口大小。

    目前,这是我首选的解决方案,但似乎工作量太大。为了减少工作量,我创建了一个 WindowSettings 类,它将窗口位置、大小、状态和任何拆分器位置序列化为单个应用程序设置。然后我可以为我的应用程序中的每个表单创建一个该类型的设置,在关闭时保存并在加载时恢复。

    我发布了the source code,包括WindowSettings 类和一些使用它的表单。将其添加到项目的说明包含在 WindowSettings.cs 文件中。最棘手的部分是弄清楚如何添加具有自定义类型的应用程序设置。您从类型下拉列表中选择 Browse...,然后手动输入命名空间和类名。您项目中的类型未显示在列表中。

    更新:我添加了一些静态方法来简化您添加到每个表单的样板代码。按照说明将 WindowSettings 类添加到项目并创建应用程序设置后,这里有一个代码示例,必须添加到您要记录和恢复其位置的每个表单。

        private void MyForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            Settings.Default.CustomWindowSettings = WindowSettings.Record(
                Settings.Default.CustomWindowSettings,
                this, 
                splitContainer1);
        }
    
        private void MyForm_Load(object sender, EventArgs e)
        {
            WindowSettings.Restore(
                Settings.Default.CustomWindowSettings, 
                this, 
                splitContainer1);
        }
    

    【讨论】:

    • 不幸的是,绑定示例的 donkirkby 项目的许可证可能不允许简单地随意使用代码。考虑在此处重新发布。
    • 我现在已更改为 MIT 许可证;我并不是要限制代码的使用。当然,归因是值得赞赏的。代码有点长,贴在这里。
    • 做得好,唐。感谢迅速的反应。不用说,功劳归于应得的地方,麻省理工学院的许可证应该照顾到这一点。
    • 这看起来很酷,但是当我尝试添加设置并在 Mynamespace.WindowSettings 中为 type 字段键入时,它显示 Type 'Mynamespace.WindowSettings' 未定义。这是在 .NET 4.0 中 - 有什么想法吗?
    • 好的,我试试看。我真的希望它能够工作,因为我可以扩展类以保存我的列表视图列宽等。非常好的想法。
    【解决方案2】:

    您可以使用设置来存储该信息。您所要做的就是将所需的属性(例如 form.Size 和 form.Location)绑定到特定设置,然后它会自动保存和更新。

    【讨论】:

      【解决方案3】:

      根据 Don Kirkby 接受的答案和他编写的 WindowSettings 类,您可以从标准表单派生一个 CustomForm 以减少为每个表单编写的相同代码的数量,可能像这样:

      using System;
      using System.Configuration;
      using System.Reflection;
      using System.Windows.Forms;
      
      namespace CustomForm
      {
        public class MyCustomForm : Form
        {
          private ApplicationSettingsBase _appSettings = null;
          private string _settingName = "";
      
          public Form() : base() { }
      
          public Form(ApplicationSettingsBase settings, string settingName)
            : base()
          {
            _appSettings = settings;
            _settingName = settingName;
      
            this.Load += new EventHandler(Form_Load);
            this.FormClosing += new FormClosingEventHandler(Form_FormClosing);
          }
      
          private void Form_Load(object sender, EventArgs e)
          {
            if (_appSettings == null) return;
      
            PropertyInfo settingProperty = _appSettings.GetType().GetProperty(_settingName);
            if (settingProperty == null) return;
      
            WindowSettings previousSettings = settingProperty.GetValue(_appSettings, null) as WindowSettings;
            if (previousSettings == null) return;
      
            previousSettings.Restore(this);
          }
      
          private void Form_FormClosing(object sender, FormClosingEventArgs e)
          {
            if (_appSettings == null) return;
      
            PropertyInfo settingProperty = _appSettings.GetType().GetProperty(_settingName);
            if (settingProperty == null) return;
      
            WindowSettings previousSettings = settingProperty.GetValue(_appSettings, null) as WindowSettings;
            if (previousSettings == null)
              previousSettings = new WindowSettings();
      
            previousSettings.Record(this);
      
            settingProperty.SetValue(_appSettings, previousSettings, null);
      
            _appSettings.Save();
          }
        }
      }
      

      要使用它,请在构造函数中传递您的应用程序设置类和设置名称:

      CustomForm.MyCustomForm f = new CustomForm.MyCustomForm(Properties.Settings.Default, "formSettings");
      

      这使用反射从/到设置类获取/设置以前的设置。将 Save 调用放入 Form_Closing 例程中可能不是最佳选择,只要主应用程序退出,就可以删除它并保存设置文件。

      要将其用作常规形式,只需使用无参数构造函数即可。

      【讨论】:

      • 减少样板代码是一个有趣的想法。我可能会将它调整为 WindowSettings 类上的静态方法,该方法将 WindowSettings 对象作为 ref 参数。我认为这可以避免使用反射。
      • 感谢您的评论。我不确定如何使用反射来解决问题。您必须传递一些代表设置对象的类型。由于包含设置的对象因项目而异,我不知道如何在不使用反射的情况下设置和提取设置,因为每种情况下的属性名称都不同。为简化起见,我还尝试使用 this.Name 构造一个更通用的设置名称,但这不起作用,因为表单名称是在 InitializeComponent 调用中设置的,这发生在调用基本构造函数之后.
      • 好的,我在示例代码中添加了静态方法:code.google.com/p/donkirkby/source/browse/trunk/WindowSettings/… 您可以在上面接受的答案的 sn-p 中看到它们是如何被调用的。谢谢你的想法。
      • 对,我误解了你的做法。这更加简化了事情......
      • 我突然想到了......因为我可能会在未来的每个项目中使用它,它可能是扩展方法的一个很好的用例。我会考虑一段时间...
      【解决方案4】:

      下面的示例显示了我是如何做到的

      • 在关闭表单时调用 SavePreferences 并保存表单的大小,以及指示它是否已最大化的标志(在此版本中,如果已最小化,我不会保存 - 下次它会恢复或最大化) .

      • LoadPreferences 从 OnLoad 中调用。

      • 首先保存设计时的 WindowState 并将其设置为 Normal。只有WindowState为Normal才能成功设置表单大小。

      • 接下来从您的持久设置中恢复大小。

      • 现在确保表单适合您的屏幕(调用 FitToScreen)。自您上次运行该应用程序以来,屏幕分辨率可能已更改。

      • 最后将 WindowState 设置回 Maximized(如果这样保持不变),或者设置为之前保存的设计时值。

      这显然可以适应保持起始位置以及关闭时表单是否最小化 - 我不需要这样做。表单上控件的其他设置(例如拆分器位置和选项卡容器)非常简单。

      private void FitToScreen()
      {
          if (this.Width > Screen.PrimaryScreen.WorkingArea.Width)
          {
              this.Width = Screen.PrimaryScreen.WorkingArea.Width;
          }
          if (this.Height > Screen.PrimaryScreen.WorkingArea.Height)
          {
              this.Height = Screen.PrimaryScreen.WorkingArea.Height;
          }
      }   
      private void LoadPreferences()
      {
          // Called from Form.OnLoad
      
          // Remember the initial window state and set it to Normal before sizing the form
          FormWindowState initialWindowState = this.WindowState;
          this.WindowState = FormWindowState.Normal;
          this.Size = UserPreferencesManager.LoadSetting("_Size", this.Size);
          _currentFormSize = Size;
          // Fit to the current screen size in case the screen resolution
          // has changed since the size was last persisted.
          FitToScreen();
          bool isMaximized = UserPreferencesManager.LoadSetting("_Max", initialWindowState == FormWindowState.Maximized);
          WindowState = isMaximized ? FormWindowState.Maximized : FormWindowState.Normal;
      }
      private void SavePreferences()
      {
          // Called from Form.OnClosed
          UserPreferencesManager.SaveSetting("_Size", _currentFormSize);
          UserPreferencesManager.SaveSetting("_Max", this.WindowState == FormWindowState.Maximized);
          ... save other settings
      }
      

      x

      【讨论】:

      • 我试过了,但在 UserPreferencesManager 参考方面遇到了问题。 Google 指出这是一个 Java 类,而不是 c#!
      • 我不是很清楚是不是。在这个示例中,UserPreferencesManager 是我编写的一个类,它负责将设置加载和保存到持久媒体。这是针对 .NET 1.1 的,现在您会使用 .NET 2.0 设置架构来实现持久性。请注意,此示例的重点是加载设置时设置属性的顺序,而不是保存/恢复它们的详细信息。
      • 代码注释说“适合当前屏幕尺寸...”但实际代码使用 Screen.PrimaryScreen:当前屏幕可能不是多显示器设置中的 PrimaryScreen!
      • @onedaywhen - 好点,但为了缓解问题,这是为所有屏幕尺寸相同且起始位置不持久的环境编写的。
      【解决方案5】:

      我为每个要保存的值进行设置,并使用如下代码:

      private void MainForm_Load(object sender, EventArgs e) {
        RestoreState();
      }
      
      private void MainForm_FormClosing(object sender, FormClosingEventArgs e) {
        SaveState();
      }
      
      private void SaveState() {
        if (WindowState == FormWindowState.Normal) {
          Properties.Settings.Default.MainFormLocation = Location;
          Properties.Settings.Default.MainFormSize = Size;
        } else {
          Properties.Settings.Default.MainFormLocation = RestoreBounds.Location;
          Properties.Settings.Default.MainFormSize = RestoreBounds.Size;
        }
        Properties.Settings.Default.MainFormState = WindowState;
        Properties.Settings.Default.SplitterDistance = splitContainer1.SplitterDistance;
        Properties.Settings.Default.Save();
      }
      
      private void RestoreState() {
        if (Properties.Settings.Default.MainFormSize == new Size(0, 0)) {
          return; // state has never been saved
        }
        StartPosition = FormStartPosition.Manual;
        Location = Properties.Settings.Default.MainFormLocation;
        Size = Properties.Settings.Default.MainFormSize;
        // I don't like an app to be restored minimized, even if I closed it that way
        WindowState = Properties.Settings.Default.MainFormState == 
          FormWindowState.Minimized ? FormWindowState.Normal : Properties.Settings.Default.MainFormState;
        splitContainer1.SplitterDistance = Properties.Settings.Default.SplitterDistance;
      }
      

      请记住,重新编译会擦除存储设置的配置文件,因此在保存和恢复之间不要更改代码进行测试。

      【讨论】:

        【解决方案6】:

        我发现的最简单的解决方案是将数据绑定与应用程序设置结合使用。我将窗口上的 location 和 clientSize 属性与拆分器上的 splitterDistance 绑定在一起。

        缺点:

        • 如果您在最小化时关闭窗口,它会在下次隐藏时打开。真的很难把窗户找回来。
        • 如果您在最大化时关闭窗口,它会打开填满整个屏幕,但不会最大化(小问题)。
        • 使用右上角或左下角调整窗口大小实在是太难看了。我猜这两个数据绑定属性正在互相争斗。

        如果您想尝试这种奇怪的行为,我使用这种技术发布了sample solution

        【讨论】:

        • 您可以参考一个 WindowState 属性来检查最小化或最大化的窗口。但是,我无法评论处于这两种状态之一时的窗口大小。
        • 是的,我尝试了 WindowState 属性。它的行为非常奇怪。可能与调整大小的问题类似,同时更改多个数据绑定属性会导致争用和闪烁。
        【解决方案7】:

        这是我自己使用的几个示例。它只考虑主显示器,因此如果在多台显示器上使用,最好以不同方式处理。

        Size size;
        int x;
        int y;
        if (WindowState.Equals(FormWindowState.Normal))
        {
            size = Size;
            if (Location.X + size.Width > Screen.PrimaryScreen.Bounds.Width)
                x = Screen.PrimaryScreen.Bounds.Width - size.Width;
            else
                x = Location.X;
            if (Location.Y + Size.Height > Screen.PrimaryScreen.Bounds.Height)
                y = Screen.PrimaryScreen.Bounds.Height - size.Height;
            else
                y = Location.Y;
        }
        else
        {
        size = RestoreBounds.Size;
        x = (Screen.PrimaryScreen.Bounds.Width - size.Width)/2;
        y = (Screen.PrimaryScreen.Bounds.Height - size.Height)/2;
        }
        Properties.Settings.Position.AsPoint = new Point(x, y); // Property setting is type of Point
        Properties.Settings.Size.AsSize = size;                 // Property setting is type of Size
        Properties.Settings.SplitterDistance.Value = splitContainer1.SplitterDistance; // Property setting is type of int
        Properties.Settings.IsMaximized = WindowState == FormWindowState.Maximized;    // Property setting is type of bool
        Properties.Settings.DropDownSelection = DropDown1.SelectedValue;
        Properties.Settings.Save();
        

        【讨论】:

        • 是的,这就是我在第二个答案中所说的那种自定义代码。正如我所说,做对是很麻烦的。我希望有一个更简单的方法。不过,谢谢,我不知道 AsSize 和 AsPoint 属性。我会检查一下。
        【解决方案8】:

        您可以使用应用程序设置来设置哪些控件属性将被持久化,在 Form_closed 事件中您必须使用应用程序设置上的保存方法将这些属性写入磁盘:

        Properties.Settings.Default.Save();
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-07-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多