在 MVVM 应用程序中的视图之间导航时如何保留视图的完整状态? [英] How to preserve the full state of the View when navigating between Views in an MVVM application?

查看:14
本文介绍了在 MVVM 应用程序中的视图之间导航时如何保留视图的完整状态?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 MVVM 应用程序,需要在屏幕之间进行基本的向后/向前导航.目前,我已经使用 WorkspaceHostViewModel 实现了这一点,该模型跟踪当前工作区并公开必要的导航命令,如下所示.

I have an MVVM application that requires basic backward/forward navigation between screens. Currently, I have implemented this using a WorkspaceHostViewModel that tracks the current workspace and exposes the necessary navigation commands as follows.

public class WorkspaceHostViewModel : ViewModelBase
{
    private WorkspaceViewModel _currentWorkspace;
    public WorkspaceViewModel CurrentWorkspace
    {
        get { return this._currentWorkspace; }
        set
        {
            if (this._currentWorkspace == null
                || !this._currentWorkspace.Equals(value))
            {
                this._currentWorkspace = value;
                this.OnPropertyChanged(() => this.CurrentWorkspace);
            }
        }
    }

    private LinkedList<WorkspaceViewModel> _navigationHistory;

    public ICommand NavigateBackwardCommand { get; set; }
    public ICommand NavigateForwardCommand { get; set; }
}

我还有一个 WorkspaceHostView 绑定到 WorkspaceHostViewModel,如下所示.

I also have a WorkspaceHostView that binds to the WorkspaceHostViewModel as follows.

<Window x:Class="MyNavigator.WorkspaceHostViewModel"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

  <Window.Resources>
    <ResourceDictionary Source="../../Resources/WorkspaceHostResources.xaml" />
  </Window.Resources>

  <Grid>
    <!-- Current Workspace -->
    <ContentControl Content="{Binding Path=CurrentWorkspace}"/>
  </Grid>

</Window>

在 WorkspaceHostResources.xaml 文件中,我关联了 WPF 应该用来使用 DataTemplates 呈现每个 WorkspaceViewModel 的视图.

In the WorkspaceHostResources.xaml file, I associate the View that WPF should use to render each WorkspaceViewModel using DataTemplates.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:MyNavigator">

  <DataTemplate DataType="{x:Type local:WorkspaceViewModel1}">
    <local:WorkspaceView1/>
  </DataTemplate>

  <DataTemplate DataType="{x:Type local:WorkspaceViewModel2}">
    <local:WorkspaceView2/>
  </DataTemplate>

</ResourceDictionary>

这很有效,但一个缺点是由于 DataTemplates 的机制,视图会在每次导航之间重新创建.如果视图包含复杂的控件,如 DataGrids 或 TreeViews,它们的内部状态将丢失.例如,如果我有一个带有可展开和可排序行的 DataGrid,当用户导航到下一个屏幕然后返回到 DataGrid 屏幕时,展开/折叠状态和排序顺序将丢失.在大多数情况下,可以跟踪需要在导航之间保留的每条状态信息,但这似乎是一种非常不雅的方法.

This works pretty well, but one disadvantage is that the Views are recreated between each navigation due to the mechanics of DataTemplates. If the view contains complex controls, like DataGrids or TreeViews, their internal state is lost. For example if I have a DataGrid with expandable and sortable rows, the expand/collapse state and sort order is lost when the user navigates to the next screen and then back to the DataGrid screen. In most cases it would be possible to track each piece of state information that needs to be preserved between navigations, but it seems like a very inelegant approach.

是否有更好的方法在更改整个屏幕的导航事件之间保留视图的整个状态?

Is there a better way to preserve the entire state of a view between navigation events that change the entire screen?

推荐答案

我最终向 WorkspaceHostViewModel 添加了 ActiveWorkspaces ObservableCollection 属性,并按如下方式将 ItemsControl 绑定到它.

I ended up adding an ActiveWorkspaces ObservableCollection property to the WorkspaceHostViewModel and binding an ItemsControl to it as follows.

<!-- Workspace -->
<ItemsControl ItemsSource="{Binding Path=ActiveWorkspaces}">
    <ItemsControl.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
    </ItemsControl.Resources>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Grid/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>            
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="{x:Type ContentPresenter}">
            <Setter Property="Visibility" Value="{Binding Visible, Converter={StaticResource BooleanToVisibilityConverter}}"/>
        </Style>
    </ItemsControl.ItemContainerStyle>
</ItemsControl>

ActiveWorkspaces 属性包含导航历史记录中的所有工作区.它们都在 UI 中相互叠加呈现,但通过绑定各自 ContentPresenter 的可见性,我一次只能显示一个.

The ActiveWorkspaces property contains all of the workspaces in the navigation history. They all get rendered on top of one another in the UI, but by binding the Visibility of their respective ContentPresenter I am able to show only one at a time.

操作 Visible 属性(它是工作区本身的一个新属性)的逻辑存在于导航前进/后退命令中.

The logic that manipulates the Visible property (which is a new property in the Workspace itself) exists in the navigate forward/backward commands.

这与 Rachel 提出的解决方案非常相似,部分基于她网站上的 ItemsControl 教程;但是,我选择自己编写显示/隐藏逻辑,而不是依靠子类 TabControl 来为我完成.我仍然觉得有可能改进显示/隐藏逻辑.具体来说,我想从 Workspace 类中删除 Visible 属性,但现在这已经足够了.

This is a very similar approach to the solution proposed by Rachel and is in part based on the ItemsControl tutorial found on her web site; however, I opted to write the show/hide logic myself rather than rely on a subclassed TabControl to do it for me. I still feel that it would be possible to improve the show/hide logic. Specifically I would like to eliminate the Visible property from the Workspace class, but for now this works well enough.

在成功使用上述解决方案几个月后,我选择将其替换为 Prism 提供的基于视图的导航功能.尽管这种方法需要更多的开销,但其优势大大超过了所涉及的工作量.一般的想法是在您的视图中定义 Region,然后通过在您的 ViewModel 中调用 regionManager.RequestNavigate("RegionName", "navigationUri") 进行导航.Prism 处理在指定区域中实例化、初始化和显示您的视图的繁琐工作.此外,您可以控制 View 的生命周期,是否应该在后续导航请求中重新使用它,应该在导航到和从事件导航时执行什么逻辑,以及是否应该中止导航(由于当前视图中未保存的更改等)请注意,基于 Prism 视图的导航需要依赖注入容器(例如 Unity 或 MEF),因此您可能需要将其合并到您的应用程序架构中,但即使没有 Prism 导航,也要采用 DI容器非常值得投资.

After using the above solution successfully for several months, I opted to replace it with the view-based navigation functionality provided by Prism. Although this approach requires much more overhead, the advantages greatly outweigh the effort involved. The general idea is to define Region's in your Views and then navigate by calling regionManager.RequestNavigate("RegionName", "navigationUri") in your ViewModel. Prism handles the legwork of instantiating, initializing and displaying your View in the specified Region. Additionally, you can control the lifetime of your View, whether or not it should be re-used upon subsequent navigation requests, what logic should be performed on navigation to and on navigation from events, and whether or not navigation should be aborted (due to unsaved changes in current View, etc.) Note that Prism view-based navigation requires a Dependency Injection Container (such as Unity or MEF) so you will likely need to incorporate this into your application architecture, but even without Prism navigation, adopting a DI container is well worth the investment.

这篇关于在 MVVM 应用程序中的视图之间导航时如何保留视图的完整状态?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