C# .NET WPF 记住会话之间的窗口大小

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/847752/
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-05 04:00:12  来源:igfitidea点击:

.NET WPF Remember window size between sessions

c#.netwpfwindows

提问by Daniil Harik

Basically when user resizes my application's window I want application to be same size when application is re-opened again.

基本上,当用户调整我的应用程序窗口的大小时,我希望应用程序再次重新打开时应用程序的大小相同。

At first I though of handling SizeChanged event and save Height and Width, but I think there must be easier solution.

起初我虽然处理 SizeChanged 事件并保存高度和宽度,但我认为必须有更简单的解决方案。

Pretty simple problem, but I can not find easy solution to it.

很简单的问题,但我找不到简单的解决方案。

采纳答案by ChrisF

Save the values in the user.config file.

将值保存在 user.config 文件中。

You'll need to create the value in the settings file - it should be in the Properties folder. Create five values:

您需要在设置文件中创建值 - 它应该在 Properties 文件夹中。创建五个值:

  • Topof type double
  • Leftof type double
  • Heightof type double
  • Widthof type double
  • Maximizedof type bool- to hold whether the window is maximized or not. If you want to store more information then a different type or structure will be needed.
  • Top类型 double
  • Left类型 double
  • Height类型 double
  • Width类型 double
  • Maximized类型bool- 保存窗口是否最大化。如果要存储更多信息,则需要不同的类型或结构。

Initialise the first two to 0 and the second two to the default size of your application, and the last one to false.

将前两个初始化为 0,将后两个初始化为应用程序的默认大小,最后一个为 false。

Create a Window_OnSourceInitialized event handler and add the following:

创建一个 Window_OnSourceInitialized 事件处理程序并添加以下内容:

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

NOTE:The set window placement needs to go in the on source initialised event of the window not the constructor, otherwise if you have the window maximised on a second monitor, it will always restart maximised on the primary monitor and you won't be able to access it.

注意:设置窗口放置需要进入窗口的源初始化事件而不是构造函数,否则如果您在第二台显示器上最大化窗口,它将始终在主显示器上重新启动最大化并且您将无法访问它。

Create a Window_Closing event handler and add the following:

创建一个 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();

This will fail if the user makes the display area smaller - either by disconnecting a screen or changing the screen resolution - while the application is closed so you should add a check that the desired location and size is still valid before applying the values.

如果用户在应用程序关闭时使显示区域变小(通过断开屏幕连接或更改屏幕分辨率),这将失败,因此您应该在应用值之前添加检查所需的位置和大小仍然有效。

回答by Thomas Levesque

Actually you don't need to use code-behind to do that (except for saving the settings). You can use a custom markup extension to bind the window size and position to the settings like this :

实际上,您不需要使用代码隐藏来执行此操作(保存设置除外)。您可以使用自定义标记扩展将窗口大小和位置绑定到如下设置:

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

You can find the code for this markup extension here : http://www.thomaslevesque.com/2008/11/18/wpf-binding-to-application-settings-using-a-markup-extension/

您可以在此处找到此标记扩展的代码:http: //www.thomaslevesque.com/2008/11/18/wpf-binding-to-application-settings-using-a-markup-extension/

回答by Andy

While you can "roll your own" and manually save the settings somewhere, and in general it will work, it is very easy to not handle all of the cases correctly. It is much better to let the OS do the work for you, by calling GetWindowPlacement()at exit and SetWindowPlacement()at startup. It handles all of the crazy edge cases that can occur (multiple monitors, save the normal size of the window if it is closed while maximized, etc.) so that you don't have to.

虽然您可以“自己动手”并手动将设置保存在某处,并且通常会起作用,但很容易无法正确处理所有情况。通过在退出时调用GetWindowPlacement()和在启动时调用SetWindowPlacement(),让操作系统为您完成工作要好得多。它处理所有可能发生的疯狂边缘情况(多显示器,如果窗口在最大化时关闭,则保存窗口的正常大小等),因此您不必这样做。

This MSDN Sampleshows how to use these with a WPF app. The sample isn't perfect (the window will start in the upper left corner as small as possible on first run, and there is some odd behavior with the Settings designer saving a value of type WINDOWPLACEMENT), but it should at least get you started.

此 MSDN 示例展示了如何将这些与 WPF 应用程序一起使用。该示例并不完美(第一次运行时,窗口将从左上角开始,尽可能小,并且设置设计器保存 type 值时有一些奇怪的行为WINDOWPLACEMENT),但它至少应该让您开始。

回答by RandomEngy

