记住窗口位置、大小和状态 [on Win + Arrow 对齐](使用多个显示器)

Posted

技术标签:

【中文标题】记住窗口位置、大小和状态 [on Win + Arrow 对齐](使用多个显示器)【英文标题】:Remember Window position, size and state [on Win + Arrow alignment] (with multiple monitors) 【发布时间】:2015-03-06 10:54:18 【问题描述】:

在我们的项目中,我们保存了窗口大小、位置和最小化/最大化设置,因此我们可以在重新打开窗口时以完全相同的位置和大小打开窗口。使用本文底部的Window-Behavior-class,所有这些都运行良好。

然而,问题是当我们使用 Win 按钮 + 箭头时;这会将屏幕与屏幕的一侧对齐,但这未正确保存在行为中。相反,它保存了我使用Win +箭头对齐之前屏幕的位置和大小,也就是它再次打开的位置。

我尝试在SaveWindowState方法中使用Window的LeftTopActualWidthActualHeight(注意:这个方法中的AssociatedObject是Window。)但是LeftTop 似乎偏离了大约 20-40 像素,并且使用 ActualWidthActualHeight 和当前屏幕宽度/高度(使用多个显示器时)保存左右也有点痛。

那么,当用户使用Win +箭头对齐窗口然后关闭它时,有什么方法可以在窗口设置中保存正确的位置和大小?

WindowSettingsBehavior:

using System;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Interop;

namespace NatWa.MidOffice.Behaviors

    /// <summary>
    /// Persists a Window's Size, Location and WindowState to UserScopeSettings 
    /// </summary>
    public class WindowSettingsBehavior : Behavior<Window>
    
        [DllImport("user32.dll")]
        static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref Windowplacement lpwndpl);

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

        // ReSharper disable InconsistentNaming
        const int SW_SHOWNORMAL = 1;
        const int SW_SHOWMINIMIZED = 2;
        // ReSharper restore InconsistentNaming

        internal class WindowApplicationSettings : ApplicationSettingsBase
        
            public WindowApplicationSettings(WindowSettingsBehavior windowSettingsBehavior)
                : base(windowSettingsBehavior.AssociatedObject.GetType().FullName)
            
            

            [UserScopedSetting]
            public Windowplacement? Placement
            
                get
                
                    if (this["Placement"] != null)
                    
                        return ((Windowplacement)this["Placement"]);
                    
                    return null;
                
                set
                
                    this["Placement"] = value;
                
            
        

        /// <summary>
        /// Load the Window Size Location and State from the settings object
        /// </summary>
        private void LoadWindowState()
        
            Settings.Reload();

            if (Settings.Placement == null) return;
            try
            
                // Load window placement details for previous application session from application settings.
                // If window was closed on a monitor that is now disconnected from the computer,
                // SetWindowPlacement will place the window onto a visible monitor.
                var wp = Settings.Placement.Value;

                wp.length = Marshal.SizeOf(typeof(Windowplacement));
                wp.flags = 0;
                wp.showCmd = (wp.showCmd == SW_SHOWMINIMIZED ? SW_SHOWNORMAL : wp.showCmd);
                var hwnd = new WindowInteropHelper(AssociatedObject).Handle;
                SetWindowPlacement(hwnd, ref wp);
            
            catch (Exception ex)
            
                Debug.WriteLine("Failed to load window state:\r\n0", ex);
            
        

        /// <summary>
        /// Save the Window Size, Location and State to the settings object
        /// </summary>
        private void SaveWindowState()
        
            Windowplacement wp;
            var hwnd = new WindowInteropHelper(AssociatedObject).Handle;

            GetWindowPlacement(hwnd, out wp);
            Settings.Placement = wp;
            Settings.Save();
        

        protected override void OnAttached()
        
            base.OnAttached();
            AssociatedObject.Closing += WindowClosing;
            AssociatedObject.SourceInitialized += WindowSourceInitialized;
        

        private void WindowSourceInitialized(object sender, EventArgs e)
        
            LoadWindowState();
        

        private void WindowClosing(object sender, CancelEventArgs e)
        
            SaveWindowState();
            AssociatedObject.Closing -= WindowClosing;
            AssociatedObject.SourceInitialized -= WindowSourceInitialized;
        

        private WindowApplicationSettings _windowApplicationSettings;

        internal virtual WindowApplicationSettings CreateWindowApplicationSettingsInstance()
        
            return new WindowApplicationSettings(this);
        

        [Browsable(false)]
        internal WindowApplicationSettings Settings
        
            get  return _windowApplicationSettings
                ?? (_windowApplicationSettings = CreateWindowApplicationSettingsInstance()); 
        
    

    #region Save position classes

    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct Rect
    
        private int _left;
        private int _top;
        private int _right;
        private int _bottom;

        public Rect(int left, int top, int right, int bottom)
        
            _left = left;
            _top = top;
            _right = right;
            _bottom = bottom;
        

        public override bool Equals(object obj)
        
            if (!(obj is Rect)) return base.Equals(obj);

            var rect = (Rect)obj;
            return rect._bottom == _bottom &&
                   rect._left == _left &&
                   rect._right == _right &&
                   rect._top == _top;
        

        public override int GetHashCode()
        
            return _bottom.GetHashCode() ^
                   _left.GetHashCode() ^
                   _right.GetHashCode() ^
                   _top.GetHashCode();
        

        public static bool operator ==(Rect a, Rect b)
        
            return a._bottom == b._bottom &&
                   a._left == b._left &&
                   a._right == b._right &&
                   a._top == b._top;
        

        public static bool operator !=(Rect a, Rect b)
        
            return !(a == b);
        

        public int Left
        
            get  return _left; 
            set  _left = value; 
        

        public int Top
        
            get  return _top; 
            set  _top = value; 
        

        public int Right
        
            get  return _right; 
            set  _right = value; 
        

        public int Bottom
        
            get  return _bottom; 
            set  _bottom = value; 
        
    

    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct Point
    
        private int _x;
        private int _y;

        public Point(int x, int y)
        
            _x = x;
            _y = y;
        

        public int X
        
            get  return _x; 
            set  _x = value; 
        

        public int Y
        
            get  return _y; 
            set  _y = value; 
        

        public override bool Equals(object obj)
        
            if (!(obj is Point)) return base.Equals(obj);
            var point = (Point)obj;

            return point._x == _x && point._y == _y;
        

        public override int GetHashCode()
        
            return _x.GetHashCode() ^ _y.GetHashCode();
        

        public static bool operator ==(Point a, Point b)
        
            return a._x == b._x && a._y == b._y;
        

        public static bool operator !=(Point a, Point b)
        
            return !(a == b);
        
    

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

    #endregion

