33 votes

Restauration de la taille/position de la fenêtre avec plusieurs moniteurs

De nombreux articles traitent de la restauration de la position et de la taille d'un WinForm.

Exemples :

Mais je n'ai pas encore trouvé de code pour faire cela avec plusieurs moniteurs.

En d'autres termes, si je ferme mon application .NET Winform avec la fenêtre sur le moniteur 2, je veux qu'elle enregistre la taille, l'emplacement et l'état de la fenêtre dans les paramètres de l'application, afin de pouvoir la restaurer ultérieurement sur le moniteur 2 lorsque je redémarre l'application. Ce serait bien si, comme dans l'exemple du codeproject ci-dessus, il incluait des contrôles de cohérence, par exemple si l'emplacement enregistré est en grande partie hors écran, il le "corrige". Ou si l'emplacement sauvegardé se trouve sur un moniteur qui n'est plus là (par exemple, mon ordinateur portable est maintenant tout seul sans mon deuxième moniteur), il le déplace correctement sur le moniteur 1.

Des idées ?

Mon environnement : C#, .NET 3.5 ou inférieur, VS2008

37voto

VVS Points 11528

Essayez ce code. Points d'intérêt :

  • Vérifie si la fenêtre est (partiellement) visible sur la zone de travail de n'importe quel écran. Par exemple, le fait de la faire glisser derrière la barre des tâches ou de la déplacer complètement hors de l'écran réinitialise la position par défaut de Windows.
  • Sauvegarde les limites correctes même si le formulaire est minimisé ou maximisé (erreur courante).
  • Sauvegarde correctement le WindowState. La sauvegarde de FormWindowState.Minimized est désactivée par conception.

Les limites et l'état sont stockés dans les appsettings avec leur type correspondant, il n'est donc pas nécessaire d'analyser les chaînes. Laissez le framework faire sa magie de sérialisation.

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();

        // this is the default
        this.WindowState = FormWindowState.Normal;
        this.StartPosition = FormStartPosition.WindowsDefaultBounds;

        // check if the saved bounds are nonzero and visible on any screen
        if (Settings.Default.WindowPosition != Rectangle.Empty &&
            IsVisibleOnAnyScreen(Settings.Default.WindowPosition))
        {
            // first set the bounds
            this.StartPosition = FormStartPosition.Manual;
            this.DesktopBounds = Settings.Default.WindowPosition;

            // afterwards set the window state to the saved value (which could be Maximized)
            this.WindowState = Settings.Default.WindowState;
        }
        else
        {
            // this resets the upper left corner of the window to windows standards
            this.StartPosition = FormStartPosition.WindowsDefaultLocation;

            // we can still apply the saved size
            this.Size = Settings.Default.WindowPosition.Size;
        }
    }

    private bool IsVisibleOnAnyScreen(Rectangle rect)
    {
        foreach (Screen screen in Screen.AllScreens)
        {
            if (screen.WorkingArea.IntersectsWith(rect))
            {
                return true;
            }
        }

        return false;
    }

    protected override void OnClosed(EventArgs e)
    {
        base.OnClosed(e);

        // only save the WindowState if Normal or Maximized
        switch (this.WindowState)
        {
            case FormWindowState.Normal:
            case FormWindowState.Maximized:
                Settings.Default.WindowState = this.WindowState;
                break;

            default:
                Settings.Default.WindowState = FormWindowState.Normal;
                break;
        }

        // reset window state to normal to get the correct bounds
        // also make the form invisible to prevent distracting the user
        this.Visible = false;
        this.WindowState = FormWindowState.Normal;

        Settings.Default.WindowPosition = this.DesktopBounds;
        Settings.Default.Save();
    }
}

Le fichier de paramètres pour référence :

<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="ScreenTest" GeneratedClassName="Settings">
    <Profiles />
    <Settings>
        <Setting Name="WindowPosition" Type="System.Drawing.Rectangle" Scope="User">
            <Value Profile="(Default)">0, 0, 0, 0</Value>
        </Setting>
        <Setting Name="WindowState" Type="System.Windows.Forms.FormWindowState" Scope="User">
            <Value Profile="(Default)">Normal</Value>
        </Setting>
    </Settings>
</SettingsFile>

27voto

Michael Sorens Points 9637

