.Чистый WPF и запоминать размер окна между сеансами


в основном, когда пользователь изменяет размер окна моего приложения, я хочу, чтобы приложение было такого же размера, когда приложение снова открывается.

сначала я думал об обработке события SizeChanged и сохранении высоты и ширины, но я думаю, что должно быть более простое решение.

довольно простая проблема, но я не могу найти простое решение для этого.

13 81

13 ответов:

сохраните значения в пользователе.конфигурационный файл.

вам нужно будет создать значение в файле настроек - оно должно быть в папке свойств. Создайте пять значений:

  • Top типа double
  • Left типа double
  • Height типа double
  • Width типа double
  • Maximized типа bool - чтобы удерживать ли окно развернуто или нет. Если вы хотите хранить больше информации после этого другой тип или структура будут необходимы.

инициализируйте первые два до 0, а вторые два до размера по умолчанию вашего приложения, а последний-до false.

в конструкторе:

this.Top = Properties.Settings.Default.Top;
this.Left = Properties.Settings.Default.Left;
this.Height = Properties.Settings.Default.Height;
this.Width = Properties.Settings.Default.Width;
// Very quick and dirty - but it does the job
if (Properties.Settings.Default.Maximized)
{
    WindowState = WindowState.Maximized;
}

создайте обработчик событий Window_Closing и добавьте следующее:

if (WindowState == WindowState.Maximized)
{
    // Use the RestoreBounds as the current values will be 0, 0 and the size of the screen
    Properties.Settings.Default.Top = RestoreBounds.Top;
    Properties.Settings.Default.Left = RestoreBounds.Left;
    Properties.Settings.Default.Height = RestoreBounds.Height;
    Properties.Settings.Default.Width = RestoreBounds.Width;
    Properties.Settings.Default.Maximized = true;
}
else
{
    Properties.Settings.Default.Top = this.Top;
    Properties.Settings.Default.Left = this.Left;
    Properties.Settings.Default.Height = this.Height;
    Properties.Settings.Default.Width = this.Width;
    Properties.Settings.Default.Maximized = false;
}

Properties.Settings.Default.Save();

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

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

<Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:WpfApplication1"
        Title="Window1"
        Height="{my:SettingBinding Height}"
        Width="{my:SettingBinding Width}"
        Left="{my:SettingBinding Left}"
        Top="{my:SettingBinding Top}">

вы можете найти код для данного расширения разметки : http://www.thomaslevesque.com/2008/11/18/wpf-binding-to-application-settings-using-a-markup-extension/

просто написал запись в блоге подробно, как это сделать простым и надежным способом. Он использует GetWindowPlacement и SetWindowPlacement функции, упомянутые Энди, но с некоторыми из странного поведения он упомянул очищены:

https://engy.us/blog/2010/03/08/saving-window-size-and-location-in-wpf-and-winforms/

вот некоторые различия с другими ответы:

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

в то время как вы можете "свернуть свой собственный" и вручную сохранить настройки где-то, и в целом это будет работать, очень легко не обрабатывать все случаи правильно. Гораздо лучше позволить ОС делать работу за вас, позвонив GetWindowPlacement() на выходе и SetWindowPlacement() при запуске. Он обрабатывает все сумасшедшие крайние случаи, которые могут произойти (несколько мониторов, сохранить нормальный размер окна, если он закрыт при максимальном увеличении и т. д.) чтобы вы этого не делали должен.

этот образец MSDN показывает, как использовать их с приложением WPF. Пример не идеален (окно начнется в верхнем левом углу как можно меньше при первом запуске, и есть некоторое странное поведение с конструктором настроек, сохраняющим значение типа WINDOWPLACEMENT), но он должен по крайней мере начать.

привязка "длинная форма", которую Томас опубликовал выше, почти не требует кодирования, просто убедитесь, что у вас есть привязка пространства имен:

<Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:p="clr-namespace:WpfApplication1.Properties"
        Title="Window1"
        Height="{Binding Source={x:Static p:Settings.Default}, Path=Height, Mode=TwoWay}"
        Width="{Binding Source={x:Static p:Settings.Default}, Path=Width, Mode=TwoWay}"
        Left="{Binding Source={x:Static p:Settings.Default}, Path=Left, Mode=TwoWay}"
        Top="{Binding Source={x:Static p:Settings.Default}, Path=Top, Mode=TwoWay}">

затем, чтобы сэкономить на коде:

private void frmMain_Closed(object sender, EventArgs e)
{
    Properties.Settings.Default.Save();
}

кроме того, вам может понравиться следующий подход (см. Источник). Добавьте класс WindowSettings в свой проект и вставьте WindowSettings.Save="True" в заголовке главного окна:

<Window x:Class="YOURPROJECT.Views.ShellView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Services="clr-namespace:YOURNAMESPACE.Services" 
    Services:WindowSettings.Save="True">

где WindowSettings определяется следующим образом:

using System;
using System.ComponentModel;
using System.Configuration;
using System.Windows;

namespace YOURNAMESPACE.Services
{
/// <summary>
///   Persists a Window's Size, Location and WindowState to UserScopeSettings
/// </summary>
public class WindowSettings
{
    #region Fields

    /// <summary>
    ///   Register the "Save" attached property and the "OnSaveInvalidated" callback
    /// </summary>
    public static readonly DependencyProperty SaveProperty = DependencyProperty.RegisterAttached("Save", typeof (bool), typeof (WindowSettings), new FrameworkPropertyMetadata(OnSaveInvalidated));

    private readonly Window mWindow;

    private WindowApplicationSettings mWindowApplicationSettings;

    #endregion Fields

    #region Constructors

    public WindowSettings(Window pWindow) { mWindow = pWindow; }

    #endregion Constructors

    #region Properties

    [Browsable(false)] public WindowApplicationSettings Settings {
        get {
            if (mWindowApplicationSettings == null) mWindowApplicationSettings = CreateWindowApplicationSettingsInstance();
            return mWindowApplicationSettings;
        }
    }

    #endregion Properties

    #region Methods

    public static void SetSave(DependencyObject pDependencyObject, bool pEnabled) { pDependencyObject.SetValue(SaveProperty, pEnabled); }

    protected virtual WindowApplicationSettings CreateWindowApplicationSettingsInstance() { return new WindowApplicationSettings(this); }

    /// <summary>
    ///   Load the Window Size Location and State from the settings object
    /// </summary>
    protected virtual void LoadWindowState() {
        Settings.Reload();
        if (Settings.Location != Rect.Empty) {
            mWindow.Left = Settings.Location.Left;
            mWindow.Top = Settings.Location.Top;
            mWindow.Width = Settings.Location.Width;
            mWindow.Height = Settings.Location.Height;
        }
        if (Settings.WindowState != WindowState.Maximized) mWindow.WindowState = Settings.WindowState;
    }

    /// <summary>
    ///   Save the Window Size, Location and State to the settings object
    /// </summary>
    protected virtual void SaveWindowState() {
        Settings.WindowState = mWindow.WindowState;
        Settings.Location = mWindow.RestoreBounds;
        Settings.Save();
    }

    /// <summary>
    ///   Called when Save is changed on an object.
    /// </summary>
    private static void OnSaveInvalidated(DependencyObject pDependencyObject, DependencyPropertyChangedEventArgs pDependencyPropertyChangedEventArgs) {
        var window = pDependencyObject as Window;
        if (window != null)
            if ((bool) pDependencyPropertyChangedEventArgs.NewValue) {
                var settings = new WindowSettings(window);
                settings.Attach();
            }
    }

    private void Attach() {
        if (mWindow != null) {
            mWindow.Closing += WindowClosing;
            mWindow.Initialized += WindowInitialized;
            mWindow.Loaded += WindowLoaded;
        }
    }

    private void WindowClosing(object pSender, CancelEventArgs pCancelEventArgs) { SaveWindowState(); }

