我可以使用哪个WPF方法来设置控件的首次显示属性?

4

当用户关闭应用程序时,我需要存储我的应用程序窗口的大小/位置和状态,并在用户再次打开应用程序时将它们设置回来。

我使用注册表键轻松实现了这个目标(这是最好的方法吗?),但我想知道我应该把代码放在哪里来设置这些属性。

我认为需要在窗口第一次“出现”时设置它们。但是,在这种情况下可以使用几种方法,包括:

  • Window.Show()
  • Window.Activate()
  • Window.ApplyTemplate()
  • Window.Arrange()
  • Window.ArrangeCore()
  • Window.ArrangeOverride()
  • Window.BeginInit()
  • Window.EndInit()
  • Window.Measure()
  • Window.MeasureCore()
  • Window.MeasureOverride()
  • Window.OnApplyTemplate()
  • Window.OnInitialized()
  • Window.OnRender()
  • Window.UpdateLayout()

我知道大多数方法都不是很好的选择(例如 UpdateLayout() 将被频繁调用)。理想情况下,我正在寻找一种仅在窗口生命周期内调用一次的方法,以便我不必添加一个标志来检查这是否是该方法的第一次调用。

那么在这种情况下哪种方法最好?为什么?

附加问题:我将保存值的代码放在 Window.Close() 中(我在我的 MyWindow 类中覆盖了该方法),但我也可以将其放在 Window.OnClosing() 或 Window.OnClosed() 中。这在我的情况下有什么区别吗?

附加问题(bis):我还需要保存数据网格的列顺序,那么在这种情况下,“保存”和“加载”代码应该放在哪里?


我是否值得解决您在我下面回答中提出的与架构相关的附加问题? 我之所以问这个问题,是因为对于您当前的架构和我建议的架构,这些问题的答案会有所不同 - 希望这样说得清楚? =/ - Smudge202
3个回答

3

好的,我觉得你把WPF当作老式的WinForms应用程序来处理。您不再需要监视表单事件以从表单属性中检索信息。大多数WPF控件属性都是一种称为Dependency Property的东西。

依赖属性引入的一些聪明技巧包括Data Binding

如果您考虑使用MVVM Architecture编写应用程序,您将很快能够自己解决以下问题... =)

在 View*1 中,您可以创建依赖属性或标准属性并实现 INotifyPropertyChanged,这些属性保存大小、布局、位置等。然后将表单的属性(在 xaml 或代码中)绑定到 View 的属性上。通过简单地调整视图中属性的 Get/Set,您可以为存储/检索默认值实现任何功能,并在表单更改时自动更新。以下是 Windows 标题的快速示例:
<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="{Binding Path=DisplayName}"
        WindowStartupLocation="CenterScreen" >
    <Grid>...</Grid>
</Window>

视图的一个实现示例:

public class SampleView : System.ComponentModel.INotifyPropertyChanged
{

    public event PropertyChangedEventHandler System.ComponentModel.INotifyPropertyChanged.PropertyChanged;
    public delegate void PropertyChangedEventHandler(object sender, System.ComponentModel.PropertyChangedEventArgs e);

    private string _Title;
    public string Title {
        get {
            if (_Title == null) {
                _Title = My.Settings.MainWindowTitle;
            }
            return _Title;
        }
        set {
            _Title = value;
            if (!(_Title == My.Settings.MainWindowTitle)) {
                if (PropertyChanged != null) {
                    PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("Title"));
                }
                My.Settings.MainWindowTitle = Title;
                My.Settings.Save();
            }
        }
    }
}

编辑:关于最佳存储用户偏好的方式,我不建议使用注册表,尽管这并不罕见。现在的注册表已经充满了各种设置,在我看来,注册表并不是真正设计用来存储这些设置的。考虑使用应用程序设置设置为用户范围。这将处理大多数数据存储/检索的细节,并为您提供一个良好的类型安全接口。

*1我个人更喜欢尝试将所有内容都绑定到ViewModel,并且几乎完全使用无脑视图;尽管我知道有很多有效的情况需要视图具有代码。我不认为大小/布局等实际上是业务逻辑问题,也不是我目前关注的事情,因此这应该在视图本身中处理。

编辑2-用户/应用程序范围设置的快速示例:

这是我添加到项目中的设置的快速图片:

Settings screenshot

下面的代码尝试同时使用应用程序和用户范围的设置。 注意: 在运行时,应用程序范围设置是只读的
public class SettingsExample
{
    private Form1 frmMain = new Form1();

    public void Main()
    {
        frmMain.BackColor = My.Settings.DefaultBackColour;
    }

    public void UserLoggedIn()
    {
        frmMain.BackColor = My.Settings.UserBackcolour;
    }

    public void UpdateUserBackcolour(System.Drawing.Color newColour)
    {
        My.Settings.UserBackcolour = newColour;
        My.Settings.Save();
    }

    public void UpdateDefaultBackcolour(System.Drawing.Color newColour)
    {
        My.Settings.DefaultBackColour = newColour;
        // Compiler Error
        // This property is read only because it is an application setting
        // Only user settings can be changed at runtime
    }

}

