为什么不能将Windows窗体的大小绑定到ApplicationSettings?

时间:2020-03-05 18:41:14  来源:igfitidea点击:

更新:已解决,带有代码

我成功了,请在下面的代码中查看我的答案...

原始帖子

正如Tundey在回答我的最后一个问题时指出的那样,我们几乎可以将Windows窗体控件的几乎所有内容绑定到ApplicationSettings。那么,真的没有办法用Size来做到这一点吗?本教程说我们需要显式处理Size,以便在窗口最大化或者最小化时保存RestoreBounds而不是size。但是,我希望可以使用类似以下的属性:

public Size RestoreSize
{
    get
    {
        if (this.WindowState == FormWindowState.Normal)
        {
            return this.Size;
        }
        else
        {
            return this.RestoreBounds.Size;
        }
    }
    set
    {
        ...
    }
}

但是我在设计器中看不到将其绑定的方法(在PropertyBinding列表中明显缺少Size)。

解决方案

回答

好吧,我对此进行了快速练习,我们是正确的,尽管无法将表单的大小直接绑定到AppSettings,但是我们可以添加自己的值并在加载时更改大小。

我可能会建议,如果这是一个共同的功能,则我们可以继承Form的子类,并使其自动探测App.Config的表单大小设置。

(或者,我们也可以滚动自己的文件。获取查询Xml文件" formname.settings.xml"之类的文件?大声思考!)。

这是我所拥有的(非常粗糙,没有错误检查等)。

应用配置

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <add key ="FormHeight" value="500" />
        <add key ="FormWidth" value="200"/>
    </appSettings>
</configuration>

表格代码

private void Form1_Load(object sender, EventArgs e)
    {
        string height = ConfigurationManager.AppSettings["FormHeight"];
        int h = int.Parse(height);
        string width = ConfigurationManager.AppSettings["FormWidth"];
        int w = int.Parse(width);
        this.Size = new Size(h, w);
    }

回答

我认为不允许大小绑定的原因之一是因为屏幕可能在会话之间改变。

降低分辨率后重新加载尺寸可能导致标题栏超出屏幕限制。

我们还需要警惕多个显示器设置,在下次运行应用程序时,显示器可能不再可用。

回答

我同意罗伯·库珀的回答。但是我认为马丁提出了非常好的观点。就像让用户打开应用程序而该应用程序不在屏幕上一样!

因此,实际上,在设置表单大小之前,我们需要合并两个答案并牢记当前的屏幕尺寸。

回答

我终于提出了一个Form子类,它可以一劳永逸地解决这个问题。要使用它:

  • 从RestorableForm而不是Form继承。
  • 在(ApplicationSettings)->(PropertyBinding)中将绑定添加到WindowRestoreState。
  • 当窗口即将关闭时,调用Properties.Settings.Default.Save()。

现在,将在会话之间记住窗口的位置和状态。遵循下面其他张贴者的建议,我包括一个ConstrainToScreen函数,该函数可确保在恢复自身时该窗口很好地适合可用的显示。

代码

// Consider this code public domain. If you want, you can even tell
// your boss, attractive women, or the other guy in your cube that
// you wrote it. Enjoy!

using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;

namespace Utilities
{
    public class RestorableForm : Form, INotifyPropertyChanged
    {
        // We invoke this event when the binding needs to be updated.
        public event PropertyChangedEventHandler PropertyChanged;

        // This stores the last window position and state
        private WindowRestoreStateInfo windowRestoreState;

        // Now we define the property that we will bind to our settings.
        [Browsable(false)]        // Don't show it in the Properties list
        [SettingsBindable(true)]  // But do enable binding to settings
        public WindowRestoreStateInfo WindowRestoreState
        {
            get { return windowRestoreState; }
            set
            {
                windowRestoreState = value;
                if (PropertyChanged != null)
                {
                    // If anybody's listening, let them know the
                    // binding needs to be updated:
                    PropertyChanged(this,
                        new PropertyChangedEventArgs("WindowRestoreState"));
                }
            }
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            WindowRestoreState = new WindowRestoreStateInfo();
            WindowRestoreState.Bounds
                = WindowState == FormWindowState.Normal ?
                  Bounds : RestoreBounds;
            WindowRestoreState.WindowState = WindowState;

            base.OnClosing(e);
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            if (WindowRestoreState != null)
            {
                Bounds = ConstrainToScreen(WindowRestoreState.Bounds);
                WindowState = WindowRestoreState.WindowState;
            }
        }

        // This helper class stores both position and state.
        // That way, we only have to set one binding.
        public class WindowRestoreStateInfo
        {
            Rectangle bounds;
            public Rectangle Bounds
            {
                get { return bounds; }
                set { bounds = value; }
            }

            FormWindowState windowState;
            public FormWindowState WindowState
            {
                get { return windowState; }
                set { windowState = value; }
            }
        }

        private Rectangle ConstrainToScreen(Rectangle bounds)
        {
            Screen screen = Screen.FromRectangle(WindowRestoreState.Bounds);
            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 Rectangle(left, top, width, height);
        }
    }
}

设置绑定参考

  • SettingsBindableAttribute
  • INotifyPropertyChanged

回答

设置绑定UI中的Form.Size属性不可用的原因是,此属性被标记为DesignerSerializationVisibility.Hidden。这意味着设计人员不知道如何序列化它,更不用说为其生成数据绑定了。相反,Form.ClientSize属性是被序列化的属性。

如果尝试通过绑定Location和ClientSize变得更聪明,则会看到另一个问题。当我们尝试从左边缘或者上边缘调整表单大小时,我们会看到奇怪的行为。显然,这与双向数据绑定在相互影响的属性集的上下文中起作用的方式有关。 Location和ClientSize最终都调用一个通用方法SetBoundsCore()。

而且,将数据绑定到位置和大小等属性的效率很低。每次用户移动或者调整表单大小时,Windows都会向表单发送数百条消息,从而使数据绑定逻辑进行大量处理,而我们真正想要的只是在关闭表单之前存储最后的位置和大小。

这是我所做的工作的非常简化的版本:

private void MyForm_FormClosing(object sender, FormClosingEventArgs e)
{
    Properties.Settings.Default.MyState = this.WindowState;
    if (this.WindowState == FormWindowState.Normal)
    {
       Properties.Settings.Default.MySize = this.Size;
       Properties.Settings.Default.MyLoc = this.Location;
    }
    else
    {
       Properties.Settings.Default.MySize = this.RestoreBounds.Size;
       Properties.Settings.Default.MyLoc = this.RestoreBounds.Location;
    }
    Properties.Settings.Default.Save();
}

private void MyForm_Load(object sender, EventArgs e)
{
    this.Size = Properties.Settings.Default.MySize;
    this.Location = Properties.Settings.Default.MyLoc;
    this.WindowState = Properties.Settings.Default.MyState;
}

为什么这是一个非常简化的版本?因为正确执行此操作比看起来要复杂得多:-)