modificar - saving user settings c#
Restauración del tamaño/posición de la ventana con múltiples monitores (6)
Este es el perfecto creo basado en sus respuestas y comentarios.
Esta solución es para guardar / restaurar el tamaño y la posición del formulario con múltiples monitores + soporte para formularios de documentos múltiples , formularios múltiples o múltiples . No es un formulario MDI, sino Microsoft Word como un documento múltiple con una instancia de formulario principal diferente.
Gracias a VVS, msorens e Ian Goldby. Combino la solución de VVS, msorens y MSDN Application.Run Method (ApplicationContext) ejemplo para hacer el multi MainForm pero no MDI.
Esta solución incluye el comentario de Ian Goldby que usa Form.RestoreBounds
para eliminar OnResize()
, OnMove()
y TrackWindowState()
.
También me fijo en recordar el Monitor cuando el Formulario se mueve al otro Monitor y se maximiza antes de la salida porque no hago el seguimiento de OnResize, OnMove. Con esta solución, esta solución es compatible con la función Ajustar a Windows 7, que puede arrastrar la barra de título o la tecla Win + Flecha para ajustar la ventana del Formulario en cualquier borde del monitor o maximizarlo / normalizarlo o minimizarlo.
Esta solución se implementó en el Programa pero no en el Formulario principal para admitir el Formulario principal múltiple. Sin embargo, también puede usarlo para el formulario principal individual.
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();
}
}
}
Editar: se agregó el método PreventSameLocation para asegurarse de que el segundo formulario no se abra exactamente sobre el 1er formulario y el usuario se dará cuenta del formulario recién abierto.
Muchas publicaciones sobre cómo restaurar una posición y tamaño de WinForm.
Ejemplos:
- www.stackoverflow.com/questions/92540/save-and-restore-form-position-and-size
- www.codeproject.com/KB/dialog/restoreposition.aspx?fid=1249382&df=90&mpp=25&noise=3&sort=Position&view=Quick&select=2595746
Pero todavía tengo que encontrar el código para hacer esto con múltiples monitores.
Es decir, si cierro mi aplicación .NET Winform con la ventana en el monitor 2, quiero que guarde el tamaño, la ubicación y el estado de la ventana en la configuración de la aplicación, para que luego pueda restaurar el monitor 2 cuando reinicie la aplicación. Sería bueno si, como en el ejemplo anterior del proyecto de código, incluye algunas verificaciones de cordura, como si la ubicación guardada estuviera fuera de la pantalla y la "corrige". O si la ubicación guardada está en un monitor que ya no está allí (por ejemplo, mi computadora portátil ahora está sola sin mi segundo monitor), entonces la mueve correctamente al monitor 1.
¿Alguna idea?
Mi entorno: C #, .NET 3.5 o inferior, VS2008
¡La respuesta proporcionada por VVS fue de gran ayuda! Sin embargo, encontré dos problemas menores, así que vuelvo a publicar la mayor parte de su código con estas revisiones:
(1) La primera vez que se ejecuta la aplicación, el formulario se abre en un estado Normal pero tiene un tamaño tal que aparece solo como una barra de título. Agregué un condicional en el constructor para arreglar esto.
(2) Si la aplicación se cierra mientras se minimiza o se maximiza, el código en OnClosing no recuerda las dimensiones de la ventana en su estado Normal. (Las 3 líneas de código - que ahora he comentado - parecen razonables, pero por alguna razón simplemente no funciona). Afortunadamente, había resuelto este problema anteriormente y lo he incluido en una nueva región al final del código. para rastrear el estado de la ventana a medida que sucede en lugar de esperar el cierre.
Con estas dos correcciones en su lugar, he probado:
A. cierre en estado normal - restaura al mismo tamaño / posición y estado
B. cierre en estado minimizado - restaura al estado normal con el último tamaño / posición normal
C. cierre en estado maximizado: restaura al estado maximizado y recuerda su último tamaño / posición cuando más tarde se ajusta al estado normal.
D. cierre en el monitor 2 - restaura para monitorear 2.
E. Cerrar el monitor 2 y luego desconectar el monitor 2 - Restaurar a la misma posición en el monitor 1
David: tu código me permitió alcanzar los puntos D y E casi sin esfuerzo, no solo proporcionaste una solución para mi pregunta, sino que la proporcionaste en un programa completo, así que la tuve en funcionamiento casi a los pocos segundos de pegarla en Visual Studio. . ¡Muchísimas gracias por eso!
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
}
Esta es una pregunta antigua, pero aquí hay una versión de VB basada en las respuestas anteriores.
Un problema con las respuestas sugeridas por VVS y Michael Sorens es que una posición guardada que solo muestra un par de píxeles en una pantalla cuenta como visible. Esta solución requiere al menos 50x50 píxeles en la intersección antes de restaurar la ubicación anterior.
Configuración:
<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>
Formar:
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
La mayoría de las otras soluciones aquí se basan en la determinación manual del posicionamiento actual de cada monitor. Los casos extremos son extremadamente difíciles de descubrir, y muy pocas aplicaciones pueden hacerlo bien.
La función SetWindowPlacement dentro de Windows maneja correctamente todos los casos extremos: si la ventana se posicionaría fuera de una pantalla visible, se ajustará en consecuencia.
El mejor ejemplo que he visto en C # está en el blog de David Rickard. No solo muestra cómo usar SetWindowPlacement, sino que también muestra cómo serializar el resultado completo. http://blogs.msdn.com/b/davidrickard/archive/2010/03/09/saving-window-size-and-location-in-wpf-and-winforms.aspx
Prueba este código Puntos de interés:
- Comprueba si la ventana es (parcialmente) visible en el área de trabajo de cualquier pantalla. Por ejemplo, arrastrarlo por detrás de la barra de tareas o moverlo completamente fuera de la pantalla restablece la posición predeterminada de Windows.
- Guarda los límites correctos incluso si el formulario se minimiza o se maximiza (error común)
- Guarda el WindowState correctamente. El diseño de SaveWindowState.Minimized está deshabilitado.
Los límites y el estado se almacenan en las aplicaciones con su tipo correspondiente, por lo que no es necesario realizar ningún análisis de cadenas. Deje que el marco haga su magia de serialización.
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();
}
}
El archivo de configuración para referencia:
<?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>
Si tiene varios monitores, creo que las dimensiones de la interfaz de usuario de la pantalla son simplemente más grandes. Entonces el enfoque normal de "1 monitor" de almacenar y restaurar la ubicación simplemente funcionará. No lo he intentado porque estoy lejos de mi segundo monitor, pero no debería ser difícil de probar. La forma en que hiciste la pregunta parece que no la has probado.
Su segundo requisito significa que deberá verificar las dimensiones máximas de vista al restaurar la aplicación, y luego reposicionar según sea necesario. Para hacer esto último, utilizo este código:
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);
}
Llamo a este método al restaurar el formulario. Guardo la geometría de la pantalla en el registro en el formulario cerrar, y luego leo la geometría en el formulario abierto. Obtengo los límites, pero luego restrinjo los límites restaurados a la pantalla actual real, utilizando el método anterior.
Ahorre al cerrar:
// 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));
Restaurar en forma abierta:
// 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]));
}