0
votes

I'm trying to implement storing and restoring main window state and size in my WPF application. My current code looks like following:

  • MainWindowViewModel.cs (ran on main window's OnLoaded):

    public void NotifyLoaded()
    {
        var uiConfig = configurationService.Configuration.UI;
    
        if (uiConfig.MainWindowSizeSet.Value)
            access.SetWindowSize(new System.Windows.Size(uiConfig.MainWindowWidth.Value, 
                uiConfig.MainWindowHeight.Value));
    
        if (uiConfig.MainWindowLocationSet.Value)
            access.SetWindowLocation(new System.Windows.Point(uiConfig.MainWindowX.Value,
                uiConfig.MainWindowY.Value));
    
        if (uiConfig.MainWindowMaximized.Value)
            access.SetWindowMaximized(true);
    }
    
  • The access is an IMainWindowAccess, implemented by the MainWindow itself (this is my way of providing communication between viewmodel and window, while still keeping logic and view separated). Methods are being implemented in the following way:

    void IMainWindowAccess.SetWindowSize(Size size)
    {
        this.Width = size.Width;
        this.Height = size.Height;
    }
    
    void IMainWindowAccess.SetWindowLocation(Point point)
    {
        this.Left = point.X;
        this.Top = point.Y;
    }
    
    void IMainWindowAccess.SetWindowMaximized(bool maximized)
    {
        this.WindowState = maximized ? WindowState.Maximized : WindowState.Normal;
    }
    
  • I'm storing window position and size on window closing, but only if window is not maximized. I do that to avoid situation when user tries to restore window and it restores to its maximized size.

    public void NotifyClosingWindow()
    {
        var windowSize = access.GetWindowSize();
        var windowLocation = access.GetWindowLocation();
    
        var maximized = access.GetMaximized();
        configurationService.Configuration.UI.MainWindowMaximized.Value = maximized;
    
        if (!maximized)
        {
            Models.Configuration.UI.UIConfig uiConfig = configurationService.Configuration.UI;
    
            uiConfig.MainWindowWidth.Value = windowSize.Width;
            uiConfig.MainWindowHeight.Value = windowSize.Height;
            uiConfig.MainWindowSizeSet.Value = true;
    
            uiConfig.MainWindowX.Value = windowLocation.X;
            uiConfig.MainWindowY.Value = windowLocation.Y;
            uiConfig.MainWindowLocationSet.Value = true;
        }
    
        configurationService.Save();
    }
    

The problem is that though I restore size and location of the window before maximizing it (so it should restore to its former size), it still restores to maximized size (effectively clicking the restore button changes button to maximize - size and location remains the same).

How should I set size and location of the window and then maximize it, so that size and location will be properly stored?

1
Have you tried to manage Window.StateChanged event?Pavel Anikhouski

1 Answers

1
votes

You can do this as follows

// 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)
    {
        Left = left;
        Top = top;
        Right = right;
        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)
    {
        X = x;
        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;
}

and

public static class WindowRestorerService
{
    private const int SW_SHOWNORMAL = 1;
    private const int SW_SHOWMINIMIZED = 2;

    private static Encoding encoding = new UTF8Encoding();
    private static 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);

    public static void SetPlacement(this Window window, string placementXml)
    {
        WindowRestorerService.SetPlacement(new WindowInteropHelper(window).Handle, placementXml);
    }

    /// <summary>
    /// Set the window to the relevent position using the placement information. 
    /// </summary>
    /// <param name="windowHandle">The window handle of the target information.</param>
    /// <param name="placementXml">The placement XML.</param>
    public static void SetPlacement(IntPtr windowHandle, string placementXml)
    {
        if (string.IsNullOrEmpty(placementXml))
            return;

        WINDOWPLACEMENT placement;
        byte[] xmlBytes = encoding.GetBytes(placementXml);

        try
        {
            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.
        }
    }

    public static string GetPlacement(this Window window)
    {
        return WindowRestorerService.GetPlacement(new WindowInteropHelper(window).Handle);
    }

    /// <summary>
    /// Retruns the serialize XML of the placement information for the 
    /// target window.
    /// </summary>
    /// <param name="windowHandle">The handle of the target window.</param>
    public static string GetPlacement(IntPtr windowHandle)
    {
        WINDOWPLACEMENT placement = new WINDOWPLACEMENT();
        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);
            }
        }
    }
}

Store position on close (I'm storing this info in my settings file)

public void OnClosing()
{
    MainWindowSettingsProvider settingsProvider = MainWindowSettingsProvider.Instance;
    settingsProvider.MainWindowSettings.WindowPlacementInfo = ((Window)View).GetPlacement();
    settingsProvider.Save();
}

And set via

[Export(typeof(IMainWindowView))]
public partial class MainWindowView : MetroWindow, IMainWindowView
{
    public MainWindowView()
    {
        InitializeComponent();
    }

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);
        MainWindowSettingsProvider settingsProvider = MainWindowSettingsProvider.Instance;
        this.SetPlacement(settingsProvider.MainWindowSettings.WindowPlacementInfo);
    }
}

Hope this helps.