保存和恢复表格位置和大小

Posted

技术标签:

【中文标题】保存和恢复表格位置和大小【英文标题】:Save and Restore Form Position and Size 【发布时间】:2010-09-10 16:32:42 【问题描述】:

在 WinForms 2.0 C# 应用程序中,在应用程序中保存和恢复表单位置和大小的典型方法是什么?

相关,是否可以在运行时添加新的用户范围的应用程序设置?我完全了解如何在设计时添加设置,这不是问题。但是如果我想在运行时创建一个呢?

更多细节:

我的应用程序是现有 Visual FoxPro 应用程序的转换。我一直在尝试尽可能多地阅读有关应用程序设置、用户设置等方面的内容,并让自己清楚 .Net 的做事方式,但仍有几件事让我感到困惑。

在 Fox 应用程序中,保存的设置存储在注册表中。我的表单是子类的,并且我有基类代码,可以自动将表单位置和大小保存在注册表中,并键入表单名称。每当我创建一个新表单时,我不需要做任何特别的事情来获得这种行为;它内置在基类中。我的 .Net 表单也是子类化的,这部分运行良好。

在 .Net 中,我得到的印象是我应该使用用户范围的设置来处理用户偏好之类的事情。表单的大小和位置绝对看起来像是用户偏好。但是,我看不到任何将这些设置自动添加到项目中的方法。换句话说,每次我向我的项目添加一个新表单(并且它们是 100 个表单)时,我必须记住添加一个用户范围的应用程序设置,并确保给它提供与表单相同的名称,即“ FormMySpecialSizePosition”来保存大小和位置。我宁愿不必记得这样做。这只是运气不好?或者我是否通过尝试使用用户范围的设置来完全吠叫错误的树?我是否需要创建自己的 XML 文件来保存设置,以便我可以做任何我想做的事情(即在运行时添加新设置)?还是别的什么?

当然,这是一种非常普遍的做法,有人可以说出“正确”的做法。提前致谢!

【问题讨论】:

您可能会发现我不久前提出的一个问题很有用。它为您可以继承的表单库提供了部分完整的解决方案,以使表单保持其边界和位置。您可能只需要将其设置存储在 xml 文件或类似文件中。 【参考方案1】:

您可以创建一个具有通用功能的基本表单类,例如记住位置和大小并从该基类继承。

public class myForm : Form 
protected override void OnLoad()
    //load the settings and apply them
    base.OnLoad();


protected override void OnClose()
    //save the settings
    base.OnClose();


then for the other forms:

public class frmMainScreen : myForm 
// you get the settings for free ;)

嗯,类似的东西;)

【讨论】:

【参考方案2】:

我和你在同一条船上,因为我有许多表单(在我的例子中是 MDI 子级),我想为每个用户保留位置和大小。根据我的研究,不支持在运行时创建应用程序设置。 (见this blog entry) 但是,您不必将所有内容都粘贴在主设置文件中。您可以将设置文件添加到您的项目 (explained here in the MSDN) 并通过 Properties.Settings 对象使用它。这不会减轻必须记住为每个表单创建新设置的痛苦,但至少可以将它们保持在一起,并且不会弄乱您的主要应用程序设置。

至于使用基类来检索设置...我不知道您是否可以在那里进行。我会(并且可能会)做的是命名每个属性,然后使用 Me.GetType().ToString() (我在 VB 中工作)来合成我想要在 Load() 事件中检索的属性的名称每种形式。

【讨论】:

【参考方案3】:
private void Form1_Load( object sender, EventArgs e )

    // restore location and size of the form on the desktop
    this.DesktopBounds =
        new Rectangle(Properties.Settings.Default.Location,
    Properties.Settings.Default.Size);
    // restore form's window state
    this.WindowState = ( FormWindowState )Enum.Parse(
        typeof(FormWindowState),
        Properties.Settings.Default.WindowState);


