WPF TabControl MVVM ViewModel 在切换选项卡时每次都被实例化了。

3
我是一名有用的助手,可以为您翻译文本。
我写了一些使用MVVM模式的WPF应用程序,其中包含一个绑定到“TabViewModelItem”集合的TabControl。
主窗口XAML:
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ViewModel="clr-namespace:XcomSavesGameEditor.ViewModel"
        x:Class="XcomSavesGameEditor.MainWindow"
        xmlns:Views="clr-namespace:XcomSavesGameEditor.View"
        Title="X-COM Saved Game Editor" Height="650" Width="850" Background="#FF1B0000">
    <Window.DataContext>
        <ViewModel:TabsManagerViewModel/>
    </Window.DataContext>
    <Grid>

... (some not relevant code removed for clearity of question) ...

<TabControl x:Name="myTabs" Background="Black" Margin="0,25,0,0" BorderThickness="0,0,0,0" BorderBrush="Black" ItemsSource="{Binding Tabs}" >

            <TabControl.Resources>
                <DataTemplate DataType="{x:Type ViewModel:Tab0a_FileSaveData_ViewModel}">
                    <Views:Tab0a_FileSaveData_View />
                </DataTemplate>
                <DataTemplate DataType="{x:Type ViewModel:Tab0b_Summary_ViewModel}">
                    <Views:Tab0b_Summary_View />
                </DataTemplate>
                <DataTemplate DataType="{x:Type ViewModel:Tab1_Research_ViewModel}">
                    <Views:Tab1_Research_View />
                </DataTemplate>
                <DataTemplate DataType="{x:Type ViewModel:Tab2_Engineering_ViewModel}">
                    <Views:Tab2_Engineering_View />
                </DataTemplate>
                <DataTemplate DataType="{x:Type ViewModel:Tab3_Barracks_ViewModel}">
                    <Views:Tab3_Barracks_View />
                </DataTemplate>
                <DataTemplate DataType="{x:Type ViewModel:Tab4_Hangar_ViewModel}">
                    <Views:Tab4_Hangar_View />
                </DataTemplate>
                <DataTemplate DataType="{x:Type ViewModel:Tab5_SituationRoom_ViewModel}">
                    <Views:Tab5_SituationRoom_View />
                </DataTemplate>
            </TabControl.Resources>

            <TabControl.ItemTemplate>
                <!-- this is the header template-->
                <DataTemplate>
                    <Grid Margin="0">
                        <Border Margin="0,0,0,0" 
                                Background="Black"
                                BorderBrush="Black" 
                                BorderThickness="0,0,0,0" Padding="0,0,0,0">
                            <StackPanel   Orientation="Horizontal"
                                            Margin="0,0,0,0">
                                <Image Name ="tabImage" Source="{Binding TabImage_Disabled}" />
                            </StackPanel>
                        </Border>
                    </Grid>
                    <DataTemplate.Triggers>
                        <DataTrigger Binding="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent}}" Value="True">
                            <Setter TargetName="tabImage" Property="Source" Value="{Binding TabImage_Enabled}"/>
                        </DataTrigger>
                    </DataTemplate.Triggers>
                </DataTemplate>
            </TabControl.ItemTemplate>
            <TabControl.ContentTemplate>
                <!-- this is the body of the TabItem template-->
                <DataTemplate>
                    <Grid>
                        <Grid.Background>
                            <ImageBrush ImageSource="{Binding TabImage_Background}"/>
                        </Grid.Background>
                        <UniformGrid>
                            <ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Content="{Binding TabContents}" />
                        </UniformGrid>
                    </Grid>
                </DataTemplate>
            </TabControl.ContentTemplate>
       </TabControl>

而 ViewModel 则是持有选项卡集合的代码:

 public sealed class TabsManagerViewModel : ViewModelBase
 {

private ObservableCollection<TabViewModelItem> _tabs;

        public ObservableCollection<TabViewModelItem> Tabs
        {
            get { return _tabs; }
            set
            {
                _tabs = value;
                RaisePropertyChanged("Tabs");
            }
        }

        public TabsManagerViewModel()
        {
            Tabs = new ObservableCollection<TabViewModelItem>();
            Tabs.Add(new TabViewModelItem { TabName = "File_Save_Data", TabImage_Enabled = _aEnabledTabImages[(int)enum_Tabs.SaveFileData_Tab], TabImage_Disabled = _aDisabledTabImages[(int)enum_Tabs.SaveFileData_Tab], TabImage_Background = _aBackgroundTabImages[(int)enum_Tabs.SaveFileData_Tab], TabContents = new Tab0a_FileSaveData_ViewModel() });
            Tabs.Add(new TabViewModelItem { TabName = "Summary", TabImage_Enabled = _aEnabledTabImages[(int)enum_Tabs.Summary_Tab], TabImage_Disabled = _aDisabledTabImages[(int)enum_Tabs.Summary_Tab], TabImage_Background = _aBackgroundTabImages[(int)enum_Tabs.Summary_Tab], TabContents = new Tab0b_Summary_ViewModel() });

... (rest of code removed for clearity of question)

        }

}