La réponse fournie par VVS a été d'une grande aide ! J'ai trouvé deux problèmes mineurs avec elle cependant, donc je reposte la majeure partie de son code avec ces révisions :

(1) La toute première fois que l'application s'exécute, le formulaire est ouvert dans l'état Normal, mais sa taille est telle qu'il apparaît comme une simple barre de titre. J'ai ajouté une condition dans le constructeur pour résoudre ce problème.

(2) Si l'application est fermée alors qu'elle est minimisée ou maximisée, le code dans OnClosing ne se souvient pas des dimensions de la fenêtre dans son état normal. (Les 3 lignes de code - que j'ai maintenant commentées - semblent raisonnables mais pour une raison quelconque, elles ne fonctionnent pas). Heureusement, j'avais déjà résolu ce problème et j'ai inclus ce code dans une nouvelle région à la fin du code pour suivre l'état de la fenêtre au fur et à mesure qu'il se produit plutôt que d'attendre la fermeture.


Avec ces deux corrections en place, j'ai fait des tests :

A. fermeture dans l'état normal - rétablissement de la même taille/position et du même état

B. fermeture dans un état minimisé - rétablissement de l'état normal avec la dernière taille/position normale

C. Fermeture dans un état maximisé - rétablit l'état maximisé et se souvient de sa dernière taille/position lorsqu'on s'ajuste plus tard à l'état normal.

D. fermeture sur le moniteur 2--restauration sur le moniteur 2.

E. fermeture sur le moniteur 2 puis déconnexion du moniteur 2 - rétablissement de la même position sur le moniteur 1

David : votre code m'a permis d'atteindre les points D et E presque sans effort - non seulement vous avez fourni une solution à ma question, mais vous l'avez fournie dans un programme complet, de sorte que je l'ai fait fonctionner presque en quelques secondes après l'avoir collé dans Visual Studio. Alors un grand merci pour cela !

public partial class MainForm : Form
{
    bool windowInitialized;

    public MainForm()
    {
        InitializeComponent();

        // this is the default
        this.WindowState = FormWindowState.Normal;
        this.StartPosition = FormStartPosition.WindowsDefaultBounds;

        // check if the saved bounds are nonzero and visible on any screen
        if (Settings.Default.WindowPosition != Rectangle.Empty &&
            IsVisibleOnAnyScreen(Settings.Default.WindowPosition))
        {
            // first set the bounds
            this.StartPosition = FormStartPosition.Manual;
            this.DesktopBounds = Settings.Default.WindowPosition;

            // afterwards set the window state to the saved value (which could be Maximized)
            this.WindowState = Settings.Default.WindowState;
        }
        else
        {
            // this resets the upper left corner of the window to windows standards
            this.StartPosition = FormStartPosition.WindowsDefaultLocation;

            // we can still apply the saved size
            // msorens: added gatekeeper, otherwise first time appears as just a title bar!
            if (Settings.Default.WindowPosition != Rectangle.Empty)
            {
                this.Size = Settings.Default.WindowPosition.Size;
            }
        }
        windowInitialized = true;
    }

    private bool IsVisibleOnAnyScreen(Rectangle rect)
    {
        foreach (Screen screen in Screen.AllScreens)
        {
            if (screen.WorkingArea.IntersectsWith(rect))
            {
                return true;
            }
        }

        return false;
    }

    protected override void OnClosed(EventArgs e)
    {
        base.OnClosed(e);

        // only save the WindowState if Normal or Maximized
        switch (this.WindowState)
        {
            case FormWindowState.Normal:
            case FormWindowState.Maximized:
                Settings.Default.WindowState = this.WindowState;
                break;

            default:
                Settings.Default.WindowState = FormWindowState.Normal;
                break;
        }

        # region msorens: this code does *not* handle minimized/maximized window.

        // reset window state to normal to get the correct bounds
        // also make the form invisible to prevent distracting the user
        //this.Visible = false;
        //this.WindowState = FormWindowState.Normal;
        //Settings.Default.WindowPosition = this.DesktopBounds;

        # endregion

        Settings.Default.Save();
    }

    # region window size/position
    // msorens: Added region to handle closing when window is minimized or maximized.

    protected override void OnResize(EventArgs e)
    {
        base.OnResize(e);
        TrackWindowState();
    }

    protected override void OnMove(EventArgs e)
    {
        base.OnMove(e);
        TrackWindowState();
    }

    // On a move or resize in Normal state, record the new values as they occur.
    // This solves the problem of closing the app when minimized or maximized.
    private void TrackWindowState()
    {
        // Don't record the window setup, otherwise we lose the persistent values!
        if (!windowInitialized) { return; }

        if (WindowState == FormWindowState.Normal)
        {
            Settings.Default.WindowPosition = this.DesktopBounds;
        }
    }

    # endregion window size/position
}

9voto

ShadowChaser Points 2468

La plupart des autres solutions proposées ici consistent à déterminer manuellement le positionnement actuel de chaque moniteur. Les cas limites sont extrêmement difficiles à comprendre, et très peu d'applications sont capables d'y parvenir par elles-mêmes.

La fonction SetWindowPlacement de Windows gère correctement tous les cas limites - si la fenêtre est positionnée en dehors d'un écran visible, elle l'ajuste en conséquence.

Le meilleur exemple que j'ai vu en C# se trouve sur le blog de David Rickard. Non seulement il montre comment utiliser SetWindowPlacement, mais il montre aussi comment sérialiser le résultat entier. http://blogs.msdn.com/b/davidrickard/archive/2010/03/09/saving-window-size-and-location-in-wpf-and-winforms.aspx

3voto

CallMeLaNN Points 1945

Je pense que c'est la solution idéale, compte tenu de vos réponses et commentaires.


Cette solution consiste à sauvegarder/restaurer la taille et la position du formulaire avec moniteurs multiples + document multiple , plusieurs formes ou formulaire principal multiple soutien. Il est pas MDI mais un document multiple de type Microsoft Word avec une instance de formulaire principal différente.

Merci à VVS, msorens et Ian Goldby. Je fusionne la solution de VVS, msorens et MSDN Méthode Application.Run (ApplicationContext) exemple pour rendre le multi MainForm mais pas MDI.

Ce correctif inclut le commentaire de Ian Goldby qui utilise Form.RestoreBounds pour éliminer OnResize() , OnMove() et TrackWindowState() .

J'ai également corrigé la mémoire du moniteur lorsque le formulaire se déplace vers l'autre moniteur et est maximisé avant la sortie parce que je n'ai pas suivi le OnResize, OnMove. Grâce à ce correctif, cette solution prend en charge Fonctionnalité Snap de Windows 7 que vous pouvez faire glisser dans la barre de titre ou à l'aide de la touche Win+Flèche pour fixer la fenêtre de forme sur n'importe quel bord de l'écran ou la rendre maximale/normale ainsi que réduite.

Cette solution est implémentée dans le programme mais pas dans le formulaire principal pour supporter le formulaire multi principal. Cependant, vous pouvez également l'utiliser pour un formulaire principal unique.

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using SimpleTestForm.Properties;
using System.Drawing;

namespace SimpleTestForm
{
    static class Program
    {
        static MultiMainFormAppContext appContext;

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            appContext = new MultiMainFormAppContext();
            Application.Run(appContext);
        }

        /// <summary>
        /// Create a new MainForm and restore the form size and position if necessary. This method can be called like from Menu File > New click event.
        /// </summary>
        /// <returns></returns>
        public static MainForm createNewMainForm()
        {
            return appContext.createNewMainForm();
        }

        /// <summary>
        /// Get the current active MainForm event if a dialog is opened. Useful to create Dictionary (MainForm, T) to store Form/document dependent field. Please set the Owner of child form to prevent null reference exception.
        /// </summary>
        /// <returns></returns>
        public static MainForm GetCurrentMainFormInstance()
        {
            Form mainForm = Form.ActiveForm;
            while (!(mainForm is MainForm) && mainForm.Owner != null)
                mainForm = mainForm.Owner;
            return mainForm as MainForm;
        }
    }

    class MultiMainFormAppContext : ApplicationContext
    {
        List<MainForm> mainForms = new List<MainForm>();
        Point newRestoredLocation = Point.Empty;

        internal MultiMainFormAppContext()
        {
            createNewMainForm();
        }

        internal MainForm createNewMainForm()
        {
            MainForm mainForm = new MainForm();
            mainForm.FormClosed += new FormClosedEventHandler(mainForm_FormClosed);
            mainForm.LocationChanged += new EventHandler(mainForm_LocationChanged);
            RestoreFormSizeNPosition(mainForm);
            PreventSameLocation(mainForm);
            mainForms.Add(mainForm);
            mainForm.Show();
            return mainForm;
        }

        private void PreventSameLocation(MainForm mainForm)
        {
            const int distance = 20;
            foreach (MainForm otherMainForm in mainForms)
            {
                if (Math.Abs(otherMainForm.Location.X - mainForm.Location.X) < distance &&
                    Math.Abs(otherMainForm.Location.Y - mainForm.Location.Y) < distance)
                    mainForm.Location = new Point(mainForm.Location.X + distance, mainForm.Location.Y + distance);
            }
        }

        /// <summary>
        /// Restore the form size and position with multi monitor support.
        /// </summary>
        private void RestoreFormSizeNPosition(MainForm mainForm)
        {
            // this is the default
            mainForm.WindowState = FormWindowState.Normal;
            mainForm.StartPosition = FormStartPosition.WindowsDefaultBounds;

            // check if the saved bounds are nonzero and visible on any screen
            if (Settings.Default.WindowPosition != Rectangle.Empty &&
                IsVisibleOnAnyScreen(Settings.Default.WindowPosition))
            {
                // first set the bounds
                mainForm.StartPosition = FormStartPosition.Manual;
                mainForm.DesktopBounds = Settings.Default.WindowPosition;

                // afterwards set the window state to the saved value (which could be Maximized)
                mainForm.WindowState = Settings.Default.WindowState;
            }
            else
            {
                // this resets the upper left corner of the window to windows standards
                mainForm.StartPosition = FormStartPosition.WindowsDefaultLocation;

                // we can still apply the saved size if not empty
                if (Settings.Default.WindowPosition != Rectangle.Empty)
                {
                    mainForm.Size = Settings.Default.WindowPosition.Size;
                }
            }
        }

        private void SaveFormSizeNPosition(MainForm mainForm)
        {
            // only save the WindowState as Normal or Maximized
            Settings.Default.WindowState = FormWindowState.Normal;
            if (mainForm.WindowState == FormWindowState.Normal || mainForm.WindowState == FormWindowState.Maximized)
                Settings.Default.WindowState = mainForm.WindowState;

            if (mainForm.WindowState == FormWindowState.Normal)
            {
                Settings.Default.WindowPosition = mainForm.DesktopBounds;
            }
            else
            {
                if (newRestoredLocation == Point.Empty)
                    Settings.Default.WindowPosition = mainForm.RestoreBounds;
                else
                    Settings.Default.WindowPosition = new Rectangle(newRestoredLocation, mainForm.RestoreBounds.Size);
            }

            Settings.Default.Save();
        }

        private bool IsVisibleOnAnyScreen(Rectangle rect)
        {
            foreach (Screen screen in Screen.AllScreens)
            {
                if (screen.WorkingArea.IntersectsWith(rect))
                    return true;
            }
            return false;
        }

        void mainForm_LocationChanged(object sender, EventArgs e)
        {
            MainForm mainForm = sender as MainForm;
            if (mainForm.WindowState == FormWindowState.Maximized)
            {
                // get the center location of the form incase like RibbonForm will be bigger and maximized Location wll be negative value that Screen.FromPoint(mainForm.Location) will going to the other monitor resides on the left or top of primary monitor.
                // Another thing, you might consider the form is in the monitor even if the location (top left corner) is on another monitor because majority area is on the monitor, so center point is the best way.
                Point centerFormMaximized = new Point (mainForm.DesktopBounds.Left + mainForm.DesktopBounds.Width/2, mainForm.DesktopBounds.Top + mainForm.DesktopBounds.Height/2);
                Point centerFormRestored = new Point(mainForm.RestoreBounds.Left + mainForm.RestoreBounds.Width / 2, mainForm.RestoreBounds.Top + mainForm.RestoreBounds.Height / 2);
                Screen screenMaximized = Screen.FromPoint(centerFormMaximized);
                Screen screenRestored = Screen.FromPoint(centerFormRestored);
                // we need to change the Location of mainForm.RestoreBounds to the new screen where the form currently maximized.
                // RestoreBounds does not update the Location if you change the screen but never restore to FormWindowState.Normal
                if (screenMaximized.DeviceName != screenRestored.DeviceName)
                {
                    newRestoredLocation = mainForm.RestoreBounds.Location;
                    int screenOffsetX = screenMaximized.Bounds.Location.X - screenRestored.Bounds.Location.X;
                    int screenOffsetY = screenMaximized.Bounds.Location.Y - screenRestored.Bounds.Location.Y;
                    newRestoredLocation.Offset(screenOffsetX, screenOffsetY);
                    return;
                }
            }
            newRestoredLocation = Point.Empty;
        }

        void mainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            MainForm mainForm = sender as MainForm;
            SaveFormSizeNPosition(mainForm);
            mainForm.FormClosed -= new FormClosedEventHandler(mainForm_FormClosed);
            mainForm.LocationChanged -= new EventHandler(mainForm_LocationChanged);
            mainForm.Dispose();
            mainForms.Remove(mainForm);
            if (mainForms.Count == 0) ExitThread();
        }
    }
}