Just wrote a blog entry detailing how to do this in a simple and robust manner. It uses the GetWindowPlacementand SetWindowPlacementfunctions mentioned by Andy, but with some of the odd behavior he mentioned cleaned up:

刚刚写了一篇博客文章,详细介绍了如何以简单而强大的方式做到这一点。它使用了Andy 提到的GetWindowPlacementSetWindowPlacement函数,但他提到了一些奇怪的行为:

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

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

Here are some differences with other answers:

以下是与其他答案的一些差异:

  • This code is aware of monitor changes, so it knows to move a window when a monitor is disconnected (so it's not stranded off screen).
  • It can also save the normal size of the window even when it's maximized, so it knows what to restore to when un-maximized.
  • It also consolidates the window state into a single string, which is easier to persist.
  • Code defines a class that can be used on one or more windows in your project by just hooking two events. Thus, it does not clutter any of your project files.
  • 此代码知道监视器更改,因此它知道在监视器断开连接时移动窗口(因此它不会被困在屏幕外)。
  • 即使在最大化时,它也可以保存窗口的正常大小,因此它知道在未最大化时恢复到什么。
  • 它还将窗口状态合并为一个字符串,这样更容易持久化。
  • 代码定义了一个类,只需挂钩两个事件即可在项目中的一个或多个窗口上使用该类。因此,它不会弄乱您的任何项目文件。

回答by Lance Cleveland

The "long form" binding that Thomas posted above requires almost no coding, just make sure you have the namespace binding:

Thomas 上面发布的“长格式”绑定几乎不需要编码,只需确保您具有命名空间绑定:

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

Then to save on the code-behind:

然后保存代码隐藏:

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

回答by paul

You might like this:

你可能喜欢这个:

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

When the app closes:

当应用程序关闭时:

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

When the app starts:

当应用程序启动时:

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

回答by Erik Vullings

Alternatively, you might like the following approach too (see source). Add the WindowSettings class to your project and insert WindowSettings.Save="True"in your main window's header:

或者,您可能也喜欢以下方法(请参阅源代码)。将 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">

Where WindowSettings is defined as follows:

其中 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
}
}

回答by Alexandru Dicu

This is the most elegant solution that I've seen over the internet. Check this out:

这是我在互联网上看到的最优雅的解决方案。看一下这个:

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

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

It works for WPF and for WinForms too.

它也适用于 WPF 和 WinForms。

回答by tster

I wrote a quick class which does this. Here is how it's called:

我写了一个快速课程来做到这一点。这是它的名字:

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

And here is the code:

这是代码:

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

回答by anakic

The default way of solving it is to use settings files. The problem with settings files is that you have to define all the settings and write the code that copies data back and forth yourself. Quite tedious if you have a lot of properties to keep track of.

解决它的默认方法是使用设置文件。设置文件的问题在于您必须定义所有设置并自己编写来回复制数据的代码。如果您要跟踪很多属性,则非常乏味。

I made a pretty flexible and very easy to use library for this, you just tell it which properties of which object to track and it does the rest. You can configure the crap out of it too if you like.

我为此创建了一个非常灵活且易于使用的库,您只需告诉它要跟踪哪个对象的哪些属性,剩下的就交给它了。如果你愿意,你也可以配置它。

The library is called Jot (github), here is an old CodeProject articleI wrote about it.

该库名为Jot (github),这是我写的一篇关于它的旧CodeProject 文章

Here's how you'd use it to keep track of a window's size and location:

以下是如何使用它来跟踪窗口的大小和位置:

public MainWindow()
{
    InitializeComponent();

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

Jot vs. settings files:With Jot there's considerably less code, and it's a lot less error prone since you only need to mention each property once. With settings files you need to mention each property 5 times: once when you explicitly create the property and an additional four times in the code that copies the values back and forth.

Jot 与设置文件:使用 Jot 的代码要少得多,而且不容易出错,因为您只需要提及每个属性一次。对于设置文件,您需要提及每个属性5 次:一次是在您明确创建属性时,另外四次是在来回复制值的代码中。

Storage, serialization etc are completely configurable. Also, when using IOC, you can even hook it up so that it applies tracking automatically to all objects it resolves so that all you need to do to make a property persistent is slap a [Trackable] attribute on it.

存储、序列化等是完全可配置的。此外,在使用 IOC 时,您甚至可以将其连接起来,以便它自动将跟踪应用到它解析的所有对象,以便使属性持久化所需要做的就是在其上添加 [Trackable] 属性。

I'm writing all this because I think the library is top notch and I want to mouth off about it.

我写这一切是因为我认为图书馆是一流的,我想说出来。