.NET WPF Recordar el tamaño de la ventana entre sesiones

93

Básicamente, cuando el usuario cambia el tamaño de la ventana de mi aplicación, quiero que la aplicación tenga el mismo tamaño cuando la aplicación se vuelva a abrir.

Al principio pensé en manejar el evento SizeChanged y guardar Height y Width, pero creo que debe haber una solución más fácil.

Problema bastante simple, pero no puedo encontrar una solución fácil.

Daniil Harik
fuente
2
Tenga en cuenta que si está restaurando tanto el tamaño como la posición (como hacen la mayoría de los ejemplos de código a continuación), querrá manejar el caso de borde en el que alguien desconectó el monitor en el que se presentó la ventana por última vez, para evitar presentar su ventana fuera de la pantalla.
Omer Raviv
@OmerRaviv ¿Ha encontrado un ejemplo que tenga en cuenta el caso límite?
Andrew Truckle
Tengo menos repulsión para agregar un comentario, por lo tanto, creé este nuevo awnser. Utilizo la misma solución que Lance Cleveland, incluida la configuración de RobJohnson , pero no funciona si la usa para subventanas y desea abrir más de ellas al mismo tiempo ...
AelanY

Respuestas:

121

Guarde los valores en el archivo user.config.

Deberá crear el valor en el archivo de configuración; debe estar en la carpeta Propiedades. Crea cinco valores:

  • Top de tipo double
  • Left de tipo double
  • Height de tipo double
  • Width de tipo double
  • Maximizedde tipo bool: para mantener si la ventana está maximizada o no. Si desea almacenar más información, se necesitará un tipo o estructura diferente.

Inicialice los dos primeros en 0 y los dos segundos en el tamaño predeterminado de su aplicación, y el último en falso.

Cree un controlador de eventos Window_OnSourceInitialized y agregue lo siguiente:

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

NOTA: La ubicación de la ventana configurada debe ir en el evento inicializado en la fuente de la ventana, no en el constructor; de lo contrario, si tiene la ventana maximizada en un segundo monitor, siempre se reiniciará maximizada en el monitor principal y no podrá para acceder a él.

Cree un controlador de eventos Window_Closing y agregue lo siguiente:

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();

Esto fallará si el usuario reduce el área de visualización, ya sea desconectando una pantalla o cambiando la resolución de la pantalla, mientras la aplicación está cerrada, por lo que debe agregar una verificación de que la ubicación y el tamaño deseados siguen siendo válidos antes de aplicar los valores.

ChrisF
fuente
5
En realidad, las configuraciones con alcance "Usuario" no se guardan en el archivo app.config en Archivos de programa, sino en un archivo user.config en el directorio de datos de la aplicación del usuario. Entonces no es un problema ...
Thomas Levesque
7
En realidad, puede agregar "WindowState" a la configuración. Seleccione el tipo -> navegar -> PresentationFramework -> System.Windows -> WindowState :)
Martin Vseticka
2
FWIW, también hago esto desde el controlador de tamaño cambiado, en caso de que la aplicación se bloquee. Son raros con un procesamiento de excepciones no controlado, pero ¿por qué castigar al usuario con la pérdida de tamaño / ubicación cuando ocurren misteriosamente?
Thomas
7
Hay un error en este código porque, si el usuario abre la ventana en su segunda pantalla, luego desconecta esa pantalla de la computadora, la próxima vez que abra la ventana, se presentará fuera de la pantalla. Si la ventana es modal, el usuario no podrá interactuar con la aplicación en absoluto y no entenderá lo que está sucediendo. Debe agregar una verificación de límites usando Window.GetScreen (), después de convertir las coordenadas de la pantalla a valores dependientes de DPI.
Omer Raviv
2
@OmerRaviv: no es un error, sino una limitación :) En serio, no abordé ese aspecto del problema.
ChrisF
73

En realidad, no necesita usar código subyacente para hacer eso (excepto para guardar la configuración). Puede usar una extensión de marcado personalizada para vincular el tamaño y la posición de la ventana a la configuración de esta manera:

<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}">

