C# Caliburn Micro: Saving Window Size and Location

I've not been using WPF or Caliburn for particularly long.

The concept of GUI specification in external files has been around for a long time, for example Motif UIL.

The concept of almost complete separation of Data and Business logic has also been around for a long time as it helps with maintenance and test-ability.

Caliburn helps with this approach by leveraging .NET languages and WPF to make it fairly simple to separate the two. However this simplicity has its downsides as it hides much of the complexities that you may occasionally need to access to get the job done as the client requires.

One of those jobs that needed to be done is to save the position and location of all windows and dialogs that an application displays. This is a usability requirement where the operator of the software will layout the windows and dialogs to suit the operational needs.

There is no simple way to do this consistently in a guaranteed way in WPF/.NET/Caliburn.

The approach take is to leaverage the following blog post:
This post uses the Win32 method calls:

Using the Win32 API approach avoids the developer having to make sure that the positions are valid taking into account all the corner cases associated with monitor resolutions, number of, position of etc...
So this leaves the code needed to implement position and size save/load:
    public class WindowBase : Screen
    {
        protected readonly string _key;
        protected Window _window;
        protected System.IntPtr _hWnd;
 
        public WindowBase(string key)
        {
            _key = key;
            _window = null;
            _hWnd = System.IntPtr.Zero;
        }
 
        protected override void OnViewReady(object view)
        {
            if (view is Window window)
            {
                _window = window;
                if (_window != null)
                {
                    HwndSource source = (HwndSource)HwndSource.FromVisual(_window);
                    _hWnd = source.Handle;
                    if (_hWnd != System.IntPtr.Zero)
                    {
                        StringCollection windows = Properties.Settings.Default.Windows;
                        int position = windows.IndexOf(_key);
                        if (position >= 0)
                        {
                            string placement = windows[position + 1];
                            if (placement != null && placement.Length != 0)
                            {
                                WindowPlacement.WindowPlacement.SetPlacement(_hWnd, placement);
                            }
                        }
                    }
                }
            }
            base.OnViewReady(view);
        }
 
        protected override void OnDeactivate(bool close)
        {
            if (_hWnd != System.IntPtr.Zero)
            {
                string placement = WindowPlacement.WindowPlacement.GetPlacement(_hWnd);
                StringCollection windows = Properties.Settings.Default.Windows;
                int position = windows.IndexOf(_key);
                if (position >= 0)
                {
                    windows[position+1] = placement;
                }
                else
                {
                    windows.Add(_key);
                    windows.Add(placement);
                }
                Properties.Settings.Default.Save();
            }
            base.OnDeactivate(close);
        }
    }


There are a couple of interesting code snippets in the above that should be noted for future use (get out of jail cards).
The method OnViewReady is called after the HWND has been created but before it has been displayed. This is before OnViewLoaded is called and is early enough that you don't see the window being moved and the bonus being that the size and position are used by the layout calculations.
The view may also be obtained by using GetView().
The type the view is is dependent upon the XAML class definition, that is:
   <UserControl x:Class="DataView"

So instead of a Window I have a UserControl in C#
   if (view is System.Windows.Controls.UserControl userControl)


This means that I now have enough to get hold of the underlying WPF controls and if needed I can modify the layout and display of those controls from within my application code (yep bypassing a lot of the concepts behind Caliburn).
Note: the storage of the _key and position information is not elegant.
Note: mapapps.metro supports saving of the window position automatically.

Comments