 |
|
 |
I did have applications that had implement persisting windows' size and position, though some casual coding, that is, no structure.
This article does give me some hints that I should tidy my codes.
However, I think, it is better that the form (consumer) has no knowledge of class PersitWindowState. That is, rather than having the form to subscribe the events of class PersitWindowState, let PersitWindowState subscribe OnClose event of the form and application.OnClose. And PersitWindowState has better not to live inside the form. In short, just keep the coupling between two classes as little as possible.
In addition, rather than saving the data in registry, I think it is more flexible to provide builder pattern. And let the concrete builders to decide where to save the data, it could be registry, configuration file, proprietary XML or database.
I will probably draft an article for this if I find nobody come up.
Zijian
|
|
|
|
 |
|
 |
The suggested solution requires actually most of the time just 1 line in the constructor of the parent Form:
new PersistWindowState(this, @"Software\YourCompany\YourApp");
You don't need to keep reference to the PersistWindowState as a member, because it won't be collected - it registered for Form's events. Of course, small obvious change to the constructor or original PersistWindowState is required:
public PersistWindowState(Form parent, string registryPath) {
this.m_parent = parent;
this.m_registryPath = registryPath;
// subscribe to parent form's events
parent.Closing += new System.ComponentModel.CancelEventHandler(OnClosing);
parent.Resize += new System.EventHandler(OnResize);
parent.Move += new System.EventHandler(OnMove);
parent.Load += new System.EventHandler(OnLoad);
// get initial width and height in case form is never resized
m_normalWidth = parent.Width;
m_normalHeight = parent.Height;
}
|
|
|
|
 |
|
 |
Using the form's Size property or the Bounds property may be off in certain situations. Use the RestoreBounds property instead. This appears to hold the size of the form in it's normal state, according to this MSDN article. This is available in .NET 2.0.
http://msdn2.microsoft.com/en-gb/library/system.windows.forms.form.restorebounds.aspx
|
|
|
|
 |
|
 |
One thing that most people forget when saving Window Positions is that some people use multiple screens. If this isn't taken into consideration when saving/retrieving window coordinates, windows can be 'lost' if the positioning of a secondary monitor is changed in between uses of an application, causing a user great frustration. Microsoft Photo Editor (included with Office 2000) had this problem, and I had to go into the registry to fix the screen position if I changed my screen configurations between uses.
Window positions are saved with respect to the primary monitor's top left corner (this being x,y coordinate 0,0). This means that if a secondary monitor (Set up in Windows Display Properties) was positioned to the left and above the primary monitor, then it would have negative form.Top and form.Left properties. Now all this is fine, until the configuration of the monitors change. Consider the following two scenarios:
A - Laptop User> I use a laptop, and when it's docked, I use a dual monitor configuration. If I exit an application which saves a form's position, where that form is on the non-primary monitor, then undock my laptop (leaving only a primary monitor - the laptop monitor), then when I re-run the application, if it doesn't do a check to see whether the form is able to be displayed, then the form is lost.
B - Repositioning of 2nd Form> I have two monitors, and the second is positioned to the left of the primary monitor. I exit an application that saves form position, leaving the form on the secondary monitor. Once the application is closed, I reposition the secondary monitor to be to the right of the first. When re-starting the application, the window will be lost unless a check is performed to determine whether the form is visible.
The following code, which I have used in a static class, performs a check on a form to ensure it is visible, and if not, partially repositions the form to ensure the user can adjust it.
///
/// Ensures that the form is visible on the current screen -
/// to be called after the form has been repositioned based
/// on saved settings
///
/// Form to be shown on screen
public static void EnsureFormVisible(
System.Windows.Forms.Form form)
{
#region Ensure that the window is visible
Screen currentScreen = Screen.FromHandle(form.Handle);
// Ensure top visible
if((form.Top < currentScreen.Bounds.Top) ||
((form.Top + form.Height) > (currentScreen.Bounds.Top + currentScreen.Bounds.Height)))
{
form.Top = currentScreen.Bounds.Top;
}
// Ensure at least 60 px of Title Bar visible
if(((form.Left + form.Width - 60) < currentScreen.Bounds.Left) ||
((form.Left + 60) > (currentScreen.Bounds.Left + currentScreen.Bounds.Width)))
{
form.Left = currentScreen.Bounds.Left;
}
#endregion
}
|
|
|
|
 |
