Wpf 在MVVM应用程序中的视图之间导航时,如何保持视图的完整状态?

Wpf 在MVVM应用程序中的视图之间导航时,如何保持视图的完整状态?,wpf,mvvm,navigation,datatemplate,Wpf,Mvvm,Navigation,Datatemplate,我有一个MVVM应用程序,需要在屏幕之间进行基本的向后/向前导航。目前,我已经使用WorkspaceHostViewModel实现了这一点,该模型跟踪当前工作空间并公开必要的导航命令,如下所示 <Window x:Class="MyNavigator.WorkspaceHostViewModel" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http:/

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

<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>
<!-- 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>
公共类WorkspaceHostViewModel:ViewModelBase
{
私有工作空间视图模型_currentWorkspace;
公共工作空间视图模型当前工作空间
{
获取{返回此。\u currentWorkspace;}
设置
{
if(this.\u currentWorkspace==null
||!this.\u currentWorkspace.Equals(值))
{
这是。_currentWorkspace=值;
this.OnPropertyChanged(()=>this.CurrentWorkspace);
}
}
}
私有链接列表导航历史;
公共ICommand NavigateBackwardCommand{get;set;}
公共ICommand NavigateForwardCommand{get;set;}
}
我还有一个WorkspaceHostView,它绑定到WorkspaceHostViewModel,如下所示

<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>
<!-- 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>

在WorkspaceHostResources.xaml文件中,我关联了WPF使用DataTemplates渲染每个WorkspaceViewModel时应使用的视图

<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>

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


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

我可能没有抓住要点,但任何重要的视图状态都可以(甚至应该)存储在ViewModel中。这在某种程度上取决于有多少,以及你愿意得到多脏

如果这不令人满意(从纯粹主义的角度来看,它可能不适合您所做的事情),您可以将视图中那些不太适合VM的部分绑定到一个包含状态的单独类(可能称它们为ViewState类?)


如果它们确实是仅查看的属性,并且您不想使用这些路由中的任何一个,那么它们就是视图中它们所属的位置。相反,您应该找到一种避免每次都重新创建视图的方法:例如,使用工厂而不是内置的数据模板。如果您使用
DataTemplateSelector
返回一个我相信的模板,也许有一种方法可以重用那里的视图实例?(我必须检查..)

我也遇到了同样的问题,最后我使用了我在网上找到的一些代码,扩展了
TabControl
,以阻止它在切换选项卡时破坏其子项。我通常会覆盖
TabControl
模板来隐藏选项卡,我只会使用
SelectedItem
来定义当前应该可见的“工作区”

其背后的思想是,切换到新项目时,每个
TabItem
ContentPresenter
都会被缓存,然后当您切换回新项目时,它会重新加载缓存的项目,而不是重新创建它

<local:TabControlEx ItemsSource="{Binding AvailableWorkspaces}"
                    SelectedItem="{Binding CurrentWorkspace}"
                    Template="{StaticResource BlankTabControlTemplate}" />

代码所在的站点似乎已被删除,但我使用的代码如下。它比原来的稍微修改了一点

//扩展选项卡控件,用于保存显示的项目,这样您就不会获得
//切换选项卡时卸载和重新加载VisualTree
//从http://www.pluralsight-training.net/community/blogs/eburke/archive/2009/04/30/keeping-the-wpf-tab-control-from-destroying-its-children.aspx
//并进行了一些修改,以便在执行拖放操作时重用TabItem的ContentPresenter
[TemplatePart(Name=“PART_ItemsHolder”,Type=typeof(Panel))]
公共类TabControlEx:System.Windows.Controls.TabControl
{
//保存所有项目,但仅将当前选项卡的项目标记为可见
专用面板_itemsHolder=null;
//临时保留已删除的项目,以防这是一个拖放操作
私有对象_deletedObject=null;
公共TabControlEx()
:base()
{
//这是必要的,以便我们获得初始数据绑定的选定项
this.ItemContainerGenerator.StatusChanged+=ItemContainerGenerator\u StatusChanged;
}
/// 
///如果容器已完成,则生成所选项目
/// 
/// 
/// 
void ItemContainerGenerator_状态已更改(对象发送者,事件参数e)
{
if(this.ItemContainerGenerator.Status==GeneratorStatus.ContainerGenerated)
{
this.ItemContainerGenerator.StatusChanged-=ItemContainerGenerator\u StatusChanged;
UpdateSelectedItem();
}
}
/// 
///获取ItemsHolder并生成所有子项
/// 
应用程序模板()上的公共重写无效
{
base.OnApplyTemplate();
_itemsHolder=GetTemplateChild(“PART_itemsHolder”)作为面板;
UpdateSelectedItem();
}
/// 
///当项目更改时,我们将删除所有生成的面板子项,并根据需要添加任何新的子项
/// 
/// 
已更改受保护的覆盖(NotifyCollectionChangedEventArgs e)
{
碱基(e);
如果(_itemsHolder==null)
{
返回;
}
开关(电动)
{
案例通知CollectionChangedAction.Reset:
_itemsHolder.Children.Clear();
如果(base.Items.Count>0)
{
base.SelectedItem=base.Items[0];
更新选择项(
    }
    private string name;

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    private int tabindex;

    public int TabIndex
    {
        get { return tabindex; }
        set { tabindex = value; }
    }
}