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

Dan*_*rik 87 .net c# windows wpf

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

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

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

Chr*_*isF 114

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

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

  • 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;
}
Run Code Online (Sandbox Code Playgroud)

创建一个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();
Run Code Online (Sandbox Code Playgroud)

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

  • 实际上你可以在设置中添加"WindowState".选择类型 - >浏览 - > PresentationFramework - > System.Windows - > WindowState :) (7认同)
  • 此代码中存在一个错误,如果用户在他/她的第二个屏幕上打开窗口,然后将该屏幕与计算机断开连接,则下次打开窗口时,它将显示在屏幕外.如果窗口是模态的,则用户根本无法与应用程序交互,也无法理解正在发生的事情.在[将屏幕坐标转换为DPI相关值后],您需要使用Window.GetScreen()添加边界检查.(http://stackoverflow.com/questions/2633759/ensuring-wpf-window-is-inside-screen -bounds) (7认同)
  • 是的@AlanBaljeu是对的,这对于具有多个显示器的现代设置不起作用._only_正确的解决方案是使用这个http://blogs.msdn.com/b/davidrickard/archive/2010/03/09/saving-window-size-and-location-in-wpf-and-winforms.aspx (6认同)
  • 实际上,范围为"User"的设置不会保存在Program Files的app.config文件中,而是保存在用户应用程序数据目录的user.config文件中.所以这不是问题...... (5认同)
  • FWIW,在应用程序崩溃的情况下,我也会从大小更改处理程序执行此操作.对于未处理的异常处理,它们很少见,但是为什么在神秘地发生时惩罚丢失大小/位置的用户. (2认同)
  • @OmerRaviv - 这不是一个bug,而是一个限制:)严重 - 我没有解决问题的这个方面. (2认同)

Tho*_*que 71

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

<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}">
Run Code Online (Sandbox Code Playgroud)

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

  • +1 - 我喜欢使用绑定和扩展!如果将WindowState添加到绑定设置,它将提供完整功能.或者,如果您在DataContext中具有可用的用户设置,则可以使用诸如"{Binding Settings.Height}"之类的内容. (6认同)
  • 我更喜欢这个答案而不是所选择的答案.做得好. (4认同)
  • 当人们有两个监视器,因此它可能有负坐标,然后他们更改监视器配置并且值不再有效时怎么办? (4认同)

Ran*_*ngy 41

刚刚写了一篇博客文章,详细介绍了如何以简单而强大的方式完成此任务.它使用Andy提到的GetWindowPlacementSetWindowPlacement函数,但是他提到的一些奇怪的行为被清理了:

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

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

  • 此代码知道监视器的更改,因此它知道在监视器断开连接时移动窗口(因此它不会被屏幕关闭).
  • 即使窗口最大化,它也可以保存窗口的正常大小,因此它知道在未最大化时要恢复的内容.
  • 它还将窗口状态合并为单个字符串,这更容易持久化.
  • 代码定义了一个类,只需挂钩两个事件就可以在项目的一个或多个窗口中使用它.因此,它不会使任何项目文件混乱.

  • @Gerard如果您在博客条目指示中使用`Closing`事件,您仍然可以访问窗口句柄. (2认同)
  • 好的解决方案 但是我刚刚发现GetWindowPlacement/SetWindowPlacement [不知道Aero Snap](https://social.msdn.microsoft.com/Forums/vstudio/en-US/1c3b7b79-8bb7-4824-95f9-e70f215e9313/remembering-window-展示位置和-航空卡?论坛= WPF) (2认同)

And*_*ndy 33

虽然你可以"自己动手"并在某个地方手动保存设置,但一般情况下它会起作用,很容易无法正确处理所有情况.通过在exit处调用GetWindowPlacement()并在启动时调用SetWindowPlacement(),让操作系统为您完成工作要好得多.它可以处理所有可能发生的疯狂边缘情况(多个监视器,如果窗口的正常大小保持最大化时保存,等等),这样你就不必这样做了.

此MSDN示例显示如何在WPF应用程序中使用它们.样本不完美(窗口将在第一次运行时在左上角开始尽可能小,并且设置设计器保存类型的值有一些奇怪的行为WINDOWPLACEMENT),但它至少应该让你开始.


Lan*_*and 27

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}">
Run Code Online (Sandbox Code Playgroud)

然后保存代码隐藏:

private void frmMain_Closed(object sender, EventArgs e)
{
    Properties.Settings.Default.Save();
}
Run Code Online (Sandbox Code Playgroud)

  • +1我也用过这个,@ DavidSykes - 为窗口状态添加另一个设置似乎运行得很好,例如`WindowState ="{Binding Source = {x:Static properties:Settings.Default},Path = WindowState,Mode = TwoWay }"` (7认同)

Eri*_*ngs 5

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