|
 |
There is hardly a thing that cannot be improved...
At first I was happy to find your code that solved my problem, but I did not like two things: First, whenever the window was just a little bit off the lower border of the screen, it snapped back to the top. So I changed the code to check the two cases separately. Second thing is the Task bar. The window can be hidden beneath the task bar and docked tool bars, especially when they are is at the top of the screen. Here is the code that I ended up with:
///
/// Ensures that the form is visible on the current screen -
/// to be called after the form has been repositioned based
/// on saved settings
///
/// Form to be shown on screen
public static void EnsureFormVisible(Form form)
{
Rectangle bounds = bounds = Screen.GetWorkingArea(form);
// Ensure top visible
if (form.Top < bounds.Top) {
form.Top = bounds.Top;
}
// Move the form up if it is too low
if (form.Top + 40 > bounds.Bottom) {
form.Top = bounds.Bottom - 40;
}
// MOve form inside screen horizontally
if (form.Right - 80 < bounds.Left) {
form.Left = bounds.Left - form.Width + 80 ;
}
if (form.Left + 60 > bounds.Right) {
form.Left = bounds.Right - 60;
}
}
|
|
|
|
 |
|
 |
It's all about continuous improvement! I've put your updated code in my Windows Form app, and next time I'm doing development on it, I'll give it a burl. Thanks for sharing your updates!
|
|
|
|
 |
|
 |
I dont know if anyone is using this thread anymore. Ive tried this code but Im having a slight problem with it.
If I dont expand the taskbar to both monitors the code will think that its only running with a single monitor. I can move the window to the second screen but when I start the application its located at the edge af screen one instead of on screen 2.
I run at res 1600x1200 on both monitors with taskbar only on primary screen. "2 displayes, Independent mode(including features)" the Matrox control panel calls this setting.
Screen.GetWorkingArea(New Point(0, 0))
The above will return 3200 for width if I have the taskbar expanded and 1600 if I dont.
So how do I figure out my little problem?
Will I have to remember which screen the app was closed on and on startup check to see if it is still available and have the right placement? (screen could be moved to other side of primary while being closed)
Someone have a good idear for this?
|
|
|
|
 |
|
 |
This works great. (Many thanks to the author!)
But I can't figure out the "right" way to perform initialization based on the additional entries stored in the registry. For example I have various settings that are loaded from an XML file. the XML file name is stored in the member variable
m_FieldsErrorsFile
This code fails in the form constructor because PersistWindowState hasn't loaded the saved value of m_FieldsErrorsFile:
if (m_FieldsErrorsFile.Length > 0)
LoadFields();
Instead I'm using a trick from dealin with THREED from VB5 & VB6. I add a timer control to the form, traditionally named tmrInit, Enabled=True & Interval = 250. Here is the code that successfully initializes the fields from the XML config file.
private void tmrInit_Tick(object sender, System.EventArgs e) {
tmrInit.Enabled = false; // Turn off timer
if (m_FieldsErrorsFile.Length > 0)
LoadFields();
}
Although this "works" it's very "ugly." I looked through MSDN for the event:
Form1.FormIsLoadedAndPositionedSoDoAllFinialInitsNow
but didn't find anything....
Suggestions? Thanks!
-- Mark
|
|
|
|
 |
|
 |
Hey everybody,
I have taken Joel's code and extended it to allow XML saving and also allow the developer to setup up his own saving methodology.
I have also converted it to a component so if you are using VS.NET, you can add it to your component toolbar and drag and drop. It then auto binds to the form and set the saving method via the property window.
You can still use it via code like Joel if not using VS.NET.
My article is located at Persist Window State
Angus
3 out of every 4 people make up 75% of the worlds' population
|
|
|
|
 |