Edit : Ajout de la méthode PreventSameLocation pour s'assurer que le 2ème formulaire ouvert ne se trouve pas exactement au-dessus du 1er formulaire et que l'utilisateur remarque le nouveau formulaire ouvert.

1voto

Cheeso Points 87022

Si vous avez plusieurs moniteurs, je crois que les dimensions de l'interface utilisateur de l'écran sont simplement plus grandes. Ainsi, l'approche normale "1 moniteur" de stockage et de restauration de l'emplacement fonctionnera simplement. Je n'ai pas essayé parce que je suis loin de mon deuxième écran, mais cela ne devrait pas être difficile à tester. La façon dont vous avez posé la question semble indiquer que vous ne l'avez pas testé.

Votre deuxième exigence signifie que vous devrez vérifier les dimensions maximales de l'écran lors de la restauration de l'application, puis repositionner si nécessaire. Pour faire cette dernière chose, j'utilise ce code :

    private System.Drawing.Rectangle ConstrainToScreen(System.Drawing.Rectangle bounds)
    {
        Screen screen = Screen.FromRectangle(bounds);
        System.Drawing.Rectangle workingArea = screen.WorkingArea;
        int width = Math.Min(bounds.Width, workingArea.Width);
        int height = Math.Min(bounds.Height, workingArea.Height);
        // mmm....minimax            
        int left = Math.Min(workingArea.Right - width, Math.Max(bounds.Left, workingArea.Left));
        int top = Math.Min(workingArea.Bottom - height, Math.Max(bounds.Top, workingArea.Top));
        return new System.Drawing.Rectangle(left, top, width, height);
    }