Puede encontrar el código para esta extensión de marcado aquí: http://www.thomaslevesque.com/2008/11/18/wpf-binding-to-application-settings-using-a-markup-extension/

Thomas Levesque
fuente
4
Me gusta más esta respuesta que la respuesta elegida aceptada. Bien hecho.
Moswald
6
+1 - ¡Me encanta el uso de encuadernaciones y extensiones! Si agrega WindowState a su configuración enlazada, proporciona todas las capacidades. Alternativamente, si tiene la configuración de usuario disponible en DataContext, puede usar algo como {Binding Settings.Height}, etc.
Matt DeKrey
Este enfoque tiene un problema cuando el usuario cierra la aplicación cuando la ventana está maximizada.
Vinicius Rocha
@Vinicius, ¿puedes dar más detalles? ¿Cuál es exactamente el problema?
Thomas Levesque
4
¿Qué pasa cuando las personas tienen dos monitores y, por lo tanto, pueden tener coordenadas negativas y luego cambian las configuraciones del monitor y los valores ya no son válidos?
Andrew Truckle
33

Si bien puede "rodar por su cuenta" y guardar manualmente la configuración en algún lugar, y en general funcionará, es muy fácil no manejar todos los casos correctamente. Es mucho mejor dejar que el sistema operativo haga el trabajo por usted, llamando a GetWindowPlacement () al salir y SetWindowPlacement () al inicio. Maneja todos los casos extremos locos que pueden ocurrir (múltiples monitores, guardar el tamaño normal de la ventana si está cerrada mientras está maximizada, etc.) para que usted no tenga que hacerlo.

Este ejemplo de MSDN muestra cómo usarlos con una aplicación WPF. La muestra no es perfecta (la ventana comenzará en la esquina superior izquierda lo más pequeña posible en la primera ejecución, y hay un comportamiento extraño con el diseñador de Configuración guardando un valor de tipo WINDOWPLACEMENT), pero al menos debería ayudarlo a comenzar.

Andy
fuente
Buena solucion. Sin embargo, acabo de descubrir que GetWindowPlacement / SetWindowPlacement no
Mark Bell
1
@RandomEngy ha publicado una respuesta mejorada basada en esto.
Stéphane Gourichon
27

El enlace de "forma larga" que Thomas publicó anteriormente casi no requiere codificación, solo asegúrese de tener el enlace del espacio de nombres:

<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}">

Luego, para guardar en el código subyacente:

private void frmMain_Closed(object sender, EventArgs e)
{
    Properties.Settings.Default.Save();
}
Lance Cleveland
fuente
Elegí esta solución, pero solo guardé la configuración si el estado de la ventana era normal; de lo contrario, puede ser complicado sacarlo del modo maximizado
David Sykes
7
+1 También utilicé esto, @DavidSykes - Agregar otra configuración para el estado de la ventana parece funcionar lo suficientemente bien, por ejemploWindowState="{Binding Source={x:Static properties:Settings.Default}, Path=WindowState, Mode=TwoWay}"
RobJohnson
@RobJohnson Probé tu sugerencia y funcionó muy bien, gracias.
David Sykes
4

Alternativamente, también le puede gustar el siguiente enfoque ( ver fuente ). Agregue la clase WindowSettings a su proyecto e insértela WindowSettings.Save="True"en el encabezado de la ventana principal:

<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">

Donde WindowSettings se define de la siguiente manera:

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
}
}
Erik Vullings
fuente
3

La forma predeterminada de resolverlo es usar archivos de configuración. El problema con los archivos de configuración es que debe definir todas las configuraciones y escribir el código que copia los datos de un lado a otro. Bastante tedioso si tiene muchas propiedades de las que realizar un seguimiento.

Hice una biblioteca bastante flexible y muy fácil de usar para esto, solo dígale qué propiedades de qué objeto rastrear y él hace el resto. También puede configurar la mierda si lo desea.

La biblioteca se llama Jot (github) , aquí hay un artículo antiguo de CodeProject que escribí al respecto.

Así es como lo usaría para realizar un seguimiento del tamaño y la ubicación de una ventana:

