.NET WPF Mémoriser la taille de la fenêtre entre les sessions

93

Fondamentalement, lorsque l'utilisateur redimensionne la fenêtre de mon application, je veux que l'application soit de la même taille lorsque l'application est à nouveau ouverte.

Au début, j'ai pensé à gérer l'événement SizeChanged et à enregistrer la hauteur et la largeur, mais je pense qu'il doit y avoir une solution plus simple.

Problème assez simple, mais je ne peux pas y trouver de solution facile.

Daniil Harik
la source
2
Veuillez noter que si vous restaurez à la fois la taille et la position (comme le font la plupart des exemples de code ci-dessous), vous voudrez gérer le cas de bord où quelqu'un a débranché le moniteur sur lequel la fenêtre a été présentée pour la dernière fois, pour éviter de présenter votre fenêtre hors écran.
Omer Raviv
@OmerRaviv Avez-vous trouvé un exemple prenant en compte le cas de pointe?
Andrew Truckle
J'ai trop moins de réputation pour ajouter un commentaire, c'est pourquoi j'ai créé ce nouvel awnser. J'utilise la même solution que Lance Cleveland, y compris le réglage de RobJohnson , mais cela ne fonctionne pas si vous l'utilisez pour les sous-fenêtres et que vous voulez en ouvrir plus en même temps ...
AelanY

Réponses:

121

Enregistrez les valeurs dans le fichier user.config.

Vous devrez créer la valeur dans le fichier de paramètres - elle doit se trouver dans le dossier Propriétés. Créez cinq valeurs:

  • Top de type double
  • Left de type double
  • Height de type double
  • Width de type double
  • Maximizedde type bool- pour conserver si la fenêtre est agrandie ou non. Si vous souhaitez stocker plus d'informations, un type ou une structure différent sera nécessaire.

Initialisez les deux premiers à 0 et les deux seconds à la taille par défaut de votre application et le dernier à false.

Créez un gestionnaire d'événements Window_OnSourceInitialized et ajoutez ce qui suit:

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

REMARQUE: le placement de la fenêtre défini doit aller dans l'événement initialisé sur la source de la fenêtre et non du constructeur, sinon si vous avez la fenêtre agrandie sur un deuxième moniteur, elle redémarrera toujours maximisée sur le moniteur principal et vous ne pourrez pas pour y accéder.

Créez un gestionnaire d'événements Window_Closing et ajoutez ce qui suit:

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

Cela échouera si l'utilisateur réduit la zone d'affichage - soit en déconnectant un écran ou en modifiant la résolution de l'écran - pendant que l'application est fermée, vous devez donc ajouter une vérification que l'emplacement et la taille souhaités sont toujours valides avant d'appliquer les valeurs.

ChrisF
la source
5
En fait, les paramètres avec la portée «Utilisateur» ne sont pas enregistrés dans le fichier app.config dans Program Files, mais dans un fichier user.config dans le répertoire de données d'application de l'utilisateur. Donc ce n'est pas un problème ...
Thomas Levesque
7
En fait, vous pouvez ajouter "WindowState" aux paramètres. Sélectionnez le type -> parcourir -> PresentationFramework -> System.Windows -> WindowState :)
Martin Vseticka
2
FWIW, je le fais également à partir du gestionnaire de taille modifiée, en cas de crash de l'application. Ils sont rares avec un traitement d'exception non géré, mais pourquoi punir l'utilisateur avec une taille / un emplacement perdus lorsqu'ils se produisent mystérieusement.
Thomas le
7
Il y a un bogue dans ce code en ce que, si l'utilisateur ouvre la fenêtre sur son deuxième écran, puis déconnecte cet écran de l'ordinateur, la prochaine fois qu'il ouvrira la fenêtre, il sera présenté hors écran. Si la fenêtre est modale, l'utilisateur ne pourra pas du tout interagir avec l'application et ne comprendra pas ce qui se passe. Vous devez ajouter une vérification des limites à l'aide de Window.GetScreen (), après avoir converti les coordonnées d'écran en valeurs dépendantes DPI.
Omer Raviv
2
@OmerRaviv - ce n'est pas un bogue, mais une limitation :) Sérieusement - je n'ai pas abordé cet aspect du problème.
ChrisF
73