其中 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
}
}
Run Code Online (Sandbox Code Playgroud)


Chu*_*age 5

github 上有一个NuGet 项目 RestoreWindowPlace可以为您完成所有这些操作,并将信息保存在 XML 文件中。

要让它在窗口上工作,只需调用以下命令即可:

((App)Application.Current).WindowPlace.Register(this);

在应用程序中,您创建管理窗口的类。有关更多信息,请参阅上面的 github 链接。

  • 这个包工作得很好,它还可以正确处理最小化的窗口和断开连接的显示器。恕我直言,这应该是公认的答案。 (2认同)

小智 5

我根据 RandomEngys 的精彩答案提出了一个更通用的解决方案。它将位置保存到正在运行的文件夹中的文件中,并且您不需要为创建的每个新窗口创建新属性。这个解决方案对我来说非常有用,代码隐藏中的代码最少。

using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Interop;
using System.Xml;
using System.Xml.Serialization;

namespace WindowPlacementNameSpace
{

    // RECT structure required by WINDOWPLACEMENT structure
    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;

        public RECT(int left, int top, int right, int bottom)
        {
            this.Left = left;
            this.Top = top;
            this.Right = right;
            this.Bottom = bottom;
        }
    }

    // POINT structure required by WINDOWPLACEMENT structure
    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
        public int X;
        public int Y;

        public POINT(int x, int y)
        {
            this.X = x;
            this.Y = y;
        }
    }

    // WINDOWPLACEMENT stores the position, size, and state of a window
    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct WINDOWPLACEMENT
    {
        public int length;
        public int flags;
        public int showCmd;
        public POINT minPosition;
        public POINT maxPosition;
        public RECT normalPosition;
    }

    public static class WindowPlacement
    {
        private static readonly Encoding Encoding = new UTF8Encoding();
        private static readonly XmlSerializer Serializer = new XmlSerializer(typeof(WINDOWPLACEMENT));

        [DllImport("user32.dll")]
        private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);

        [DllImport("user32.dll")]
        private static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);

        private const int SW_SHOWNORMAL = 1;
        private const int SW_SHOWMINIMIZED = 2;

        private static void SetPlacement(IntPtr windowHandle, string placementXml)
        {
            if (string.IsNullOrEmpty(placementXml))
            {
                return;
            }

            byte[] xmlBytes = Encoding.GetBytes(placementXml);

            try
            {
                WINDOWPLACEMENT placement;
                using (MemoryStream memoryStream = new MemoryStream(xmlBytes))
                {
                    placement = (WINDOWPLACEMENT)Serializer.Deserialize(memoryStream);
                }

                placement.length = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
                placement.flags = 0;
                placement.showCmd = (placement.showCmd == SW_SHOWMINIMIZED ? SW_SHOWNORMAL : placement.showCmd);
                SetWindowPlacement(windowHandle, ref placement);
            }
            catch (InvalidOperationException)
            {
                // Parsing placement XML failed. Fail silently.
            }
        }

        private static string GetPlacement(IntPtr windowHandle)
        {
            WINDOWPLACEMENT placement;
            GetWindowPlacement(windowHandle, out placement);

            using (MemoryStream memoryStream = new MemoryStream())
            {
                using (XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8))
                {
                    Serializer.Serialize(xmlTextWriter, placement);
                    byte[] xmlBytes = memoryStream.ToArray();
                    return Encoding.GetString(xmlBytes);
                }
            }
        }
        public static void ApplyPlacement(this Window window)
        {
            var className = window.GetType().Name;
            try
            {
                var pos = File.ReadAllText(Directory + "\\" + className + ".pos");
                SetPlacement(new WindowInteropHelper(window).Handle, pos);
            }
            catch (Exception exception)
            {
                Log.Error("Couldn't read position for " + className, exception);
            }

        }

        public static void SavePlacement(this Window window)
        {
            var className = window.GetType().Name;
            var pos =  GetPlacement(new WindowInteropHelper(window).Handle);
            try
            {
                File.WriteAllText(Directory + "\\" + className + ".pos", pos);
            }
            catch (Exception exception)
            {
                Log.Error("Couldn't write position for " + className, exception);
            }
        }
        private static string Directory => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

    }
}
Run Code Online (Sandbox Code Playgroud)

在后面的代码中添加这两个方法

///This method is save the actual position of the window to file "WindowName.pos"
private void ClosingTrigger(object sender, EventArgs e)
{
    this.SavePlacement();
}
///This method is load the actual position of the window from the file
protected override void OnSourceInitialized(EventArgs e)
{
    base.OnSourceInitialized(e);
    this.ApplyPlacement();
}
Run Code Online (Sandbox Code Playgroud)

在 xaml 窗口中添加此内容

Closing="ClosingTrigger"
Run Code Online (Sandbox Code Playgroud)