J'appelle cette méthode lors de la restauration du formulaire. Je stocke la géométrie de l'écran dans le registre à la fermeture du formulaire, puis je lis la géométrie à l'ouverture du formulaire. Je récupère les limites, mais je contrains ensuite les limites restaurées à l'écran actuel, en utilisant la méthode ci-dessus.

Économisez sur la fermeture :

      // store the size of the form
      int w = 0, h = 0, left = 0, top = 0;
      if (this.Bounds.Width < this.MinimumSize.Width || this.Bounds.Height < this.MinimumSize.Height)
      {
          // The form is currently minimized.  
          // RestoreBounds is the size of the window prior to last minimize action.
          w = this.RestoreBounds.Width;
          h = this.RestoreBounds.Height;
          left = this.RestoreBounds.Location.X;
          top = this.RestoreBounds.Location.Y;
      }
      else
      {
          w = this.Bounds.Width;
          h = this.Bounds.Height;
          left = this.Location.X;
          top = this.Location.Y;
      }
      AppCuKey.SetValue(_rvn_Geometry,
        String.Format("{0},{1},{2},{3},{4}",
              left, top, w, h, (int)this.WindowState));

Restaurer à l'ouverture du formulaire :

    // restore the geometry of the form
    string s = (string)AppCuKey.GetValue(_rvn_Geometry);
    if (!String.IsNullOrEmpty(s))
    {
        int[] p = Array.ConvertAll<string, int>(s.Split(','),
                         new Converter<string, int>((t) => { return Int32.Parse(t); }));
        if (p != null && p.Length == 5)
            this.Bounds = ConstrainToScreen(new System.Drawing.Rectangle(p[0], p[1], p[2], p[3]));
    }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X