我是WPF的新手,试图建立一个项目,该项目遵循Josh Smith描述模型 - 视图 - 视图模型设计模式的优秀文章的建议.
使用Josh的示例代码作为基础,我创建了一个包含许多"工作区"的简单应用程序,每个工作区由TabControl中的选项卡表示.在我的应用程序中,工作空间是一个文档编辑器,它允许通过TreeView控件操作分层文档.
虽然我已成功打开多个工作区并在绑定的TreeView控件中查看其文档内容,但我发现TreeView在选项卡之间切换时"忘记"其状态.例如,如果Tab1中的TreeView部分展开,则在切换到Tab2并返回到Tab1后,它将显示为完全折叠.此行为似乎适用于所有控件的控件状态的所有方面.
经过一些实验,我意识到我可以通过将每个控件状态属性显式绑定到底层ViewModel上的专用属性来保留TabItem中的状态.然而,这似乎是很多额外的工作,当我只是希望我的所有控件在工作区之间切换时记住它们的状态.
我想我错过了一些简单的东西,但我不知道在哪里寻找答案.任何指导都将非常感谢.
蒂姆,谢谢
更新:
根据要求,我将尝试发布一些演示此问题的代码.但是,由于作为TreeView基础的数据很复杂,我将发布一个展示相同symtoms的简化示例.这是主窗口中的XAML:
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Path=Docs}">
<TabControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Path=Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<view:DocumentView />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Run Code Online (Sandbox Code Playgroud)
上面的XAML正确绑定到DocumentViewModel的ObservableCollection,其中每个成员都通过DocumentView呈现.
为了简化这个例子,我从DocumentView中删除了TreeView(如上所述),并将其替换为包含3个固定标签的TabControl:
<TabControl>
<TabItem Header="A" />
<TabItem Header="B" />
<TabItem Header="C" />
</TabControl>
Run Code Online (Sandbox Code Playgroud)
在这种情况下,DocumentView和DocumentViewModel之间没有绑定.运行代码时,内部TabControl在切换外部TabControl时无法记住其选择.
但是,如果我显式绑定内部TabControl的SelectedIndex属性...
<TabControl SelectedIndex="{Binding Path=SelectedDocumentIndex}">
<TabItem Header="A" />
<TabItem Header="B" />
<TabItem Header="C" />
</TabControl>
Run Code Online (Sandbox Code Playgroud)
...到DocumentViewModel上的相应虚拟属性...
public int SelecteDocumentIndex { get; set; }
Run Code Online (Sandbox Code Playgroud)
...内部标签能够记住它的选择.
我知道我可以通过将这种技术应用到每个控件的每个视觉属性来有效地解决我的问题,但我希望有一个更优雅的解决方案.
当WPF选项卡控件中的选项卡更改时,有没有办法阻止选项卡卸载/重新加载?或者,如果不可能,是否有建议的方法来缓存选项卡内容,以便不必在每次更改选项卡时重新生成它们?
例如,一个选项卡的UI可完全自定义并存储在数据库中.当用户选择要处理的对象时,自定义布局中的项目将填充该对象的数据.用户期望初始加载或检索数据时会有轻微延迟,但在选项卡之间来回切换时则不会,并且更改选项卡时的延迟非常明显.
我有一个MVVM应用程序,需要在屏幕之间进行基本的向后/向前导航.目前,我已经使用WorkspaceHostViewModel实现了这一点,它跟踪当前工作空间并公开必要的导航命令,如下所示.
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; }
}
Run Code Online (Sandbox Code Playgroud)
我还有一个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>
Run Code Online (Sandbox Code Playgroud)
在WorkspaceHostResources.xaml文件中,我将WPF用于使用DataTemplates呈现每个WorkspaceViewModel的View关联起来.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyNavigator"> …Run Code Online (Sandbox Code Playgroud) 我有两个 DataTemplates 根据当前的 ViewModel 进行切换。但是,每当我切换 ViewModel 时,它似乎都会调用相应的 View 构造函数并调用构造函数中的 InitializeComponent() 调用,这意味着每当我切换 DataTemplate 时,它都会生成一个绑定到相应 DataTemplate 的新视图。我不确定为什么会发生这种情况,但是有没有办法在切换 ViewModel 时防止创建新视图?
下面是位于我的 MainView 的 DataTemplates。
<Window.Resources>
<DataTemplate DataType="{x:Type viewModels:FirstPanelViewModel}">
<views:FirstPanelView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:SecondPanelViewModel}">
<views:SecondPanelView />
</DataTemplate>
</Window.Resources>
Run Code Online (Sandbox Code Playgroud)
模板显示在 ContentControl 中。
<ContentControl Grid.Row="1" Content="{Binding CurrentViewModel}" />
Run Code Online (Sandbox Code Playgroud)
这是我的 SecondPanelView,它与我的 FirstPanelView 相同,非常简单。
public partial class FirstPanelView
{
public FirstPanelView()
{
InitializeComponent();
}
}
public partial class SecondPanelView
{
public SecondPanelView()
{
InitializeComponent();
}
}
Run Code Online (Sandbox Code Playgroud)
我的 Ioc 确保我只生成 SecondPanelView 的一个实例
container.Register<IFirstPanelViewModel, FirstPanelViewModel>(new PerContainerLifetime())
container.Register<ISecondPanelViewModel, …Run Code Online (Sandbox Code Playgroud) wpf ×4
mvvm ×3
tabcontrol ×2
binding ×1
c# ×1
caching ×1
datatemplate ×1
navigation ×1
state ×1