C# 如何在 Windows 窗体应用程序设置中记录窗口位置

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/105932/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 14:13:02  来源:igfitidea点击:

How to record window position in Windows Forms application settings

提问by Don Kirkby

It seems like a standard requirement: next time the user launches the application, open the window in the same position and state as it was before. Here's my wish list:

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

  • Window position same as it was
    • Unless the screen has resized and the old position is now off screen.
  • Splitters should retain their position
  • Tab containers should retain their selection
  • Some dropdowns should retain their selection
  • Window state (maximize, minimize, normal) is the same as it was.
    • Maybe you should never start minimized, I haven't decided.
  • 窗口位置和原来一样
    • 除非屏幕已调整大小并且旧位置现在不在屏幕上。
  • 分离器应保持其位置
  • 标签容器应保留其选择
  • 一些下拉菜单应该保留它们的选择
  • 窗口状态(最大化、最小化、正常)与以前相同。
    • 也许你永远不应该开始最小化,我还没有决定。

I'll add my current solutions as an answer along with the limitations.

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

采纳答案by Don Kirkby

My other option is to write more custom code around the application settings and execute it on formLoad and formClosed. This doesn't use data binding.

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

Drawbacks:

缺点:

  • More code to write.
  • Very fiddly. The order you set the properties on formLoad is confusing. For example, you have to make sure you've set the window size before you set the splitter distance.
  • 编写更多代码。
  • 很别扭。您在 formLoad 上设置属性的顺序令人困惑。例如,您必须确保在设置拆分器距离之前设置了窗口大小。

Right now, this is my preferred solution, but it seems like too much work. To reduce the work, I created a WindowSettings class that serializes the window location, size, state, and any splitter positions to a single application setting. Then I can just create a setting of that type for each form in my application, save on close, and restore on load.

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

I posted the source code, including the WindowSettings class and some forms that use it. Instructions on adding it to a project are included in the WindowSettings.cs file. The trickiest part was figuring out how to add an application setting with a custom type. You choose Browse... from the type dropdown, and then manually enter the namespace and class name. Types from your project don't show up in the list.

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

Update:I added some static methods to simplify the boilerplate code that you add to each form. Once you've followed the instructions for adding the WindowSettings class to your project and creating an application setting, here's an example of the code that has to be added to each form whose position you want to record and restore.

更新:我添加了一些静态方法来简化您添加到每个表单的样板代码。按照说明将 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);
    }

回答by Don Kirkby

The simplest solution I've found is to use data binding with the application settings. I bind the location and clientSize properties on the window along with the splitterDistance on the splitter.

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

Drawbacks:

缺点:

  • If you close the window while minimized, it opens hidden the next time. It's really hard to get the window back.
  • If you close the window while maximized, it opens filling the whole screen, but not maximized (minor issue).
  • Resizing the window using the top-right corner or the bottom-left corner is just ugly. I guess the two databound properties are fighting each other.
  • 如果您在最小化时关闭窗口,它会在下次打开时隐藏。把窗户弄回来真的很难。
  • 如果在最大化时关闭窗口,它会打开整个屏幕,但不会最大化(小问题)。
  • 使用右上角或左下角调整窗口大小非常难看。我猜这两个数据绑定属性正在互相争斗。

If you'd like to experiment with the strange behaviour, I posted a sample solutionusing this technique.

如果你想尝试奇怪的行为,我发布了一个使用这种技术的示例解决方案

回答by Dror Helper

A hack you can use Settings to store that information. All you have to do is bind the desired property (ex. form.Size and form.Location) to a specific setting and it get saved and updated automatically.

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

回答by benPearce

You can use the application settings to set which control properties will be persisted, in the Form_closed event you have to use the save method on the application settings to write these to disk:

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

Properties.Settings.Default.Save();

回答by Geir-Tore Lindsve

Here is an example of a few I use myself. It only takes into consideration the primary monitor, so it might be better to handle it differently if used on multiple monitors.

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

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();

回答by Wonko

I make a Setting for each value I want to save, and use code like this:

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

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;
}

Keep in mind that recompiling wipes the config file where the settings are stored, so test it without making code changes in between a save and a restore.

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

回答by Joe

The sample below shows how I do it

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

  • SavePreferences is called when closing the form and saves the form's size, and a flag indicating if it's maximized (in this version I don't save if it's minimized - it will come back up restored or maximized next time).

  • LoadPreferences is called from OnLoad.

  • First save the design-time WindowState and set it to Normal. You can only successfully set the form size if it's WindowState is Normal.

  • Next restore the Size from your persisted settings.

  • Now make sure the form fits on your screen (call to FitToScreen). The screen resolution may have changed since you last ran the application.

  • Finally set the WindowState back to Maximized (if persisted as such), or to the design-time value saved earlier.

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

  • LoadPreferences 从 OnLoad 调用。

  • 首先保存设计时WindowState 并将其设置为Normal。只有在 WindowState 为 Normal 时,您才能成功设置窗体大小。

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

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

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

This could obviously be adapted to persist the start position and whether the form was minimized when closed - I didn't need to do that. Other settings for controls on your form such as splitter position and tab container are straightforward.

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

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

X

回答by takrl

Based on the accepted answer by Don Kirkby and the WindowSettings class he wrote, you could derive a CustomForm from the standard one to reduce the amount of identical code written for each and every form, maybe like this:

根据 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();
    }
  }
}

To use this, pass your application settings class and setting name in the constructor:

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

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

This uses Reflection to get/set the previous settings from/to the settings class. It may not be optimal to put the Save call into the Form_Closing routine, one could remove that and save the settings file whenever the main app exits.

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

To use it as a regular form, just use the parameterless constructor.

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