I have made a class which a form can inherit from and it handles form Location, Size and State. And it works nicely. Except for one thing:
When you maximize the application on a different screen than your main one, the location and size (before you maximized) gets stored correctly, but when it is maximized (according to its previous state) it is maximized on my main monitor. When I then restore it to normal state, it goes to the other screen where it was before. When I then maximize it again, it of course maximized on the correct screen.
So my question is... how can I make a form, when it is maximized, remember what screen it was maximized on? And how do I restore that when the form opens again?
I accepted the answer which had a very good tip about how to if on screen. But that was just part of my problem, so here is my solution:
On load
Bounds
and WindowState
from whatever storage.Bounds
.Bounds
are visible either by Screen.AllScreens.Any(ø => ø.Bounds.IntersectsWith(Bounds))
or MdiParent.Controls.OfType<MdiClient>().First().ClientRectangle.IntersectsWith(Bounds)
.
Location = new Point();
.On closing
WindowState
.WindowState
is FormWindowState.Normal
, then store Bounds
, otherwise store RestoreBounds
.And thats it! =)
So, as suggested by Oliver, here is some code. It needs to be fleshed out sort of, but this can be used as a start for whoever wants to:
Takes care of storing and fetching the data somewhere.
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;
}
}
Note 1: In the load method you have to look for previously stored WindowState
, WindowBounds
and other values. We use SQL Server, and have a Window
table with columns for Id
, Name
, MachineName
(for Environment.MachineName
), UserId
, WindowState
, X
, Y
, Height
, Width
. So for every window, you would have one row with WindowState
, X
, Y
, Height
and Width
for each user and machine. In addition we have a WindowValues
table which just has a foreign key to WindowId
, a Key
column of type String
and a Value
column of type Binary
. If there is stuff that is not found, I just leave things default and return false.
Note 2: In the save method you then, of course do the reverse from what you do in the Load method. Creating rows for Window
and WindowValues
if they don't exist already for the current user and machine.
This class uses the previous class and forms a handy base class for other forms.
// 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();
}
}
And thats pretty much it. To use it, a form would just inherit from the PersistentFormBase. That would automatically take care of bounds and state. If anything else should be stored, like a splitter distance, you would listen for the ValuesLoaded
and StoringValues
events and in those use the GetValue
and StoreValue
methods.
Hope this can help someone! Please let me know if it does. And also, please provide some feedback if there is anything you think could be done better or something. I would like to learn =)
C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...
In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.
What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.
C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.
There's no built in way to do this - you'll have to write the logic yourself. One reason for this is that you have to decide how to handle the case where the monitor that the window was last shown on is no longer available. This can be quite common with laptops and projectors, for example. The Screen class has some useful functionality to help with this, although it can be difficult to uniquely and consistently identify a display.
I found a solution to your problem by writing a little functio, that tests, if a poitn is on a connected screen. The main idea came from http://msdn.microsoft.com/en-us/library/system.windows.forms.screen(VS.80).aspx but some modifications were needed.
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;
}
There are a few issues with the above solution.
On multiple screens as well as if the restore screen is smaller.
It should use Contains(...), rather than IntersectsWith as the control part of the form might otherwise be outside the screen-area.
I will suggest something along these lines
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));
}
and used like this. (Note that I let Windows handle it if the saved values does not work)
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With