resolucion - formulario pantalla completa c#
C#: cómo hacer que un formulario recuerde sus límites y WindowState(teniendo en cuenta las configuraciones de monitor dual) (4)
Hice una clase de la cual un formulario puede heredar y maneja desde Ubicación, Tamaño y Estado. Y funciona bien Excepto por una cosa:
Cuando maximiza la aplicación en una pantalla diferente a la principal, la ubicación y el tamaño (antes de maximizar) se almacenan correctamente, pero cuando se maximiza (de acuerdo con su estado anterior) se maximiza en mi monitor principal. Cuando luego lo restauro al estado normal, va a la otra pantalla donde estaba antes. Cuando lo vuelvo a maximizar, por supuesto se maximiza en la pantalla correcta.
Entonces mi pregunta es ... ¿cómo puedo hacer un formulario, cuando se maximiza, recordar en qué pantalla se maximizó? ¿Y cómo restaurar eso cuando el formulario se abre de nuevo?
Tipo de solución completa al problema
Acepté la respuesta que tenía un muy buen consejo sobre cómo hacerlo en la pantalla. Pero eso fue solo parte de mi problema, así que aquí está mi solución:
En carga
- Primero obtén
Bounds
yWindowState
almacenados de cualquier almacenamiento. - Luego establece los
Bounds
. - Asegúrese de que los
Bounds
estén visibles porScreen.AllScreens.Any(ø => ø.Bounds.IntersectsWith(Bounds))
oMdiParent.Controls.OfType<MdiClient>().First().ClientRectangle.IntersectsWith(Bounds)
.- Si no lo hace, simplemente haga
Location = new Point();
.
- Si no lo hace, simplemente haga
- Luego establece el estado de la ventana.
Al cierre
- Tienda
WindowState
. - Si
WindowState
esFormWindowState.Normal
, guarde losBounds
, de lo contrario, almaceneRestoreBounds
.
¡Y eso es! =)
Un código de ejemplo
Entonces, como lo sugirió Oliver , aquí hay un código. Tiene que completarse, pero esto se puede utilizar como un comienzo para quien quiera:
PersistentFormHandler
Se ocupa de almacenar y recuperar los datos en algún lugar.
public sealed class PersistentFormHandler
{
/// <summary>The form identifier in storage.</summary>
public string Name { get; private set; }
/// <summary>Gets and sets the window state. (int instead of enum so that it can be in a BI layer, and not require a reference to WinForms)</summary>
public int WindowState { get; set; }
/// <summary>Gets and sets the window bounds. (X, Y, Width and Height)</summary>
public Rectangle WindowBounds { get; set; }
/// <summary>Dictionary for other values.</summary>
private readonly Dictionary<string, Binary> otherValues;
/// <summary>
/// Instantiates new persistent form handler.
/// </summary>
/// <param name="windowType">The <see cref="Type.FullName"/> will be used as <see cref="Name"/>.</param>
/// <param name="defaultWindowState">Default state of the window.</param>
/// <param name="defaultWindowBounds">Default bounds of the window.</param>
public PersistentFormHandler(Type windowType, int defaultWindowState, Rectangle defaultWindowBounds)
: this(windowType, null, defaultWindowState, defaultWindowBounds) { }
/// <summary>
/// Instantiates new persistent form handler.
/// </summary>
/// <param name="windowType">The <see cref="Type.FullName"/> will be used as base <see cref="Name"/>.</param>
/// <param name="id">Use this if you need to separate windows of same type. Will be appended to <see cref="Name"/>.</param>
/// <param name="defaultWindowState">Default state of the window.</param>
/// <param name="defaultWindowBounds">Default bounds of the window.</param>
public PersistentFormHandler(Type windowType, string id, int defaultWindowState, Rectangle defaultWindowBounds)
{
Name = string.IsNullOrEmpty(id)
? windowType.FullName
: windowType.FullName + ":" + id;
WindowState = defaultWindowState;
WindowBounds = defaultWindowBounds;
otherValues = new Dictionary<string, Binary>();
}
/// <summary>
/// Looks for previously stored values in database.
/// </summary>
/// <returns>False if no previously stored values were found.</returns>
public bool Load()
{
// See Note 1
}
/// <summary>
/// Stores all values in database
/// </summary>
public void Save()
{
// See Note 2
}
/// <summary>
/// Adds the given <paramref key="value"/> to the collection of values that will be
/// stored in database on <see cref="Save"/>.
/// </summary>
/// <typeparam key="T">Type of object.</typeparam>
/// <param name="key">The key you want to use for this value.</param>
/// <param name="value">The value to store.</param>
public void Set<T>(string key, T value)
{
// Create memory stream
using (var s = new MemoryStream())
{
// Serialize value into binary form
var b = new BinaryFormatter();
b.Serialize(s, value);
// Store in dictionary
otherValues[key] = new Binary(s.ToArray());
}
}
/// <summary>
/// Same as <see cref="Get{T}(string,T)"/>, but uses default(<typeparamref name="T"/>) as fallback value.
/// </summary>
/// <typeparam name="T">Type of object</typeparam>
/// <param name="key">The key used on <see cref="Set{T}"/>.</param>
/// <returns>The stored object, or the default(<typeparamref name="T"/>) object if something went wrong.</returns>
public T Get<T>(string key)
{
return Get(key, default(T));
}
/// <summary>
/// Gets the value identified by the given <paramref name="key"/>.
/// </summary>
/// <typeparam name="T">Type of object</typeparam>
/// <param name="key">The key used on <see cref="Set{T}"/>.</param>
/// <param name="fallback">Value to return if the given <paramref name="key"/> could not be found.
/// In other words, if you haven''t used <see cref="Set{T}"/> yet.</param>
/// <returns>The stored object, or the <paramref name="fallback"/> object if something went wrong.</returns>
public T Get<T>(string key, T fallback)
{
// If we have a value with this key
if (otherValues.ContainsKey(key))
{
// Create memory stream and fill with binary version of value
using (var s = new MemoryStream(otherValues[key].ToArray()))
{
try
{
// Deserialize, cast and return.
var b = new BinaryFormatter();
return (T)b.Deserialize(s);
}
catch (InvalidCastException)
{
// T is not what it should have been
// (Code changed perhaps?)
}
catch (SerializationException)
{
// Something went wrong during Deserialization
}
}
}
// Else return fallback
return fallback;
}
}
Nota 1: en el método de carga, debe buscar WindowState
, WindowBounds
y otros valores previamente almacenados. Utilizamos SQL Server y tenemos una tabla de Window
con columnas para Id
, Name
, MachineName
(para Environment.MachineName
), UserId
, WindowState
, X
, Y
, Height
, Width
. Entonces, para cada ventana, tendría una fila con WindowState
, X
, Y
, Height
y Width
para cada usuario y máquina. Además, tenemos una tabla WindowValues
que solo tiene una clave foránea para WindowId
, una columna Key
de tipo String
y una columna Value
de tipo Binary
. Si hay cosas que no se encuentran, simplemente dejo las cosas predeterminadas y devuelvo falso.
Nota 2: en el método de guardar, entonces, por supuesto, haga lo contrario de lo que hace en el método de carga. Crear filas para Window
y WindowValues
si ya no existen para el usuario y la máquina actuales.
PersistentFormBase
Esta clase usa la clase anterior y forma una clase base útil para otras formas.
// Should have been abstract, but that makes the the designer crash at the moment...
public class PersistentFormBase : Form
{
private PersistentFormHandler PersistenceHandler { get; set; }
private bool handlerReady;
protected PersistentFormBase()
{
// Prevents designer from crashing
if (LicenseManager.UsageMode != LicenseUsageMode.Designtime)
{
Load += persistentFormLoad;
FormClosing += persistentFormFormClosing;
}
}
protected event EventHandler<EventArgs> ValuesLoaded;
protected event EventHandler<EventArgs> StoringValues;
protected void StoreValue<T>(string key, T value)
{
if (!handlerReady)
throw new InvalidOperationException();
PersistenceHandler.Set(key, value);
}
protected T GetValue<T>(string key)
{
if (!handlerReady)
throw new InvalidOperationException();
return PersistenceHandler.Get<T>(key);
}
protected T GetValue<T>(string key, T fallback)
{
if (!handlerReady)
throw new InvalidOperationException();
return PersistenceHandler.Get(key, fallback);
}
private void persistentFormLoad(object sender, EventArgs e)
{
// Create PersistenceHandler and load values from it
PersistenceHandler = new PersistentFormHandler(GetType(), (int) FormWindowState.Normal, Bounds);
PersistenceHandler.Load();
handlerReady = true;
// Set size and location
Bounds = PersistenceHandler.WindowBounds;
// Check if we have an MdiParent
if(MdiParent == null)
{
// If we don''t, make sure we are on screen
if (!Screen.AllScreens.Any(ø => ø.Bounds.IntersectsWith(Bounds)))
Location = new Point();
}
else
{
// If we do, make sure we are visible within the MdiClient area
var c = MdiParent.Controls.OfType<MdiClient>().FirstOrDefault();
if(c != null && !c.ClientRectangle.IntersectsWith(Bounds))
Location = new Point();
}
// Set state
WindowState = Enum.IsDefined(typeof (FormWindowState), PersistenceHandler.WindowState) ? (FormWindowState) PersistenceHandler.WindowState : FormWindowState.Normal;
// Notify that values are loaded and ready for getting.
var handler = ValuesLoaded;
if (handler != null)
handler(this, EventArgs.Empty);
}
private void persistentFormFormClosing(object sender, FormClosingEventArgs e)
{
// Set common things
PersistenceHandler.WindowState = (int) WindowState;
PersistenceHandler.WindowBounds = WindowState == FormWindowState.Normal ? Bounds : RestoreBounds;
// Notify that values will be stored now, so time to store values.
var handler = StoringValues;
if (handler != null)
handler(this, EventArgs.Empty);
// Save values
PersistenceHandler.Save();
}
}
Y eso es todo. Para usarlo, un formulario simplemente heredará de PersistentFormBase. Eso automáticamente se ocuparía de los límites y el estado. Si se almacenara algo más, como una distancia del divisor, escucharía los eventos ValuesLoaded
y StoringValues
y en esos usaría los métodos GetValue
y StoreValue
.
¡Espero que esto pueda ayudar a alguién! Por favor, hágamelo saber si lo hace. Y también, por favor, proporcione algunos comentarios si hay algo que cree que podría hacerse mejor o algo así. Me gustaría aprender =)
Intente generar su formulario principal en su ubicación guardada en estado restaurado (no maximizado), ENTONCES maximícelo si se maximizó el último estado.
Como dijo Stu, ten cuidado con los monitores eliminados en este caso. Dado que la ubicación guardada puede contener coordenadas fuera de la pantalla (incluso las negativas), puede terminar de forma efectiva con una ventana invisible (fuera de la pantalla, en realidad). Creo que revisar los límites del escritorio antes de cargar el estado anterior debería evitar esto.
No hay una forma de hacerlo, tendrás que escribir la lógica tú mismo. Una razón para esto es que debe decidir cómo manejar el caso donde el monitor en el que se mostró por última vez la ventana ya no está disponible. Esto puede ser bastante común con computadoras portátiles y proyectores, por ejemplo. La clase Screen tiene algunas funciones útiles para ayudar con esto, aunque puede ser difícil identificar de manera única y consistente una pantalla.
Encontré una solución a su problema escribiendo una pequeña función, que prueba si hay un punto en la pantalla conectada. La idea principal provino de http://msdn.microsoft.com/en-us/library/system.windows.forms.screen(VS.80).aspx pero se necesitaban algunas modificaciones.
public static bool ThisPointIsOnOneOfTheConnectedScreens(Point thePoint)
{
bool FoundAScreenThatContainsThePoint = false;
for(int i = 0; i < Screen.AllScreens.Length; i++)
{
if(Screen.AllScreens[i].Bounds.Contains(thePoint))
FoundAScreenThatContainsThePoint = true;
}
return FoundAScreenThatContainsThePoint;
}
Hay algunos problemas con la solución anterior.
En pantallas múltiples, así como si la pantalla de restauración es más pequeña.
Debe usar Contiene (...), en lugar de Intersectos, ya que la parte de control del formulario podría estar fuera del área de la pantalla.
Sugeriré algo a lo largo de estas líneas
bool TestBounds(Rectangle R) {
if (MdiParent == null) return Screen.AllScreens.Any(ø => ø.Bounds.Contains(R)); // If we don''t have an MdiParent, make sure we are entirely on a screen
var c = MdiParent.Controls.OfType<MdiClient>().FirstOrDefault(); // If we do, make sure we are visible within the MdiClient area
return (c != null && c.ClientRectangle.Contains(R));
}
y usado así (Tenga en cuenta que dejo que Windows lo maneje si los valores guardados no funcionan)
bool BoundsOK=TestBounds(myBounds);
if (!BoundsOK) {
myBounds.Location = new Point(8,8); // then try (8,8) , to at least keep the size, if other monitor not currently present, or current is lesser
BoundsOK = TestBounds(myBounds);
}
if (BoundsOK) { //Only change if acceptable, otherwise let Windows handle it
StartPosition = FormStartPosition.Manual;
Bounds = myBounds;
WindowState = Enum.IsDefined(typeof(FormWindowState), myWindowState) ? (FormWindowState)myWindowState : FormWindowState.Normal;
}