En fait, vous n'avez pas besoin d'utiliser le code-behind pour le faire (sauf pour enregistrer les paramètres). Vous pouvez utiliser une extension de balisage personnalisée pour lier la taille et la position de la fenêtre aux paramètres comme ceci:

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

Vous pouvez trouver le code de cette extension de balisage ici: http://www.thomaslevesque.com/2008/11/18/wpf-binding-to-application-settings-using-a-markup-extension/

Thomas Levesque
la source
4
J'aime cette réponse plus que la réponse acceptée choisie. Bien joué.
moswald le
6
+1 - J'adore l'utilisation de la reliure et des extensions! Si vous ajoutez WindowState à vos paramètres liés, il fournit toutes les fonctionnalités. Sinon, si vous avez les paramètres utilisateur disponibles dans le DataContext, vous pouvez utiliser quelque chose comme {Binding Settings.Height}, etc.
Matt DeKrey
Cette approche pose un problème lorsque l'utilisateur ferme l'application lorsque la fenêtre est agrandie.
Vinicius Rocha
@Vinicius, pouvez-vous élaborer? Quel est le problème exactement?
Thomas Levesque
4
Qu'en est-il lorsque les gens ont deux moniteurs et qu'il peut donc avoir des coordonnées négatives, puis qu'ils changent de configuration de moniteur et que les valeurs ne sont plus valides?
Andrew Truckle
33

Bien que vous puissiez "rouler vous-même" et enregistrer manuellement les paramètres quelque part, et en général cela fonctionnera, il est très facile de ne pas gérer correctement tous les cas. Il est préférable de laisser le système d'exploitation faire le travail pour vous, en appelant GetWindowPlacement () à la sortie et SetWindowPlacement () au démarrage. Il gère tous les cas de bord fous qui peuvent survenir (plusieurs moniteurs, enregistrez la taille normale de la fenêtre si elle est fermée tout en maximisant, etc.) afin que vous n'ayez pas à le faire.

Cet exemple MSDN montre comment les utiliser avec une application WPF. L'exemple n'est pas parfait (la fenêtre commencera dans le coin supérieur gauche aussi petit que possible lors de la première exécution, et il y a un comportement étrange avec le concepteur de paramètres enregistrant une valeur de type WINDOWPLACEMENT), mais cela devrait au moins vous aider à démarrer.

Andy
la source
Belle solution. Cependant, je viens de découvrir que GetWindowPlacement / SetWindowPlacement ne
Mark Bell
1
@RandomEngy a publié une réponse améliorée sur cette base.
Stéphane Gourichon
27

La liaison "longue forme" que Thomas a postée ci-dessus ne nécessite presque aucun codage, assurez-vous simplement que vous avez la liaison d'espace de noms:

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

Ensuite, pour enregistrer sur le code-behind:

private void frmMain_Closed(object sender, EventArgs e)
{
    Properties.Settings.Default.Save();
}
Lance Cleveland
la source
J'ai choisi cette solution, mais je n'ai enregistré les paramètres que si l'état de la fenêtre était normal, sinon il peut être fastidieux de la sortir du mode maximisé
David Sykes
7
+1 J'ai utilisé ceci aussi, @DavidSykes - L'ajout d'un autre paramètre pour l'état de la fenêtre semble fonctionner assez bien, par exempleWindowState="{Binding Source={x:Static properties:Settings.Default}, Path=WindowState, Mode=TwoWay}"
RobJohnson
@RobJohnson J'ai essayé votre suggestion et cela a très bien fonctionné, merci.
David Sykes
4

Vous pourriez également aimer l'approche suivante ( voir source ). Ajoutez la classe WindowSettings à votre projet et insérez-la WindowSettings.Save="True"dans l'en-tête de votre fenêtre principale:

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

Où WindowSettings est défini comme suit:

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
la source
3

Le moyen par défaut de le résoudre consiste à utiliser des fichiers de paramètres. Le problème avec les fichiers de paramètres est que vous devez définir tous les paramètres et écrire vous-même le code qui copie les données d'avant en arrière. Assez fastidieux si vous avez beaucoup de propriétés à suivre.