private void Form1_FormClosing( object sender, FormClosingEventArgs e )

    System.Drawing.Rectangle bounds = this.WindowState != FormWindowState.Normal ? this.RestoreBounds : this.DesktopBounds;
    Properties.Settings.Default.Location = bounds.Location;
    Properties.Settings.Default.Size = bounds.Size;
    Properties.Settings.Default.WindowState =
        Enum.GetName(typeof(FormWindowState), this.WindowState);
    // persist location ,size and window state of the form on the desktop
    Properties.Settings.Default.Save();

【讨论】:

这个解决方案如果在最小化状态下关闭窗口会有问题。 如果您不编写应用程序,此解决方案也不起作用,例如如果您正在编写插件。它也没有提到如何设置或配置不明显且需要文档搜索的属性。【参考方案4】:

我只是将它流式传输到一个单独的 XML 文件 - 快速而肮脏,可能不是你想要的:

Dim winRect As String() = util.ConfigFile.GetUserConfigInstance().GetValue("appWindow.rect").Split(",")
Dim winState As String = util.ConfigFile.GetUserConfigInstance().GetValue("appWindow.state")

Me.WindowState = FormWindowState.Normal

Me.Left = CType(winRect(0), Integer)
Me.Top = CType(winRect(1), Integer)
Me.Width = CType(winRect(2), Integer)
Me.Height = CType(winRect(3), Integer)

If winState = "maximised" Then
    Me.WindowState = FormWindowState.Maximized
End If

Dim winState As String = "normal"
If Me.WindowState = FormWindowState.Maximized Then
    winState = "maximised"
ElseIf Me.WindowState = FormWindowState.Minimized Then
    winState = "minimised"
End If

If Me.WindowState = FormWindowState.Normal Then

    Dim winRect As String = CType(Me.Left, String) & "," & CType(Me.Top, String) & "," & CType(Me.Width, String) & "," & CType(Me.Height, String)
    ' only save window rectangle if its not maximised/minimised
    util.ConfigFile.GetUserConfigInstance().SetValue("appWindow.rect", winRect)
End If

util.ConfigFile.GetUserConfigInstance().SetValue("appWindow.state", winState)

【讨论】:

【参考方案5】:

这里有一些相关的链接可以查看:

Saving out a Form's Size and Location using the Application Settings feature

Any good examples of how to use Applications settings

Exploring Secrets of Persistent Application Settings

【讨论】:

大多数这些链接管理一组设置,一个表单的位置和大小。如果您有多个表单,情况会变得更糟,并且您希望在表单的基类中保持简单。本文处理多种形式:code-magazine.com/article.aspx?quickid=0607031【参考方案6】:

我从某个地方得到了这段代码,但不幸的是当时(很久以前)没有评论我从哪里得到它。

这会将表单信息保存到用户的 HKCU 注册表中:

using System;
using System.Windows.Forms;
using Microsoft.Win32;

/// <summary>Summary description for FormPlacement.</summary>
public class PersistentForm : System.Windows.Forms.Form

    private const string DIALOGKEY = "Dialogs";

    /// <summary></summary>
    protected override void OnCreateControl()
    
        LoadSettings();
        base.OnCreateControl ();
    

    /// <summary></summary>
    protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
    
        SaveSettings();
        base.OnClosing(e);
    

    /// <summary>Saves the form's settings.</summary>
    public void SaveSettings()
    
        RegistryKey dialogKey = Application.UserAppDataRegistry.CreateSubKey(DIALOGKEY);
        if (dialogKey != null)
        
            RegistryKey formKey = dialogKey.CreateSubKey(this.GetType().ToString());
            if (formKey != null)
            
                formKey.SetValue("Left", this.Left);
                formKey.SetValue("Top", this.Top);
                formKey.Close();
            
            dialogKey.Close();
        
    

    /// <summary></summary>
    public void LoadSettings()
    
        RegistryKey dialogKey = Application.UserAppDataRegistry.OpenSubKey(DIALOGKEY);
        if (dialogKey != null)
        
            RegistryKey formKey = dialogKey.OpenSubKey(this.GetType().ToString());
            if (formKey != null)
            
                this.Left = (int)formKey.GetValue("Left");
                this.Top = (int)formKey.GetValue("Top");
                formKey.Close();
            
            dialogKey.Close();
        
    

