Remember Window position, size and state [on Win + Arrow alignment] (with multiple monitors)
Asked Answered
L

1

10

In our project we save the Window Size, Position and Minimized/Maximized Settings, so we can open the window at the exact same spot and size when we re-open it. All this is working quite well, using the Window-Behavior-class found at the bottom of this post.

The problem however, is when we use the Win-button + an arrow; This aligns the screen to the side of the screen, but this isn't correctly saved in the behavior. Instead, it saves the position and size of the screen before I used the Win + arrow to align it, and that is the position it is opened again.

I've tried to use the Window's Left, Top, ActualWidth and ActualHeight in the SaveWindowState-method (Note: The AssociatedObject in this method is the Window.) But the Left and Top seem to be off by about 20-40 pixels, and saving the Right and Left using the ActualWidth, ActualHeight and current screen width/height (when using multiple monitors) is also a bit of a pain.

So, is there any way to save the correct position and size in the Window Settings when a user uses Win + arrow to align the Window and then closes it?

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\n{0}", 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
}
Lattonia answered 8/1, 2015 at 10:44 Comment(2)
Wondering why you work with the user32.dll import, rather then accessing the window instance directly?Disconnect
Yes, Aero Snap is very quirky. Biggest issue is that there is no notification for it nor a way to snap with the winapi, the window placement doesn't update. And beware the odd coordinate system that Get/SetWindowPlacement uses, it subtracts out the taskbars. Best avoided completely.Nosebleed
E
0

Have you tried the System.Windows.Window instance instead of p/invoke ? I use two simple methods to save and set the window position using this class and it works pixelperfectly on differents apps, architectures, clients, Windows OSs, with or without 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();
}

Or am I missing something ?

Embolism answered 6/9, 2017 at 15:54 Comment(2)
"Or am I missing something ?" I will be completely honest, I have absolutely no idea what I have / haven't tried. I posted the question more than 2.5 years ago. ;) Currently I don't even develop in .NET C# anymore on a daily basis.Lattonia
I keep on forgetting to check for the thread dates :D Have fun in your new projects then !Embolism

© 2022 - 2024 — McMap. All rights reserved.