【问题标题】:How to cache dynamically switched views by ViewModel-First approach using DataTemplates如何使用 DataTemplates 通过 ViewModel-First 方法缓存动态切换的视图
【发布时间】:2015-07-25 19:06:03
【问题描述】:

(我正在使用GalaSoft.MvvmLight 框架)

我在MainWindow.xaml 中有一些视图,我在运行时通过用户选择在它们之间动态切换。

这些视图使用以下技术与它们对应的视图模型绑定:

MainWindow.xaml

...
<Window.Resources>
        <DataTemplate DataType="{x:Type vm:Control1ViewModel}">
            <v:Control1/>
        </DataTemplate>
    ... // Assume there is more then one DataTemplate. Every view has a unique view-model.
</Window.Resources>
...

Control1ViewModel.cs

public class Control1ViewModel : ViewModelBase
{
    ...
}

MainWindow.xaml 使用以下技术在上述视图之间切换:

MainWindow.xaml

...
<ContentControl Content="{Binding CurrentView}"/> // This is were the view appears.
...

MainViewModel.cs

public class MainViewModel : ViewModelBase
{
    ...
    private ViewModelBase _currentView;
    public ViewModelBase CurrentView
    {
        get { return _currentView; }
        private set
        {
            _currentView = value;
            base.RaisePropertyChanged("CurrentView");
        }
    }
    ...
}

为方便起见,我没有添加更多控件,只是放了一个(Control1)以缩短问题代码部分。如上所述,假设有多个视图可以切换。

每次CurrentView属性设置为新的ViewModelBase值(例如Control1ViewModel),WPF将构造绑定视图DataTemplate视觉树对象的新实例,所以旧的会丢失。

这意味着在视图之间切换时我无法缓存视图(例如Control1)。

我找到答案的唯一解决方案是使用其 ViewModel(使用 DataContext)“硬编码”视图,但遵循此解决方案会发生以下情况:

  • 我正在打破 ViewModel-First 方法。
  • 为了不破坏完整的 MVVM,我必须更改 CurrentView 签名并将其移动到我的 MainWindow.xaml 后面的代码中。
  • 我不是切换ViewModelBase 类型,而是切换具体控件。

我想知道是否有任何解决方案不使用视图模型“硬编码”视图,因此我可以保留当前的ViewModelBase 开关和 ViewModel-First 方法。

【问题讨论】:

    标签: c# wpf xaml mvvm


    【解决方案1】:

    你可以有以下方法:

    1. 而不是采取ContentControl 采取ItemsControl

    &lt;ItemsControl ItemsSource="{Binding Views}" SelectedItem="{Binding CurrentView}"/&gt;

    1. ItemsControl 中的ItemsPanel 作为一个网格,对于SelectedItem,将Z-Index 设置为1,对于其余的项目,将Z-Index 设置为0。这样只有一个视图一次可见,将在其他视图之上。

    2. MainViewModel 中取两个属性。 Views 类型为ObservableCollection&lt;ViewModelBase&gt;CurrentView 类型为ViewModelBase 并分别与ItemsControlsItemsSourceSelectedItem 绑定。

    现在,当您要打开视图时,创建一个 ViewModel,将其添加到 Views 列表并将其设置为 CurrentView。如果它已经在列表中,只需将其设置为CurrentView。

    如果您希望它永远关闭,还可以提供一个关闭按钮。即,如果您将其关闭,它将从列表中删除并且不会被缓存。

    这就像您在窗口中打开了不同的视图一样工作,您可以在它们之间切换。如果你愿意,你可以关闭一个视图。

    编辑: 见以下代码:

                <ItemsControl ItemsSource="{Binding Views}">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <Grid Margin="10,10,0,10">
                            </Grid>
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
    
                    <ItemsControl.ItemContainerStyle>
                        <Style>
                            <Setter Property="Grid.Opacity" Value="{Binding ZIndex}"/>
                            <Setter Property="Grid.ZIndex" Value="{Binding ZIndex}"/>
                        </Style>
                    </ItemsControl.ItemContainerStyle>
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <ContentControl Content="{Binding}"/>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
    

    在这里您可以看到,在您的ViewModel 中,您必须有一个属性ZIndex,它将用于在顶部显示当前视图。因此,每当您想显示视图时,只需将 ViewModelZIndex 属性设置为 1 并将重置视图重置为 0。

    【讨论】:

    • 谢谢你的回答,ItemsControl似乎没有SelectedItem属性。此属性仅适用于Selector 派生类。如果我理解您的回答正确,您的意思是TabControl?一个示例代码将不胜感激。
    • 对不起,我弄糊涂了。是的,你是对的,ItemsControl 没有 SelectedItem 属性。我已经更新了我的答案并提供了xaml 参考。是的,您可以说它类似于TabControl,但并非完全如此。在这里,您可以提供更多功能。我在我的一个项目中使用相同的结构。
    • 感谢您为您的答案提供代码,它更容易理解。我完全按照您提供的代码编写了代码,并在 ViewModelBase 中实现了 ZIndex 属性,因此它将在 ViewModel 之间共享。这是完美的工作!非常感谢您的回答!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多