public MainWindow()
{
    InitializeComponent();

    _stateTracker.Configure(this)
        .IdentifyAs("MyMainWindow")
        .AddProperties(nameof(Height), nameof(Width), nameof(Left), nameof(Top), nameof(WindowState))
        .RegisterPersistTrigger(nameof(Closed))
        .Apply();
}

Archivos Jot frente a archivos de configuración: con Jot hay considerablemente menos código y es mucho menos propenso a errores, ya que solo necesita mencionar cada propiedad una vez . Con los archivos de configuración, debe mencionar cada propiedad 5 veces : una cuando crea explícitamente la propiedad y cuatro veces adicionales en el código que copia los valores de un lado a otro.

El almacenamiento, la serialización, etc. son completamente configurables. Además, al usar IOC, incluso puede conectarlo para que aplique el seguimiento automáticamente a todos los objetos que resuelve, de modo que todo lo que necesita hacer para que una propiedad sea persistente es colocar un atributo [Trackable] en ella.

Estoy escribiendo todo esto porque creo que la biblioteca es de primera categoría y quiero hablar sobre ello.

anakic
fuente
Bien, gracias por esto. He usado su fragmento de código en una nueva clase para configurar el rastreador de estado con una ruta basada en el nombre del programa. A partir de ahora, solo tengo que escribir una línea y se manejan todas las propiedades de la ventana
Awesomeness
1