【问题讨论】:

想知道为什么要使用 user32.dll 导入,而不是直接访问窗口实例? 是的,Aero Snap 非常古怪。最大的问题是它没有通知,也没有办法用winapi捕捉,窗口位置不会更新。请注意 Get/SetWindowPlacement 使用的奇怪坐标系,它会减去任务栏。最好完全避免。 【参考方案1】:

您是否尝试过 System.Windows.Window 实例而不是 p/invoke ? 我使用两个简单的方法来保存和使用这个类设置窗口位置,它可以在不同的应用程序、架构、客户端、Windows 操作系统上完美运行,无论是否有 Aero...

void SetWindowPosition()

    this.Left = Settings.Default.WindowPositionLeft;
    this.Top = Settings.Default.WindowPositionTop;

void SaveWindowPosition()

    Settings.Default.WindowPositionTop = this.Top;
    Settings.Default.WindowPositionLeft = this.Left;
    Settings.Default.Save();

还是我错过了什么?

【讨论】:

"或者我错过了什么?" 老实说,我完全不知道我有/没有尝试过什么。我在 2.5 多年前发布了这个问题。 ;) 目前我什至不再每天使用 .NET C# 进行开发。 我总是忘记检查线程日期:D 那么在你的新项目中玩得开心!

以上是关于记住窗口位置、大小和状态 [on Win + Arrow 对齐](使用多个显示器)的主要内容,如果未能解决你的问题,请参考以下文章

QQ软件是不是支持记住窗口的大小和位置?

windows客户端开发--使你的客户端运行时记住上次关闭的大小和位置

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

java实现小球碰撞反弹

电脑不能记忆窗口大小和位置

XP下资源管理器无法正常记忆窗口大小和位置,求解。