|
 |
I've done a quick translation of this code to VB. I would gladly submit it to this forum, but I'm a newbie and don't know how.
|
|
|
|
 |
|
 |
A small improvement: to not serialize the size when FormBorderStyle differ from FormBorderStyle.Sizable or FormBorderStyle.SizableToolWindow.
In my project, I have some fixed size forms and the controls are fixed in their position. When I have changed the size of the forms from designer and I have run the program, the old size has been restored.
I've modified:
m_parent.Size = new Size(width, height);
to
if (m_parent.FormBorderStyle == FormBorderStyle.Sizable ||
m_parent.FormBorderStyle == FormBorderStyle.SizableToolWindow)
m_parent.Size = new Size(width, height);
|
|
|
|
 |
|
 |
First, let me thank you for the simple but very useful class!
I've changed the constructor to require a "this" and an optional registry key. If you don't pass a key, the name of the assembly is used. This lessens the required work for each application your team writes.
public PersistWindowState(Form parent) {
ConstructorCommon(parent, null);
}
public PersistWindowState(Form parent, string regPath) {
ConstructorCommon(parent, regPath);
}
private void ConstructorCommon(Form parent, string regPath) {
if (regPath==null) {
Assembly a = Assembly.GetExecutingAssembly();
m_regPath = @"Software\MyCompany\"+a.GetName().Name;
} else {
m_regPath = regPath;
}
m_parent = parent;
// subscribe to parent form's events
m_parent.Closing += new System.ComponentModel.CancelEventHandler(OnClosing);
m_parent.Resize += new System.EventHandler(OnResize);
m_parent.Move += new System.EventHandler(OnMove);
m_parent.Load += new System.EventHandler(OnLoad);
// get initial width and height in case form is never resized
m_normalWidth = m_parent.Width;
m_normalHeight = m_parent.Height;
}
Then all your calling app has to do is this:
public AppForm() {
m_windowState = new PersistWindowState(this);
}
Joel, your thoughts???
donavon
http://donavon.com
|
|
|
|
 |
|
 |
The only issue with this is that you have hard coded "Software\MyCompany\" which makes this code less useful for general use.
Joel
VssConnect - Remote SourceSafe(r) Access http://www.voxcode.com[^]
|
|
|
|
 |
|
 |
I agree with Joel, that using a hard-coded "MyCOmpany" is bad idea. Instead, I propose to use the parent's CompanyName property. It's set by the AssemblyCompanyAttribute.
private void ConstructorCommon(Form parent, string regPath)
{
if (regPath==null)
{
Assembly a = Assembly.GetExecutingAssembly();
m_regPath = @"Software\" + parent.CompanyName + "\" + a.GetName().Name;
}
else
{
m_regPath = regPath;
}
}
Regards
Thomas
Disclaimer: Because of heavy processing requirements, we are currently using some of your unused brain capacity for backup processing. Please ignore any hallucinations, voices or unusual dreams you may experience. Please avoid concentration-intensive tasks until further notice. Thank you.
|
|
|
|
 |
|
 |
public class PersistentForm : System.Windows.Forms.Form {
private string registryPath;
public string RegistryPath {
set {
registryPath = "WindowPlacements\\"+value;
}
get {
return registryPath;
}
}
public PersistentForm() {
registryPath = "WindowPlacements\\"+this.GetType().ToString();
}
///
/// Load placement and state from Win32 registry
///
private void LoadWindowPlacement() {
ApplicationRegistry key = ApplicationRegistry.OpenSubKey(registryPath);
if(key != null) {
this.SuspendLayout();
this.Bounds = new Rectangle((int)key.GetValue("Left"),(int)key.GetValue("Top"),(int)key.GetValue("Width"),(int)key.GetValue("Height"));
this.WindowState = (FormWindowState) key.GetValue("WindowState");
this.ResumeLayout();
}
}
protected override void OnLoad(System.EventArgs e) {
LoadWindowPlacement();
}
///
/// Save placement and style into Win32 registry
///
private void SaveWindowPlacement() {
ApplicationRegistry key = ApplicationRegistry.CreateSubKey(registryPath);
key.SetValue("Left", this.Bounds.Left);
key.SetValue("Top", this.Bounds.Top);
key.SetValue("Width", this.Bounds.Width);
key.SetValue("Height", this.Bounds.Height);
key.SetValue("WindowState",(int)this.WindowState);
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e) {
SaveWindowPlacement();
}
}
note : ApplicationRegistry is similar to .NET RegistryKey
Wiizi
|
|
|
|
 |
