Как записать положение окна в настройках приложения Windows Forms

Это похоже на стандартное требование: в следующий раз, когда пользователь запустит приложение, откройте окно в том же положении и состоянии, что и раньше. Вот мой список желаний:

  • Window position same as it was
    • Unless the screen has resized and the old position is now off screen.
  • Сплиттеры должны сохранять свое положение
  • Контейнеры вкладок должны сохранить свой выбор
  • Некоторые раскрывающиеся списки должны сохранить свой выбор
  • Window state (maximize, minimize, normal) is the same as it was.
    • Maybe you should never start minimized, I haven't decided.

Я добавлю свои текущие решения в качестве ответа вместе с ограничениями.


person Don Kirkby    schedule 19.09.2008    source источник


Ответы (8)


Другой вариант - написать дополнительный код для настроек приложения и выполнить его в formLoad и formClosed. Это не использует привязку данных.

Недостатки:

  • Еще код для написания.
  • Очень неудобно. Порядок, в котором вы устанавливаете свойства в formLoad, сбивает с толку. Например, вы должны убедиться, что вы установили размер окна, прежде чем устанавливать расстояние разделителя.

Сейчас это мое предпочтительное решение, но, похоже, слишком много работы. Чтобы уменьшить объем работы, я создал класс WindowSettings, который сериализует расположение окна, размер, состояние и любые положения разделителя в одну настройку приложения. Затем я могу просто создать настройку этого типа для каждой формы в моем приложении, сохранить при закрытии и восстановить при загрузке.

Я разместил исходный код, включая класс WindowSettings и некоторые формы, которые его используют. Инструкции по добавлению его в проект включены в файл WindowSettings.cs. Самым сложным было выяснить, как добавить параметр приложения с настраиваемым типом. Вы выбираете Обзор ... в раскрывающемся списке типов, а затем вручную вводите пространство имен и имя класса. Типы из вашего проекта не отображаются в списке.

Обновление: я добавил несколько статических методов, чтобы упростить шаблонный код, который вы добавляете в каждую форму. После того, как вы выполнили инструкции по добавлению класса 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);
    }
person Don Kirkby    schedule 19.09.2008
comment
К сожалению, лицензия проекта donkirkby, в соответствии с которым связан образец, может не допускать простое добровольное использование кода. Рассмотрите возможность повторной публикации здесь. - person Atif Aziz; 07.04.2009
comment
Теперь я перешел на лицензию MIT; Я не хотел ограничивать использование кода. Конечно, атрибуция приветствуется. Код немного длинноват, чтобы публиковать здесь. - person Don Kirkby; 08.04.2009
comment
Хороший ход, Дон. Оцените быстрый ответ. Излишне говорить, что заслуга идет там, где она должна быть, и лицензия MIT должна позаботиться об этом. - person Atif Aziz; 14.04.2009
comment
Это выглядит действительно круто, но когда я пытаюсь добавить параметр и набираю Mynamespace.WindowSettings для поля типа, он говорит, что Тип «Mynamespace.WindowSettings» не определен. Это в .NET 4.0 - есть идеи? - person Charlie Skilbeck; 11.04.2011
comment
@cskilbeck, попробуйте выполнить компиляцию после добавления класса WindowSettings в ваш проект и перед добавлением нового параметра приложения. Вы следовали всем инструкциям в WindowSettings.cs, включая изменение пространства имен в соответствии с вашим проектом? Можете ли вы скомпилировать мой образец проекта в своей среде? Если ничего из этого не помогает, я предлагаю вам опубликовать новый вопрос и связать его с этим. - person Don Kirkby; 11.04.2011
comment
Хорошо, я попробую. Я действительно хочу, чтобы это сработало, так как я могу расширить класс, чтобы сохранить ширину столбцов списка и т. Д. Очень интересная идея. - person Charlie Skilbeck; 12.04.2011
comment
Ага, работает! Теперь он даже показывает тип в раскрывающемся списке. Я скомпилировал его, но, возможно, выход и перезапуск VS2010 могли заставить его работать. Большое спасибо за это, очень хорошее решение. - person Charlie Skilbeck; 12.04.2011

В приведенном ниже примере показано, как я это делаю.

  • 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

person Joe    schedule 20.09.2008
comment
Я пробовал это, и у меня возникли проблемы со ссылкой на UserPreferencesManager. Google указывает, что это класс Java, а не C #! - person Tom Bushell; 09.12.2009
comment
Мне не очень ясно, был ли я. В этом примере UserPreferencesManager - это класс, который я написал, который выполняет работу по загрузке и сохранению настроек на постоянный носитель. Это было для .NET 1.1, в наши дни вы использовали бы архитектуру настроек .NET 2.0 для сохранения. Обратите внимание, что в этом примере основное внимание уделялось порядку, в котором свойства устанавливаются при загрузке параметров, а не деталям того, как они сохраняются / восстанавливаются. - person Joe; 09.12.2009
comment
В комментарии к коду написано Подогнать к текущему размеру экрана ... но фактический код использует Screen.PrimaryScreen: текущий экран может не быть PrimaryScreen в настройке с несколькими мониторами! - person onedaywhen; 18.02.2015
comment
@onedaywhen - хороший момент, но для смягчения последствий это было написано для среды, где все экраны были одного размера, а начальная позиция не сохранялась. - person Joe; 18.02.2015

Самым простым решением, которое я нашел, является использование привязки данных с настройками приложения. Я привязываю свойства location и clientSize к окну вместе с splitterDistance на сплиттере.