    private void WindowInitialized(object pSender, EventArgs pEventArgs) { LoadWindowState(); }

    private void WindowLoaded(object pSender, RoutedEventArgs pRoutedEventArgs) { if (Settings.WindowState == WindowState.Maximized) mWindow.WindowState = Settings.WindowState; }

    #endregion Methods

    #region Nested Types

    public class WindowApplicationSettings : ApplicationSettingsBase
    {
        #region Constructors

        public WindowApplicationSettings(WindowSettings pWindowSettings) { }

        #endregion Constructors

        #region Properties

        [UserScopedSetting] public Rect Location {
            get {
                if (this["Location"] != null) return ((Rect) this["Location"]);
                return Rect.Empty;
            }
            set { this["Location"] = value; }
        }

        [UserScopedSetting] public WindowState WindowState {
            get {
                if (this["WindowState"] != null) return (WindowState) this["WindowState"];
                return WindowState.Normal;
            }
            set { this["WindowState"] = value; }
        }

        #endregion Properties
    }

    #endregion Nested Types
}
}

Это наиболее элегантное решение, которое я видел в интернете. Проверьте это:

http://blogs.msdn.com/b/davidrickard/archive/2010/03/09/saving-window-size-and-location-in-wpf-and-winforms.aspx

Он работает для WPF и для WinForms тоже.

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

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

библиотека называется Jot (github), вот статья CodeProject я писал об этом.

вот как вы будете использовать его, чтобы отслеживать размер и расположение окна:

public MainWindow()
{
    InitializeComponent();

    _stateTracker.Configure(this)
        .IdentifyAs("MyMainWindow")
        .AddProperties(nameof(Height), nameof(Width), nameof(Left), nameof(Top), nameof(WindowState))
        .RegisterPersistTrigger(nameof(Closed))
        .Apply();
}

записать файлы и параметры: С Jot есть значительно меньше кода, и это намного меньше ошибок, так как вам нужно только упомянуть каждое свойство после. С файлами настроек вам нужно упомянуть каждое свойство 5 раз: один раз, когда вы явно создаете свойство и дополнительные четыре раза в коде, который копирует значения туда и обратно.

хранение, сериализация и т. д. полностью настраиваются. Кроме того, при использовании IOC вы даже можете подключить его, чтобы он автоматически применял отслеживание ко всем объектам, которые он разрешает, так что все, что вам нужно сделать, чтобы сделать свойство постоянным, - это пощечина [отслеживаемого] атрибута на нем.

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