哦,我必须说我倾向于和Lukas一起收敛,并发现在这种情况下使用MVVM有些过度。在MyWindow类的代码后面执行保存/加载要容易得多。数据绑定并非没有成本,仅在应用程序的生命周期中更新属性及其源显得有点过度了。此外,在这种情况下,当用户调整窗口大小时(我想保存窗口的大小):在注册表中进行数千次读/写...我认为这不是最好的方法。但是对于应用程序设置,我会好好看看:) - David
好的,@David,很高兴我能帮助你设置应用程序。我认为绑定可能有很多优缺点。可以辩称绑定是有益的,如果应用程序足够轻,不需要担心绑定的性能损失,那么这个应用程序可能不大到需要考虑(假定小)性能损失。此外,这样可以减少引入的模式。然而,这些都是主观的,最终取决于你。=) - Smudge202
我必须补充一点,由于遗留代码的原因,我必须在代码后台完成大部分实例化/属性值赋值工作。因此,在我的情况下,使用绑定会使事情变得非常混乱,我肯定会在这里避免使用它们(尽管我在其他可以自由操作的地方经常使用它们)。但是我正在寻找一个更通用的答案,因此我的问题缺乏细节。我已经查看了应用程序设置,但不太理解其工作方式。它似乎仅限于在Visual Studio中执行。您是否有代码示例? - David
完全可以理解,现有的应用程序往往决定了所需的代码方法。=(我会在我的帖子中为您添加一个快速设置示例,需要5分钟... - Smudge202
关于我刚刚发布的设置示例,由于我在那里进行了整个用户登录,它实际上是一个糟糕的示例...(抱歉)如果您忽略该部分,它确实向您展示了如何创建、使用和在userbackcolour的情况下更改/保存设置。 - Smudge202
显示剩余2条评论

0

首先,你忘记了

Loaded事件 - 当元素布局、渲染并准备好交互时发生。(从FrameworkElement继承。)

没有一个简单的答案。情况可能因为是子对话框窗口(那么我会在Show()之前设置大小),同一窗口的新实例或应用程序的新实例而有所不同。

我认为UpdateLayout()是个坏主意。实际上,这是个非常好的主意。例如:

private bool m_onStart = true;

public MainWindow()
{
        InitializeComponent();
        this.LayoutUpdated += new EventHandler(MainWindow_LayoutUpdated);
}

void MainWindow_LayoutUpdated(object sender, EventArgs e)
{
     if (m_onStart)
     {
         m_onStart = false;
         Dispatcher.BeginInvoke(() =>
         {
              //Start App
         }
         );
     }
 }

即使每秒调用一千次(这是非常不可能的),您也不会注意到它,也不会影响性能。

总之,您可以创建一个帮助方法来保存用户首选项并读取第二个。由于任务与视图相关,并且对于此任务使用MVVM和绑定过于复杂,因此在Loaded事件中设置大小(在所有构造函数、初始化和可视树完成时执行)。


"bi[n]ding对于这个来说有些过度了。我不是在争论这个问题,只是想知道为什么?" - Smudge202
1
因为你需要设置许多东西来完成简单的任务。这就是为什么 MVVM light 做得很好的原因。"第二个原因是来自 MVVM 创造者 John Gossman 本人,他指出在实现 MVVM 中的开销对于简单的 UI 操作来说是“过度”的。他还表示,对于更大的应用程序,泛化 View 层变得更加困难。此外,他说明如果没有很好地管理数据绑定,它会导致应用程序中的相当大的内存消耗。" - Lukasz Madon
我还没有机会去了解MVVM Light,所以感谢你提到它。不确定你所说的“需要设置很多东西”是什么意思,因为比较我们两个代码示例,代码量似乎没有太大差异,并且我发布的代码不需要维护(自身)状态- m_onStart标志。然而,我会查阅我能找到的MVVM Light文章并解决所有问题。感谢您的回复! - Smudge202
实际上,这是全部内容:我有一个MyWindow类,它可以是第一个窗口,也可以是子窗口,并且一个实例可以在应用程序的生命周期中既是第一个窗口又是子窗口,具体取决于上下文。因此,我的想法是重写上述方法之一以实现我想要的效果。但我仍然不确定是否使用LayoutUpdated。每当你将鼠标移动到窗口上时,它就会被调用,因此它实际上经常被调用,你可能每秒钟可以达到数百次甚至不是数千次的调用。此外,它很难调试(你无法设置断点)。 - David

0

我的选择:最终我把从注册表加载值的代码放在了window.Show()中。

我这样做的原因有两个:

  1. 我存储窗口的状态(最小化/最大化),而WPF的处理方式是,我需要先设置宽度/高度,然后再设置最大化状态(如果需要),否则会破坏布局。如果我不先设置宽度/高度,那么当我取消最大化之后,就会失去它们。因此,我必须按照这个顺序精确地进行操作:先宽度+高度,然后状态。(此外,在使用多个屏幕时,这也是必要的,否则你会失去你正在工作的屏幕)。这意味着上面的一些方法是不切实际的(例如“measure”方法)

  2. 除此之外,如果我把我的代码放在上述大多数方法中,我会在第一次显示时得到一个不好看的效果:窗口首先以设置了宽度和高度的方式出现在屏幕中央,然后经过短暂的延迟,窗口被最大化。

将代码放在window.Show()中成功解决了这两个问题。我可能会用其他方法中的一个或多个得到相同的结果,但我只是厌倦了尝试不同的配置,最终使用了第一个让我完全满意的方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接