基本上,它是一个绑定到“TabViews”集合的选项卡控件。 根据对象数据类型,它显示View1或View2。 注意:View1和View2都是UserControls,每个UserControl都绑定到自己的ViewModel。 这个概念很好。
现在问题出在哪里? 我的问题是: 每次我点击另一个选项卡,然后返回到同一选项卡时,我会再次调用特定选项卡的ViewModel构造函数,而我希望ViewModel对象保持不变。
这是个问题,因为当我在选项卡之间切换时,会导致我丢失对该页面所做的任何修改。并且由于ctor每次都被调用,我甚至不能使用ViewModel来存储此信息。
我的问题是: 1)有没有办法防止TabControl在选项卡处于非活动状态时销毁ViewModel对象?是否可以预先创建所有ViewModel对象并在隐藏时不将其销毁? 2)使用这个概念存在哪些“解决方法”,可以让我存储给定选项卡的“可视树”,以便如果我从中导航,然后重新打开它,它将保存关于它的所有信息(例如所选复选框、已编写的文本等)。
感谢您对此事的任何帮助。

问候,Idan


我看过那篇帖子,但是我不认为"WpfApplicationFramework"中的"writer"示例是解决这个问题的答案,因为它对于所有文档都只使用单个视图,而我则为每个选项卡使用单独的视图(每个视图都绑定到自己的视图模型)。 - IdanB
1
这个问题可能更加合适。https://dev59.com/f2oy5IYBdhLWcg3wCpzQ - jamesSampica
谢谢 :) 听起来是正确的方向,我会查一下。 我会在进展时发布回复。 - IdanB
templateSelector解决方案不起作用(仍然在每次切换标签之间处理viewmodel)。现在我将尝试使用TabControlEx类的解决方案,并更新进展。 - IdanB
TabControlEx 已经可以工作了,但是你提供的链接并没有解决问题,因为它只是一个“部分”解决方案。它缺少控件样式模板才能正常工作,我通过谷歌找到了这个模板 @ https://dev59.com/8mkw5IYBdhLWcg3wlbd3 。再次感谢 :) - IdanB
2个回答

3
解决问题的方法是扩展TabControl并替换默认行为,使其不会卸载旧标签页。最终解决方案(包括控件和控件模板)在以下链接中: Stop TabControl from recreating its children 感谢Shoe指引我正确的方向,帮助我找到最终的解决方案 :)

0

我遇到了类似的问题,然后想出了这个解决方案

    void OnAddWorkSpace(object Sender, EventArgs e)
    {
        WorkspaceViewModel loclViewModel = (e as WorkSpaceEventArgs).ViewModel;
        DataTemplate loclTemplate = (DataTemplate)Resources[new DataTemplateKey(loclViewModel.GetType())];

        if (loclTemplate != null)
        {
            DXTabItem loclTabItem = new DXTabItem();
            loclTabItem.Content = loclTemplate.LoadContent();
            (loclTabItem.Content as DependencyObject).SetValue(DataContextProperty, loclViewModel);
            loclTabItem.HeaderTemplate = (DataTemplate)FindResource("WorkspaceItemTemplate");
            loclTabItem.Header = (loclTabItem.Content as DependencyObject).GetValue(DataContextProperty);
            MainContentTabs.Items.Add(loclTabItem);
        }
    }

我在我的ViewModel中创建了一个事件处理程序,我的View订阅了它。我的ViewModel对象被添加到一个集合中。现在,每当添加一个ViewModel时,我的MainViewModel将调用此事件处理程序。

在这里,我们需要找到为我们正在添加的ViewModel的DataType定义的DataTemplate。一旦我们掌握了这个,我们就可以创建一个选项卡项,然后从数据模板加载内容。

由于我正在使用DevExpress TabControl,所以我创建了DXTabItem。TabItem也应该起同样的作用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接