Я написал небольшой класс, который делает это. Вот как это называется:

    public MainWindow()
    {
        FormSizeSaver.RegisterForm(this, () => Settings.Default.MainWindowSettings,
                                   s =>
                                   {
                                       Settings.Default.MainWindowSettings = s;
                                       Settings.Default.Save();
                                   });
        InitializeComponent();
        ...

и вот код:

public class FormSizeSaver
{
    private readonly Window window;
    private readonly Func<FormSizeSaverSettings> getSetting;
    private readonly Action<FormSizeSaverSettings> saveSetting;
    private FormSizeSaver(Window window, Func<string> getSetting, Action<string> saveSetting)
    {
        this.window = window;
        this.getSetting = () => FormSizeSaverSettings.FromString(getSetting());
        this.saveSetting = s => saveSetting(s.ToString());

        window.Initialized += InitializedHandler;
        window.StateChanged += StateChangedHandler;
        window.SizeChanged += SizeChangedHandler;
        window.LocationChanged += LocationChangedHandler;
    }

    public static FormSizeSaver RegisterForm(Window window, Func<string> getSetting, Action<string> saveSetting)
    {
        return new FormSizeSaver(window, getSetting, saveSetting);
    }


    private void SizeChangedHandler(object sender, SizeChangedEventArgs e)
    {
        var s = getSetting();
        s.Height = e.NewSize.Height;
        s.Width = e.NewSize.Width;
        saveSetting(s);
    }

    private void StateChangedHandler(object sender, EventArgs e)
    {
        var s = getSetting();
        if (window.WindowState == WindowState.Maximized)
        {
            if (!s.Maximized)
            {
                s.Maximized = true;
                saveSetting(s);
            }
        }
        else if (window.WindowState == WindowState.Normal)
        {
            if (s.Maximized)
            {
                s.Maximized = false;
                saveSetting(s);
            }
        }
    }

    private void InitializedHandler(object sender, EventArgs e)
    {
        var s = getSetting();
        window.WindowState = s.Maximized ? WindowState.Maximized : WindowState.Normal;

        if (s.Height != 0 && s.Width != 0)
        {
            window.Height = s.Height;
            window.Width = s.Width;
            window.WindowStartupLocation = WindowStartupLocation.Manual;
            window.Left = s.XLoc;
            window.Top = s.YLoc;
        }
    }

    private void LocationChangedHandler(object sender, EventArgs e)
    {
        var s = getSetting();
        s.XLoc = window.Left;
        s.YLoc = window.Top;
        saveSetting(s);
    }
}

[Serializable]
internal class FormSizeSaverSettings
{
    public double Height, Width, YLoc, XLoc;
    public bool Maximized;

    public override string ToString()
    {
        using (var ms = new MemoryStream())
        {
            var bf = new BinaryFormatter();
            bf.Serialize(ms, this);
            ms.Position = 0;
            byte[] buffer = new byte[(int)ms.Length];
            ms.Read(buffer, 0, buffer.Length);
            return Convert.ToBase64String(buffer);
        }
    }

    internal static FormSizeSaverSettings FromString(string value)
    {
        try
        {
            using (var ms = new MemoryStream(Convert.FromBase64String(value)))
            {
                var bf = new BinaryFormatter();
                return (FormSizeSaverSettings) bf.Deserialize(ms);
            }
        }
        catch (Exception)
        {
            return new FormSizeSaverSettings();
        }
    }
}

Вам может понравиться это:

public class WindowStateHelper
{
    public static string ToXml(System.Windows.Window win)
    {
        XElement bounds = new XElement("Bounds");
        if (win.WindowState == System.Windows.WindowState.Maximized)
        {
            bounds.Add(new XElement("Top", win.RestoreBounds.Top));
            bounds.Add(new XElement("Left", win.RestoreBounds.Left));
            bounds.Add(new XElement("Height", win.RestoreBounds.Height));
            bounds.Add(new XElement("Width", win.RestoreBounds.Width));
        }
        else
        {
            bounds.Add(new XElement("Top", win.Top));
            bounds.Add(new XElement("Left", win.Left));
            bounds.Add(new XElement("Height", win.Height));
            bounds.Add(new XElement("Width", win.Width));
        }
        XElement root = new XElement("WindowState",
            new XElement("State", win.WindowState.ToString()),
            new XElement("Visibility", win.Visibility.ToString()),
            bounds);

        return root.ToString();
    }

    public static void FromXml(string xml, System.Windows.Window win)
    {
        try
        {
            XElement root = XElement.Parse(xml);
            string state = root.Descendants("State").FirstOrDefault().Value;
            win.WindowState = (System.Windows.WindowState)Enum.Parse(typeof(System.Windows.WindowState), state);

            state = root.Descendants("Visibility").FirstOrDefault().Value;
            win.Visibility = (System.Windows.Visibility)Enum.Parse(typeof(System.Windows.Visibility), state);

            XElement bounds = root.Descendants("Bounds").FirstOrDefault();
            win.Top = Convert.ToDouble(bounds.Element("Top").Value);
            win.Left = Convert.ToDouble(bounds.Element("Left").Value);
            win.Height = Convert.ToDouble(bounds.Element("Height").Value);
            win.Width = Convert.ToDouble(bounds.Element("Width").Value);
        }
        catch (Exception x)
        {
            System.Console.WriteLine(x.ToString());
        }
    }
}

когда приложение закрывается:

        Properties.Settings.Default.Win1Placement = WindowStateHelper.ToXml(win1);
        Properties.Settings.Default.Win2Placement = WindowStateHelper.ToXml(win2);
        ...

когда приложение запускается:

        WindowStateHelper.FromXml(Properties.Settings.Default.Win1Placement, win1);
        WindowStateHelper.FromXml(Properties.Settings.Default.Win2Placement, win2);
        ...

создайте строку с именем WindowXml в настройках по умолчанию.

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

using YourProject.Properties;
using System;
using System.Linq;
using System.Windows;
using System.Xml.Linq;

namespace YourProject.Extensions
{
    public static class WindowExtensions
    {
        public static void SaveSizeAndLocation(this Window w)
        {
            try
            {
                var s = "<W>";
                s += GetNode("Top", w.WindowState == WindowState.Maximized ? w.RestoreBounds.Top : w.Top);
                s += GetNode("Left", w.WindowState == WindowState.Maximized ? w.RestoreBounds.Left : w.Left);
                s += GetNode("Height", w.WindowState == WindowState.Maximized ? w.RestoreBounds.Height : w.Height);
                s += GetNode("Width", w.WindowState == WindowState.Maximized ? w.RestoreBounds.Width : w.Width);
                s += GetNode("WindowState", w.WindowState);
                s += "</W>";

                Settings.Default.WindowXml = s;
                Settings.Default.Save();
            }
            catch (Exception)
            {
            }
        }

        public static void RestoreSizeAndLocation(this Window w)
        {
            try
            {
                var xd = XDocument.Parse(Settings.Default.WindowXml);
                w.WindowState = (WindowState)Enum.Parse(typeof(WindowState), xd.Descendants("WindowState").FirstOrDefault().Value);
                w.Top = Convert.ToDouble(xd.Descendants("Top").FirstOrDefault().Value);
                w.Left = Convert.ToDouble(xd.Descendants("Left").FirstOrDefault().Value);
                w.Height = Convert.ToDouble(xd.Descendants("Height").FirstOrDefault().Value);
                w.Width = Convert.ToDouble(xd.Descendants("Width").FirstOrDefault().Value);
            }
            catch (Exception)
            {
            }
        }

        private static string GetNode(string name, object value)
        {
            return string.Format("<{0}>{1}</{0}>", name, value);
        }
    }
}

здесь NuGet Project RestoreWindowPlace смотрите дальше github что делает все это для вас, сохраняя информацию в XML-файле.

чтобы заставить его работать на окне, это так же просто, как вызов:

((App)Application.Current).WindowPlace.Register(this, "MainWindow");

в приложении вы создаете класс, который управляет вашими окнами. См. ссылку github выше для получения дополнительной информации.

Я использую ответ от Лэнса Кливленда и привязываю настройку. Но я использую еще немного кода, чтобы избежать того, что мое окно находится вне экрана.

private void SetWindowSettingsIntoScreenArea()
{
    // first detect Screen, where we will display the Window
    // second correct bottom and right position
    // then the top and left position.
    // If Size is bigger than current Screen, it's still possible to move and size the Window

    // get the screen to display the window
    var screen = System.Windows.Forms.Screen.FromPoint(new System.Drawing.Point((int)Default.Left, (int)Default.Top));

    // is bottom position out of screen for more than 1/3 Height of Window?
    if (Default.Top + (Default.Height / 3) > screen.WorkingArea.Height)
        Default.Top = screen.WorkingArea.Height - Default.Height;

    // is right position out of screen for more than 1/2 Width of Window?
    if (Default.Left + (Default.Width / 2) > screen.WorkingArea.Width)
        Default.Left = screen.WorkingArea.Width - Default.Width;

    // is top position out of screen?
    if (Default.Top < screen.WorkingArea.Top)
        Default.Top = screen.WorkingArea.Top;

    // is left position out of screen?
    if (Default.Left < screen.WorkingArea.Left)
        Default.Left = screen.WorkingArea.Left;
}