【讨论】:

【参考方案7】:

实际上,互联网上任何地方都没有一个单一的“有效”解决方案,所以这是我自己的创作:

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.Win32;
using System.ComponentModel;
using System.Security.Cryptography;

namespace nedprod

    abstract public class WindowSettings
    
        private Form form;

        public FormWindowState state;
        public Point location;
        public Size size;

        public WindowSettings(Form _form)
        
            this.form = _form;
        
        internal class MD5Sum
        
            static MD5CryptoServiceProvider engine = new MD5CryptoServiceProvider();
            private byte[] sum = engine.ComputeHash(BitConverter.GetBytes(0));
            public MD5Sum()  
            public MD5Sum(string s)
            
                for (var i = 0; i < sum.Length; i++)
                    sum[i] = byte.Parse(s.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber);
            
            public void Add(byte[] data)
            
                byte[] temp = new byte[sum.Length + data.Length];
                var i=0;
                for (; i < sum.Length; i++)
                    temp[i] = sum[i];
                for (; i < temp.Length; i++)
                    temp[i] = data[i - sum.Length];
                sum=engine.ComputeHash(temp);
            
            public void Add(int data)
            
                Add(BitConverter.GetBytes(data));
            
            public void Add(string data)
            
                Add(Encoding.UTF8.GetBytes(data));
            
            public static bool operator ==(MD5Sum a, MD5Sum b)
            
                if (a.sum == b.sum) return true;
                if (a.sum.Length != b.sum.Length) return false;
                for (var i = 0; i < a.sum.Length; i++)
                    if (a.sum[i] != b.sum[i]) return false;
                return true;
            
            public static bool operator !=(MD5Sum a, MD5Sum b)
            
                return !(a == b);
            
            public override bool Equals(object obj)
            
                try
                
                    return (bool)(this == (MD5Sum)obj);
                
                catch
                
                    return false;
                
            
            public override int GetHashCode()
            
                return ToString().GetHashCode();
            
            public override string ToString()
            
                StringBuilder sb = new StringBuilder();
                for (var i = 0; i < sum.Length; i++)
                    sb.Append(sum[i].ToString("x2"));
                return sb.ToString();
            
        
        private MD5Sum screenconfig()
        
            MD5Sum md5=new MD5Sum();
            md5.Add(Screen.AllScreens.Length); // Hash the number of screens
            for(var i=0; i<Screen.AllScreens.Length; i++)
            
                md5.Add(Screen.AllScreens[i].Bounds.ToString()); // Hash the dimensions of this screen
            
            return md5;
        
        public void load()
        
            using (RegistryKey r = Registry.CurrentUser.OpenSubKey(@"Software\" + CompanyId() + @"\" + AppId() + @"\Window State\" + form.Name))
            
                if (r != null)
                
                    try
                    
                        string _location = (string)r.GetValue("location"), _size = (string)r.GetValue("size");
                        state = (FormWindowState)r.GetValue("state");
                        location = (Point)TypeDescriptor.GetConverter(typeof(Point)).ConvertFromInvariantString(_location);
                        size = (Size)TypeDescriptor.GetConverter(typeof(Size)).ConvertFromInvariantString(_size);

                        // Don't do anything if the screen config has since changed (otherwise windows vanish off the side)
                        if (screenconfig() == new MD5Sum((string) r.GetValue("screenconfig")))
                        
                            form.Location = location;
                            form.Size = size;
                            // Don't restore if miminised (it's unhelpful as the user misses the fact it's opened)
                            if (state != FormWindowState.Minimized)
                                form.WindowState = state;
                        
                    
                    catch (Exception)
                    
                    
                
            
        
        public void save()
        
            state = form.WindowState;
            if (form.WindowState == FormWindowState.Normal)
            
                size = form.Size;
                location = form.Location;
            
            else
            
                size = form.RestoreBounds.Size;
                location = form.RestoreBounds.Location;
            
            using (RegistryKey r = Registry.CurrentUser.CreateSubKey(@"Software\" + CompanyId()+@"\"+AppId() + @"\Window State\" + form.Name, RegistryKeyPermissionCheck.ReadWriteSubTree))
            
                r.SetValue("state", (int) state, RegistryValueKind.DWord);
                r.SetValue("location", location.X.ToString() + "," + location.Y.ToString(), RegistryValueKind.String);
                r.SetValue("size", size.Width.ToString()+","+size.Height.ToString(), RegistryValueKind.String);
                r.SetValue("screenconfig", screenconfig().ToString(), RegistryValueKind.String);
            
        
        abstract protected string CompanyId();
        abstract protected string AppId();
    

此实现将表单的位置和大小存储在 HKCU/Software///Window State/

中。如果显示器配置发生变化,它不会恢复设置,以防止窗口被恢复到屏幕外。

显然这不能处理同一表单的多个实例。我还专门禁用了还原最小化,但这是源代码的简单修复。

上面的内容被设计为放入它自己的 .cs 文件中,并且永远不会再被触及。您必须像这样实例化一个本地命名空间副本(在 Program.cs 或您的插件主 .cs 文件或任何地方):

namespace <your app/plugin namespace name>

    public class WindowSettings : nedprod.WindowSettings
    
        public WindowSettings(Form form) : base(form)  
        protected override string CompanyId()  return "<your company name>"; 
        protected override string AppId()  return "<your app name>"; 
    
    ....

现在您在主命名空间中有一个非抽象实例化。因此,要使用,请将其添加到您要保存和恢复的表单中:

    private void IssuesForm_FormClosing(object sender, FormClosingEventArgs e)
    
        new WindowSettings(this).save();
    

    private void IssuesForm_Load(object sender, EventArgs e)
    
        new WindowSettings(this).load();
    

显然,您可以根据自己的目的随意定制。没有任何明示或暗示的保证。使用风险自负 - 我否认任何版权。

尼尔

【讨论】:

【参考方案8】:

这是我使用的代码。

private void SaveWindowPosition()

    Rectangle rect = (WindowState == FormWindowState.Normal) ?
        new Rectangle(DesktopBounds.Left, DesktopBounds.Top, DesktopBounds.Width, DesktopBounds.Height) :
        new Rectangle(RestoreBounds.Left, RestoreBounds.Top, RestoreBounds.Width, RestoreBounds.Height);
    RegistrySettings.SetSetting("WindowPosition", String.Format("0,1,2,3,4",
        (int)this.WindowState,
        rect.Left, rect.Top, rect.Width, rect.Height));


private void RestoreWindowPosition()

    try
    
        string s = RegistrySettings.GetSetting("WindowPosition", String.Empty) as string;
        if (s != null)
        
            List<int> settings = s.Split(new char[]  ',' , StringSplitOptions.RemoveEmptyEntries)
                                  .Select(v => int.Parse(v)).ToList();
            if (settings.Count == 5)
            
                this.SetBounds(
                    settings[1],
                    settings[2],
                    settings[3],
                    settings[4]);
                this.WindowState = (FormWindowState)settings[0];
            
        
    
    catch  /* Just leave current position if error */ 

我也在我的文章Saving and Restoring a Form's Window Position中介绍了这段代码。

【讨论】:

以上是关于保存和恢复表格位置和大小的主要内容,如果未能解决你的问题,请参考以下文章

HTMLHTML 注册表单案例 ① ( 表格设置 | 设置表格位置和大小 | 设置表格标题 | 表单设置 | 表格中设置单选按钮 )

重新启动应用程序时恢复自定义表格视图单元格

在线编辑文本字段时,UITableViewController 不会自动调整其表格视图的大小和位置

如何方便的保存WinForm窗体控件的位置大小等等配置信息

我用WORD编辑的表格,黑线怎么都变成透明的线了,要怎么恢复?求解

Excel表格里内容删除如何恢复