De nombreux articles sur la restauration d'une position et d'une taille WinForm.
Exemples:
Mais je n'ai pas encore trouvé de code pour le faire avec plusieurs moniteurs.
Autrement dit, si je ferme mon application .NET Winform avec la fenêtre sur le moniteur 2, je souhaite que celle-ci enregistre la taille, l'emplacement et l'état de Windows dans les paramètres de l'application, afin de pouvoir le restaurer ultérieurement sur le moniteur 2 au redémarrage de l'application. Ce serait bien si, comme dans l'exemple de codeproject ci-dessus, il incluait des contrôles de cohérence, comme si l'emplacement sauvegardé était généralement hors écran, il le "corrigeait". Ou si l'emplacement enregistré se trouve sur un moniteur qui n'y est plus (par exemple, mon ordinateur portable est désormais autonome sans mon deuxième moniteur), il est alors correctement déplacé vers le moniteur 1.
Des pensées?
Mon environnement: C #, .NET 3.5 ou inférieur, VS2008
La réponse fournie par VVS a été d'une grande aide! Cependant, j'ai trouvé deux problèmes mineurs, alors je republie la majeure partie de son code avec ces révisions:
(1) La toute première fois que l'application est exécutée, le formulaire est ouvert dans un état normal, mais sa taille est telle qu'il apparaît uniquement comme une barre de titre. J'ai ajouté une condition dans le constructeur pour résoudre ce problème.
(2) Si l'application est fermée tout en étant minimisée ou maximisée, le code dans OnClosing ne parvient pas à se souvenir 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 ne fonctionnent tout simplement pas.) Heureusement, j'avais déjà résolu ce problème et l'avais inclus dans une nouvelle région à la fin du code pour suivre l'état de la fenêtre en cours plutôt que d'attendre la fermeture.
Avec ces deux correctifs en place, j'ai testé:
A. fermeture en état normal - rétablit à la même taille/position et état
B. fermeture à l'état minimisé - rétablit à l'état normal avec la dernière taille/position normale
C. fermeture en état maximisé - rétablit à l'état maximisé et se souvient de sa dernière taille/position lors de son dernier ajustement à l'état normal.
D. fermer sur le moniteur 2 - restaure le moniteur 2.
E. fermeture sur le moniteur 2 puis déconnexion du moniteur 2 - rétablit la même position sur le moniteur 1
David: votre code m'a permis d'obtenir des points D et E presque sans effort - vous avez non seulement fourni une solution à ma question, mais vous l'avez fournie dans un programme complet. Je l'ai donc utilisé presque quelques secondes après que je l'ai collé dans Visual Studio. . Donc, un grand merci pour ça!
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
}
Essayez ce code. Points d'interêts:
Les limites et l'état sont stockés dans les paramètres d'application avec le 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>
La plupart des autres solutions reposent sur la détermination manuelle du positionnement actuel de chaque moniteur. Les cas Edge sont extrêmement difficiles à comprendre et très peu d'applications peuvent réussir correctement.
La fonction SetWindowPlacement dans Windows lui-même gère correctement tous les cas Edge. 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 # est sur le blog de David Rickard. Cela montre non seulement comment utiliser SetWindowPlacement, mais 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
Cette solution consiste à enregistrer/restaurer la taille et la position du formulaire avec plusieurs moniteurs + plusieurs documents , multi-formulaires ou multi-principaux forme support. C'est pas MDI form, mais Microsoft Word ressemble à un document multiple avec une instance de formulaire principale différente.
Merci à VVS, Msorens et Ian Goldby. Je fusionne la solution de VVS, de msorens et de MSDN Méthode Application.Run (ApplicationContext) exemple pour créer le multi MainForm mais pas le MDI.
Ce correctif de inclut le commentaire de Ian Goldby qui utilise Form.RestoreBounds
pour éliminer OnResize()
, OnMove()
et TrackWindowState()
.
Je m'arrange aussi pour me souvenir du moniteur lorsque le formulaire passe à l'autre moniteur et est maximisé avant de quitter car je ne surveille pas OnResize, OnMove. Par ce correctif, cette solution prend en charge Fonctionnalité de capture de Windows 7 qui vous pouvez faire glisser la barre de titre ou la touche Win + la flèche pour insérer la fenêtre de formulaire dans n’importe quel moniteur ou la rendre agrandie/normale ou réduite.
Cette solution est implémentée dans Program mais pas dans le formulaire principal pour prendre en charge le formulaire multi-principal. Cependant, vous pouvez également utiliser pour un seul formulaire principal.
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();
}
}
}
Éditer: Méthode PreventSameLocation ajoutée pour s’assurer que le deuxième formulaire s’est ouvert pas exactement au-dessus du premier formulaire et que l’utilisateur remarquera le nouveau formulaire ouvert.
Si vous avez plusieurs moniteurs, je pense que les dimensions de l'interface utilisateur à l'écran sont tout simplement plus grandes. Ainsi, l'approche normale "1 moniteur" de stockage et de restauration de l'emplacement fonctionnera simplement. Je n'ai pas essayé cela parce que je suis loin de mon deuxième moniteur, mais cela ne devrait pas être difficile à tester. La façon dont vous avez posé la question semble avoir été testée.
Votre deuxième exigence signifie que vous devrez vérifier les dimensions maximales de l'écran lors de la restauration de l'application, puis vous repositionner si nécessaire. Pour faire ce dernier bit, 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 au formulaire ouvert. J'obtiens les limites, mais je contrains ensuite les limites restaurées à l'écran actuel, en utilisant la méthode ci-dessus.
Économiser à proximité:
// 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 sur formulaire ouvert:
// 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]));
}
C'est une vieille question, mais voici une version VB basée sur les réponses précédentes.
Un problème avec les réponses suggérées par VVS et Michael Sorens est qu’une position enregistrée qui n’affiche que quelques pixels sur un écran compte comme visible. Cette solution nécessite une intersection d’au moins 50 x 50 pixels avant de restaurer l’emplacement précédent.
Réglages:
<Settings>
<Setting Name="WindowState" Type="System.Windows.Forms.FormWindowState" Scope="User">
<Value Profile="(Default)">Normal</Value>
</Setting>
<Setting Name="WindowBounds" Type="System.Drawing.Rectangle" Scope="User">
<Value Profile="(Default)">10, 10, 800, 600</Value>
</Setting>
</Settings>
Forme:
Partial Public Class MainForm
Private loadingComplete As Boolean = False
Public Sub New()
InitializeComponent()
RestoreWindowLocation()
End Sub
Private Sub MainForm_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
loadingComplete = True
End Sub
Private Sub MainForm_Resize(sender As System.Object, e As System.EventArgs) Handles MyBase.Resize
TrackWindowLocation()
End Sub
Private Sub MainForm_Move(sender As System.Object, e As System.EventArgs) Handles MyBase.Move
TrackWindowLocation()
End Sub
Private Sub MainForm_FormClosing(sender As System.Object, e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
SaveWindowLocation()
End Sub
Private Sub RestoreWindowLocation()
If IsRectangleVisible(My.Settings.WindowBounds) Then
Me.StartPosition = FormStartPosition.Manual
Me.DesktopBounds = My.Settings.WindowBounds
End If
If Not My.Settings.WindowState = FormWindowState.Minimized Then
Me.WindowState = My.Settings.WindowState
End If
End Sub
Private Sub TrackWindowLocation()
If loadingComplete Then
If Me.WindowState = FormWindowState.Normal Then
My.Settings.WindowBounds = Me.DesktopBounds
My.Settings.WindowState = Me.WindowState
End If
End If
End Sub
Private Sub SaveWindowLocation()
If Not Me.WindowState = FormWindowState.Minimized Then
My.Settings.WindowState = Me.WindowState
End If
If Me.WindowState = FormWindowState.Normal Then
My.Settings.WindowBounds = Me.DesktopBounds
End If
My.Settings.Save()
End Sub
Private Function IsRectangleVisible(Rectangle As Rectangle) As Boolean
For Each screen As Screen In screen.AllScreens
Dim r As Rectangle = Rectangle.Intersect(Rectangle, screen.WorkingArea)
If Not r.IsEmpty Then
If r.Width > 50 And r.Height > 50 Then Return True
End If
Next
Return False
End Function
End Class