Escribí una clase rápida que hace esto. Así es como se llama:

    public MainWindow()
    {
        FormSizeSaver.RegisterForm(this, () => Settings.Default.MainWindowSettings,
                                   s =>
                                   {
                                       Settings.Default.MainWindowSettings = s;
                                       Settings.Default.Save();
                                   });
        InitializeComponent();
        ...

Y aquí está el código:

public class FormSizeSaver
{
    private readonly Window window;
    private readonly Func<FormSizeSaverSettings> getSetting;
    private readonly Action<FormSizeSaverSettings> saveSetting;
    private FormSizeSaver(Window window, Func<string> getSetting, Action<string> saveSetting)
    {
        this.window = window;
        this.getSetting = () => FormSizeSaverSettings.FromString(getSetting());
        this.saveSetting = s => saveSetting(s.ToString());

        window.Initialized += InitializedHandler;
        window.StateChanged += StateChangedHandler;
        window.SizeChanged += SizeChangedHandler;
        window.LocationChanged += LocationChangedHandler;
    }

    public static FormSizeSaver RegisterForm(Window window, Func<string> getSetting, Action<string> saveSetting)
    {
        return new FormSizeSaver(window, getSetting, saveSetting);
    }


    private void SizeChangedHandler(object sender, SizeChangedEventArgs e)
    {
        var s = getSetting();
        s.Height = e.NewSize.Height;
        s.Width = e.NewSize.Width;
        saveSetting(s);
    }

    private void StateChangedHandler(object sender, EventArgs e)
    {
        var s = getSetting();
        if (window.WindowState == WindowState.Maximized)
        {
            if (!s.Maximized)
            {
                s.Maximized = true;
                saveSetting(s);
            }
        }
        else if (window.WindowState == WindowState.Normal)
        {
            if (s.Maximized)
            {
                s.Maximized = false;
                saveSetting(s);
            }
        }
    }

    private void InitializedHandler(object sender, EventArgs e)
    {
        var s = getSetting();
        window.WindowState = s.Maximized ? WindowState.Maximized : WindowState.Normal;

        if (s.Height != 0 && s.Width != 0)
        {
            window.Height = s.Height;
            window.Width = s.Width;
            window.WindowStartupLocation = WindowStartupLocation.Manual;
            window.Left = s.XLoc;
            window.Top = s.YLoc;
        }
    }

    private void LocationChangedHandler(object sender, EventArgs e)
    {
        var s = getSetting();
        s.XLoc = window.Left;
        s.YLoc = window.Top;
        saveSetting(s);
    }
}

[Serializable]
internal class FormSizeSaverSettings
{
    public double Height, Width, YLoc, XLoc;
    public bool Maximized;

    public override string ToString()
    {
        using (var ms = new MemoryStream())
        {
            var bf = new BinaryFormatter();
            bf.Serialize(ms, this);
            ms.Position = 0;
            byte[] buffer = new byte[(int)ms.Length];
            ms.Read(buffer, 0, buffer.Length);
            return Convert.ToBase64String(buffer);
        }
    }

    internal static FormSizeSaverSettings FromString(string value)
    {
        try
        {
            using (var ms = new MemoryStream(Convert.FromBase64String(value)))
            {
                var bf = new BinaryFormatter();
                return (FormSizeSaverSettings) bf.Deserialize(ms);
            }
        }
        catch (Exception)
        {
            return new FormSizeSaverSettings();
        }
    }
}
tster
fuente
window.Intitialized debe ser window.Loaded, ver mosttech.blogspot.com/2008/01/…
Gleb Sevruk
@Gleb, creo que ambos funcionan. ¿Tiene problemas con él en Initialized?
tster
Sí, ya que la ventana maximizada estará en la pantalla incorrecta si usa solo el evento inicializado. Lo que hice y esto parece funcionar: Ahora también me suscribo al evento Loaded. Moví _window.WindowState = s.Maximized? WindowState.Maximized: WindowState.Normal; línea dentro del controlador de eventos "Loaded". window.Initialized + = InitializedHandler; window.Loaded + = LoadedHandler; por cierto: me gusta este enfoque
Gleb Sevruk
1

Hay una vista de RestoreWindowPlace del proyecto NuGet en github que hace todo esto por usted, guardando la información en un archivo XML.

Para que funcione en una ventana, es tan simple como llamar:

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

En la aplicación creas la clase que administra tus ventanas. Consulte el enlace de github anterior para obtener más información.

Chuck Savage
fuente
0

Te puede gustar esto:

public class WindowStateHelper
{
    public static string ToXml(System.Windows.Window win)
    {
        XElement bounds = new XElement("Bounds");
        if (win.WindowState == System.Windows.WindowState.Maximized)
        {
            bounds.Add(new XElement("Top", win.RestoreBounds.Top));
            bounds.Add(new XElement("Left", win.RestoreBounds.Left));
            bounds.Add(new XElement("Height", win.RestoreBounds.Height));
            bounds.Add(new XElement("Width", win.RestoreBounds.Width));
        }
        else
        {
            bounds.Add(new XElement("Top", win.Top));
            bounds.Add(new XElement("Left", win.Left));
            bounds.Add(new XElement("Height", win.Height));
            bounds.Add(new XElement("Width", win.Width));
        }
        XElement root = new XElement("WindowState",
            new XElement("State", win.WindowState.ToString()),
            new XElement("Visibility", win.Visibility.ToString()),
            bounds);

        return root.ToString();
    }

    public static void FromXml(string xml, System.Windows.Window win)
    {
        try
        {
            XElement root = XElement.Parse(xml);
            string state = root.Descendants("State").FirstOrDefault().Value;
            win.WindowState = (System.Windows.WindowState)Enum.Parse(typeof(System.Windows.WindowState), state);

            state = root.Descendants("Visibility").FirstOrDefault().Value;
            win.Visibility = (System.Windows.Visibility)Enum.Parse(typeof(System.Windows.Visibility), state);

            XElement bounds = root.Descendants("Bounds").FirstOrDefault();
            win.Top = Convert.ToDouble(bounds.Element("Top").Value);
            win.Left = Convert.ToDouble(bounds.Element("Left").Value);
            win.Height = Convert.ToDouble(bounds.Element("Height").Value);
            win.Width = Convert.ToDouble(bounds.Element("Width").Value);
        }
        catch (Exception x)
        {
            System.Console.WriteLine(x.ToString());
        }
    }
}

Cuando se cierra la aplicación:

        Properties.Settings.Default.Win1Placement = WindowStateHelper.ToXml(win1);
        Properties.Settings.Default.Win2Placement = WindowStateHelper.ToXml(win2);
        ...

Cuando se inicia la aplicación:

        WindowStateHelper.FromXml(Properties.Settings.Default.Win1Placement, win1);
        WindowStateHelper.FromXml(Properties.Settings.Default.Win2Placement, win2);
        ...
Pablo
fuente
0

Cree una cadena llamada WindowXml en su configuración predeterminada.

Utilice este método de extensión en los eventos de ventana cargada y de cierre para restaurar y guardar el tamaño y la ubicación de la ventana.

using YourProject.Properties;
using System;
using System.Linq;
using System.Windows;
using System.Xml.Linq;

namespace YourProject.Extensions
{
    public static class WindowExtensions
    {
        public static void SaveSizeAndLocation(this Window w)
        {
            try
            {
                var s = "<W>";
                s += GetNode("Top", w.WindowState == WindowState.Maximized ? w.RestoreBounds.Top : w.Top);
                s += GetNode("Left", w.WindowState == WindowState.Maximized ? w.RestoreBounds.Left : w.Left);
                s += GetNode("Height", w.WindowState == WindowState.Maximized ? w.RestoreBounds.Height : w.Height);
                s += GetNode("Width", w.WindowState == WindowState.Maximized ? w.RestoreBounds.Width : w.Width);
                s += GetNode("WindowState", w.WindowState);
                s += "</W>";

                Settings.Default.WindowXml = s;
                Settings.Default.Save();
            }
            catch (Exception)
            {
            }
        }

        public static void RestoreSizeAndLocation(this Window w)
        {
            try
            {
                var xd = XDocument.Parse(Settings.Default.WindowXml);
                w.WindowState = (WindowState)Enum.Parse(typeof(WindowState), xd.Descendants("WindowState").FirstOrDefault().Value);
                w.Top = Convert.ToDouble(xd.Descendants("Top").FirstOrDefault().Value);
                w.Left = Convert.ToDouble(xd.Descendants("Left").FirstOrDefault().Value);
                w.Height = Convert.ToDouble(xd.Descendants("Height").FirstOrDefault().Value);
                w.Width = Convert.ToDouble(xd.Descendants("Width").FirstOrDefault().Value);
            }
            catch (Exception)
            {
            }
        }

        private static string GetNode(string name, object value)
        {
            return string.Format("<{0}>{1}</{0}>", name, value);
        }
    }
}
Tempeck
fuente
0

Estoy usando la respuesta de Lance Cleveland y vinculo el ajuste. Pero estoy usando más código para evitar que mi ventana esté fuera de pantalla.

private void SetWindowSettingsIntoScreenArea()
{
    // first detect Screen, where we will display the Window
    // second correct bottom and right position
    // then the top and left position.
    // If Size is bigger than current Screen, it's still possible to move and size the Window

    // get the screen to display the window
    var screen = System.Windows.Forms.Screen.FromPoint(new System.Drawing.Point((int)Default.Left, (int)Default.Top));

    // is bottom position out of screen for more than 1/3 Height of Window?
    if (Default.Top + (Default.Height / 3) > screen.WorkingArea.Height)
        Default.Top = screen.WorkingArea.Height - Default.Height;

    // is right position out of screen for more than 1/2 Width of Window?
    if (Default.Left + (Default.Width / 2) > screen.WorkingArea.Width)
        Default.Left = screen.WorkingArea.Width - Default.Width;

    // is top position out of screen?
    if (Default.Top < screen.WorkingArea.Top)
        Default.Top = screen.WorkingArea.Top;

    // is left position out of screen?
    if (Default.Left < screen.WorkingArea.Left)
        Default.Left = screen.WorkingArea.Left;
}
Markus
fuente
0

Hice una solución más genérica basada en la brillante respuesta de RandomEngys. Guarda la posición en el archivo en la carpeta en ejecución y no necesita crear nuevas propiedades para cada nueva ventana que cree. Esta solución funciona muy bien para mí con un código mínimo en el código subyacente.

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

    }
}

En su código detrás, agregue estos dos métodos

///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();
}

en la ventana xaml agregas esto

Closing="ClosingTrigger"
Bjorn
fuente