【问题标题】:Lifetime for the view / viewmodel when using data templates使用数据模板时视图/视图模型的生命周期
【发布时间】:2012-06-21 11:48:38
【问题描述】:

我正在定义一个策略,其中主视图将使用数据模板在视图之间切换。目前它可以在 3 个视图之间切换:

  • ApplicationView:它实际上是由许多 不同的视图,主要使用选项卡/停靠进行分层。这是一个 处理应用程序数据的视图。
  • Lo​​gInView:用于登录用户。
  • DialogView:用于显示对话框视图。此视图还将使用数据模板来选择所需的适当视图。

这个想法是当需要显示一个对话框视图时,它被设置为主视图上的当前视图。选择完成后,此信息将传递给 ApplicationView,或作为 ApplicationView 一部分的视图。在显示 DialogView 时,不能从内存中释放 ApplicationView,因为 ApplicationViewModel 仍将处理数据(它需要在后台持续工作)。

我正在考虑使用 DataTemplates 来实现这一点,并将 ContentControl 的内容绑定到 CurrentView:

// in MainView
DataTemplate DataType="{x:Type vm:ApplicationViewModel}">
    <vw:ApplicationView />
</DataTemplate>

.....

// in MainViewModel
public ViewModelBase CurrentView { get; set; }

基本上,我试图避免将模式窗口用于对话框。

1) 这个策略可以吗,还是有一些我不知道的问题?

2) 当我切换到 DialogView(我实际上是在切换视图模型)时,ApplicationView/ApplicationViewModel 会发生什么情况?我是否需要将 ApplicationViewModel 的引用存储在某处,这样它就不会被垃圾收集?我没有对此进行测试,但可能在我设置 CurrentView 时会创建一个新的 ViewModel/View 实例。

3) 连接到第二个问题,当使用 DataTemplates 时,以前使用的 View/ViewModel 会发生什么,现在被不同的 view/viewmodel 替换了?

【问题讨论】:

    标签: wpf mvvm


    【解决方案1】:
    1. 我认为您切换视图的方式没有任何问题,尽管通常您不想在显示对话框时摆脱应用程序。

      我过去所做的是将CurrentView 和DialogView 放在Grid 中,使它们彼此重叠,然后让ApplicationViewModel 包含@987654326 @ 和 IsDialogVisible 属性,当您想要显示对话框时,只需填充这两个字段。 (见下文示例)

    2. 如果您想返回 ApplicationViewModel 并避免创建新的 ApplicationViewModel,则必须将其存储在某处

    3. WPF 会处理不再可见的 UI 对象,因此将 CurrentView 从 Login 切换到 Application 将摆脱 LoginView 并创建一个 ApplicationView

      ContentControl 的内容被设置为您的ViewModel,因此 ViewModel 实际上被放入应用程序VisualTree。每当 WPF 在其 VisualTree 中遇到一个它不知道如何绘制的对象时,它将使用包含该对象的 .ToString() 的 TextBlock 来绘制它。通过定义DataTemplate,您是在告诉 WPF 如何绘制对象,而不是使用其默认的.ToString() 方法。一旦对象离开VisualTree,为渲染该对象而创建的任何视觉对象都将被销毁。

    虽然我会继续使用您当前的视图切换方法,但我不会将这种方法用于 Login、Application 和 Dialog 视图。

    通常LoginView 在登录时应该只显示一次,但如果您允许用户在登录后切换登录名,它可能会再次显示在Dialog 中。因此,我通常会显示LoginView在启动代码中,然后在登录成功后显示ApplicationView。

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
    
        var login = new LoginDialog();
        var loginVm = new LoginViewModel();
    
        login.DataContext = loginVm;
        login.ShowDialog();
    
        if (!login.DialogResult.GetValueOrDefault())
        {
            Environment.Exit(0);
        }
    
        // Providing we have a successful login, startup application
        var app = new ApplicationView();
        var context = new ApplicationViewModel(loginVm.CurrentUser);
        app.DataContext = context;
        app.Show();
    }
    

    就像我之前说的,我不想在显示Dialog 时隐藏Application,所以我会将Dialog 作为Application 的一部分

    这是一个DataTemplate 的示例,我将使用我自己的ApplicationViewModel,将我自己的custom Popup from my blog 用于对话框

    <Grid x:Name="ApplicationView">
        <ContentControl Content="{Binding CurrentView}" />
        <local:PopupPanel x:Name="DialogPopup"
                          Content="{Binding DialogContent}"
                          local:PopupPanel.IsPopupVisible="{Binding IsDialogVisible}"
                          local:PopupPanel.PopupParent="{Binding ElementName=ApplicationView}" />
    </Grid>
    

    【讨论】:

    • 嗨,Rachel,应用程序本身应该“永不”关闭,因此应在每次换班时在应用程序运行期间完成登录。这就是为什么我想让 LoginView 成为应用程序的一部分。所以,如果我理解正确的话,ApplicationView 将在我切换 CurrentView 后立即被释放,无论我是否保留对 ApplicationViewModel 的引用以防止它被释放?但是如果我隐藏视图,视图不会被释放?
    • @Goran 我明白了,在这种情况下,我只需将Login 作为对话框弹出窗口之一,甚至可能是应用程序的CurrentViews 之一。如果ApplicationView 不可见,它将被卸载,或者通过将其Visibility 属性设置为不可见的东西,或者通过更改CurrentView 使其不存在。如果您有要保存的数据,则应将其存储在 ApplicationViewModel 中,因此下次显示 ApplicationView 时,它将加载回用户上次所在的位置。
    【解决方案2】:

    就个人而言,我会发现在标准网格中使用 ZOrdering 更容易,并将所有内容放在同一个视图中 - 使用 ViewModel 来管理可见性。

    例如

    <Grid>
        <Grid Visibility="{Binding IsView1Visible, 
            Converter={StaticResource BoolToVisibilityConverter}}">
            <!-- view 1 contents -->
        </Grid>
        <Grid Visibility="{Binding IsView2Visible, 
            Converter={StaticResource BoolToVisibilityConverter}}">
            <!-- view 2 contents -->
        </Grid>
        <Grid Visibility="{Binding IsView3Visible, 
            Converter={StaticResource BoolToVisibilityConverter}}">
            <!-- view 3 contents -->
        </Grid>
        <Grid Visibility="{Binding IsDialogVisible, 
            Converter={StaticResource BoolToVisibilityConverter}}">
            <!-- dialog contents contents -->
        </Grid>
    </Grid>
    

    【讨论】:

    • 这对于许多不同的视图来说变得非常复杂,而且由于您必须手动管理视图可见性,所以工作量要大得多。我发现使用DataTemplates 来定义每个ViewModel 的绘制方式,在ContentControl 中显示CurrentView 属性,然后将CurrentView 设置为当前的ViewModel 会更有效。我有一个例子on my blog如果你有兴趣
    猜你喜欢
    • 2014-01-30
    • 2011-02-20
    • 2012-07-22
    • 1970-01-01
    • 1970-01-01
    • 2021-01-09
    • 1970-01-01
    • 2023-03-13
    • 1970-01-01
    相关资源
    最近更新 更多