J'ai créé une bibliothèque assez flexible et très facile à utiliser pour cela, il vous suffit de lui dire quelles propriétés de quel objet suivre et il s'occupe du reste. Vous pouvez également en configurer la merde si vous le souhaitez.

La bibliothèque s'appelle Jot (github) , voici un ancien article de CodeProject j'ai écrit à ce sujet.

Voici comment vous l'utiliseriez pour suivre la taille et l'emplacement d'une fenêtre:

public MainWindow()
{
    InitializeComponent();

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

Jot vs fichiers de paramètres: avec Jot, il y a beaucoup moins de code, et c'est beaucoup moins sujet aux erreurs puisque vous n'avez besoin de mentionner chaque propriété qu'une seule fois . Avec les fichiers de paramètres, vous devez mentionner chaque propriété 5 fois : une fois lorsque vous créez explicitement la propriété et quatre fois supplémentaires dans le code qui copie les valeurs dans les deux sens.

Le stockage, la sérialisation, etc. sont entièrement configurables. De plus, lorsque vous utilisez IOC, vous pouvez même le connecter pour qu'il applique automatiquement le suivi à tous les objets qu'il résout. Tout ce que vous avez à faire pour rendre une propriété persistante est de lui appliquer un attribut [Trackable].

J'écris tout cela parce que je pense que la bibliothèque est de premier ordre et je veux en parler.

anakic
la source
Bien, merci pour cela - j'ai utilisé votre extrait de code dans une nouvelle classe pour configurer le suivi d'état avec un chemin basé sur le nom du programme. A partir de maintenant, je n'ai plus qu'à écrire une ligne et toutes les propriétés de la fenêtre sont gérées
Awesomeness
1

J'ai écrit un cours rapide qui fait cela. Voici comment ça s'appelle:

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

Et voici le code:

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
la source
window.Intitialized should be window.Loaded voir mosttech.blogspot.com/2008/01
Gleb Sevruk
@Gleb, les deux fonctionnent je pense. Rencontrez-vous des problèmes avec Initialized?
tster le
Oui, car la fenêtre agrandie sera sur un écran incorrect si vous utilisez uniquement un événement initialisé. Ce que j'ai fait et cela semble fonctionner: maintenant, je m'abonne également à l'événement Loaded. J'ai déplacé _window.WindowState = s.Maximized? WindowState.Maximized: WindowState.Normal; ligne à l'intérieur du gestionnaire d'événements "Loaded". window.Initialized + = InitializedHandler; window.Loaded + = LoadedHandler; btw: j'aime cette approche
Gleb Sevruk
1

Il existe un projet NuGet RestoreWindowPlace voir sur github qui fait tout cela pour vous, en enregistrant les informations dans un fichier XML.

Pour le faire fonctionner sur une fenêtre, c'est aussi simple que d'appeler:

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

Dans App, vous créez la classe qui gère vos fenêtres. Voir le lien github ci-dessus pour plus d'informations.

Chuck Savage
la source
0

Vous pourriez aimer ceci:

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

Lorsque l'application se ferme:

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

Lorsque l'application démarre:

        WindowStateHelper.FromXml(Properties.Settings.Default.Win1Placement, win1);
        WindowStateHelper.FromXml(Properties.Settings.Default.Win2Placement, win2);
        ...
Paul
la source
0

Créez une chaîne nommée WindowXml dans vos paramètres par défaut.

Utilisez cette méthode d'extension sur vos événements Window Loaded et Closing pour restaurer et enregistrer la taille et l'emplacement de la fenêtre.

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
la source
0

J'utilise la réponse de Lance Cleveland et lie le cadre. Mais j'utilise un peu plus de code pour éviter que ma fenêtre ne soit hors de l'écran.

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
la source
0

J'ai créé une solution plus générique basée sur la réponse brillante de RandomEngys. Il enregistre la position dans le fichier dans le dossier en cours et vous n'avez pas besoin de créer de nouvelles propriétés pour chaque nouvelle fenêtre que vous créez. Cette solution fonctionne très bien pour moi avec un code minimal dans le code derrière.

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

    }
}

Dans votre code derrière vous ajoutez ces deux méthodes

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

dans la fenêtre xaml, vous ajoutez ceci

Closing="ClosingTrigger"
Bjorn
la source