Недостатки:

  • Если закрыть окно в свернутом виде, в следующий раз оно откроется скрытым. Вернуть окно очень сложно.
  • Если закрыть окно в развернутом состоянии, оно откроется на весь экран, но не на весь экран (небольшая проблема).
  • Изменение размера окна с помощью верхнего правого или нижнего левого угла просто некрасиво. Я предполагаю, что два свойства привязки данных борются друг с другом.

Если вы хотите поэкспериментировать со странным поведением, я опубликовал образец решения используя эту технику.

person Don Kirkby    schedule 19.09.2008
comment
Существует свойство WindowState, которое можно использовать для проверки свернутых или развернутых окон. Однако я не могу прокомментировать размер окна, когда оно находится в одном из этих двух состояний. - person Austin Salonen; 20.09.2008
comment
Да, я экспериментировал со свойством WindowState. Он вёл себя очень странно. Возможно, проблема аналогична изменению размера, когда изменение нескольких свойств привязки данных одновременно вызывает конфликты и мерцание. - person Don Kirkby; 20.09.2008

Я делаю настройку для каждого значения, которое хочу сохранить, и использую такой код:

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

Имейте в виду, что перекомпиляция стирает файл конфигурации, в котором хранятся настройки, поэтому проверьте его, не внося изменений в код между сохранением и восстановлением.

person Wonko    schedule 20.09.2008

Основываясь на принятом ответе Дона Киркби и написанном им классе 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");

Это использует Reflection, чтобы получить / установить предыдущие настройки из / в класс настроек. Размещение вызова Save в подпрограмме Form_Closing может быть неоптимальным, его можно удалить и сохранить файл настроек при выходе из основного приложения.

Чтобы использовать его как обычную форму, просто используйте конструктор без параметров.

person takrl    schedule 07.04.2011
comment
Это интересная идея сократить шаблонный код. Я мог бы адаптировать его к статическому методу класса WindowSettings, который принимает объект WindowSettings в качестве параметра ref. Я думаю, что можно было избежать использования отражения. - person Don Kirkby; 07.04.2011
comment
Спасибо за комментарий. Я не уверен, как это будет работать, чтобы обойтись с помощью отражения. Вы должны передать некоторый тип, представляющий объект настроек. Поскольку объект, содержащий настройки, варьируется от проекта к проекту, я не знал, как устанавливать и извлекать настройки без использования отражения, поскольку имена свойств различаются для каждого случая. Для упрощения я также попытался использовать this.Name для создания более общего имени параметра, но это не сработало, поскольку имя формы устанавливается в вызове InitializeComponent, который происходит после вызова базового конструктора. . - person takrl; 12.04.2011
comment
Хорошо, я добавил статические методы в пример кода: code.google.com/p/donkirkby/source/browse/trunk/WindowSettings/ Вы можете увидеть, как они вызываются, во фрагменте принятого ответа выше. Спасибо за идею. - person Don Kirkby; 13.04.2011
comment
Правильно, я неправильно понял, как вы это делали. Это еще больше упрощает ... - person takrl; 13.04.2011
comment
... но делает это практически невозможным в производной CustomForm. Чтобы обработать объект настройки, имеющий значение null, его необходимо передать конструктору по ссылке. Это невозможно для собственности. Я обнаружил, что единственное, что работает в этой ситуации, - это передача делегата, вызывающего установщик следующим образом: public frm() : base(Properties.Settings.Default.test, v => Properties.Settings.Default.test = v) ... и мне не нравится, как это выглядит, я должен сказать. - person takrl; 13.04.2011
comment
Извините, @takrl, я раньше не обращал внимания на ваш комментарий. В моем фрагменте принятого ответа вы можете увидеть, как я поступаю с нулевым значением. По сути, это setting = Record(setting, form). Таким образом, если параметр равен нулю, я создаю новый и сохраняю его. Если форма свернута, я просто сохраняю старые настройки. - person Don Kirkby; 19.05.2011
comment
Да, я заметил, как ваш код справляется с этим. Для меня это выглядит довольно элегантно, но мой путь (до того, как вы изменили ваше решение на основе моего сообщения), заключался в создании настраиваемой формы, производной от стандартной, которая делает это автоматически. И для обработки нулевого случая вы должны передать что-то, что может установить свойство в классе Settings. Это один из случаев, когда мог бы пригодиться указатель на средство задания свойств, но это просто невозможно в C #. Конечно, желаемый результат может быть достигнут, опять же, с помощью рефлексии. Решение делегата работает для этого, но я думаю, что это некрасиво. - person takrl; 20.05.2011
comment
Это просто пришло мне в голову ... поскольку это то, что я, вероятно, буду использовать в каждом будущем проекте, это может быть хорошим вариантом использования методов расширения. Я подумаю об этом немного ... - person takrl; 20.05.2011

Вы можете использовать настройки приложения, чтобы установить, какие свойства элемента управления будут сохраняться, в событии Form_closed вы должны использовать метод save в настройках приложения, чтобы записать их на диск:

Properties.Settings.Default.Save();
person benPearce    schedule 19.09.2008

Вот несколько примеров, которые я использую. Он учитывает только основной монитор, поэтому, возможно, будет лучше управлять им по-другому, если он используется на нескольких мониторах.

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();
person Geir-Tore Lindsve    schedule 19.09.2008
comment
Да, это тот тип пользовательского кода, о котором я говорю во втором ответе. Как я уже сказал, делать правильно - непросто. Я надеюсь, что есть способ попроще. Однако спасибо, я не знал о свойствах AsSize и AsPoint. Я проверю их. - person Don Kirkby; 20.09.2008

Хак, вы можете использовать Настройки для хранения этой информации. Все, что вам нужно сделать, это привязать желаемое свойство (например, form.Size и form.Location) к определенному параметру, и оно будет автоматически сохранено и обновлено.

person Dror Helper    schedule 19.09.2008