|
 |
I can't find 'ApplicationRegistry'. Is this a FCL class? If so what is the full path?
Joel
VssConnect - Remote SourceSafe(r) Access http://www.voxcode.com[^]
|
|
|
|
 |
|
 |
It's same as Microsoft.Win32.RegistryKey.
Wiizi
|
|
|
|
 |
|
 |
How is this better in your opinion? I didn't want to to derive from Form because I wanted to be able to add the functionality to any form without changing it's derivation structure. I think you missed the point about having to use the Resize event or else if you close the form when it is maximized you will loose the normalized form size.
Joel
VssConnect - Remote SourceSafe(r) Access http://www.voxcode.com[^]
|
|
|
|
 |
|
 |
Thanks, rather useful.
You mentioned in the body of the article that you saw the form move when working with pre-release(s) of .NET.
Well, if you set the form's WindowState to Maximized, then you see this effect. It's not a problem for me, just thought I'd let you know.
Hugh Robinson
|
|
|
|
 |
|
 |
He's look like a child.
|
|
|
|
 |
|
 |
Let's just say this picture is not exectly current (I was born in 1967).
Joel
|
|
|
|
 |
|
 |
I have a class something like this for VB as well. It does come in handy.
One thing that I do is create another hierarchy to insure that "Left" is associated with a specific form/window, that way I can save the settings for more than one window.
The easiest way to do this would be to set m_regPath. For example, in "public string RegistryPath set":
m_regPath = value + "\\" + m_parent.Text
The only problem with is is that .Text must resolve to a legitimate registry key and can't change.
-John
|
|
|
|
 |
|
 |
BonTon wrote:
The easiest way to do this would be to set m_regPath. For example, in "public string RegistryPath set":
m_regPath = value + "\\" + m_parent.Text
The only problem with is is that .Text must resolve to a legitimate registry key and can't change.
How about adding another property with WindowID or something like that, and if not empty, it is prepended to the property names (Left, Top, etc.).
-- LuisR
──────────────
Luis Alonso Ramos
Chihuahua, Mexico
www.luisalonsoramos.com
"Do not worry about your difficulties in mathematics, I assure you that mine are greater." -- Albert Einstein
|
|
|
|
 |
|
 |
What I have done is use the namespace/type information to create the sub key. This would work in most situations.
To handle those situations where the namespace/type information would not work, add a property that is called "KeyName" or some such. By default, when the Parent property is set, set the value of KeyName to the namespace/type information. Then, if the developer doesn't want to use that, they can manually set (or clear) the value of the sub key.
|
|
|
|
 |
|
 |
First, let me say that your idea and implementation is great.
A couple of minor comments, though:
1. Since you're only persisting the form state when the form is closing, why bother hooking the OnMove and OnResize events at all? Just extract the form's current state in your OnClosing handler. Maybe I'm missing something obvious?
2. Some people might want to write their state data somewhere other than the registry (like say in an XML file alongside the executable). I'd create a simple base class or interface that abstracts the state data read and write methods. You could then create derived classes or interface implementations that perform the actual registry and/or XML access. Then, when creating the PersistWindowState object you would simply pass in an instance of one of the concrete "persister" classes.
-Phil
|
|
|
|
 |