WPF TabControl MVVM ViewModel get instantiated every time i toggle between tabs [duplicate]
Asked Answered
D

2

3

I wrote some WPF application with MVVM pattern that holds a TabControl bound to collection of "TabViewModelItem".

The main window 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>

and the ViewModel that hold collection of tab is code:

 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)

        }

}

So basically it's tab control that is bound to a collection of "TabViews". and based of the object data type it's showing View1 or View2. Note: View1 & View 2 are UserControls, each bound to it's own ViewModel. This concept works fine.

Now where is the problem you my ask ? My problem is: EVERY time I click on another tab & then return to same tab, I get that specifc tab ViewModel constructor called again, where as I would expect the ViewModel object would remain.

This is problem, because it cause me to lose any modification made on that page, when I toggle between tabs. and since the ctor is called everytime, over & over, I can't even use the VIewModel to store this information.

My questions are: 1) Is there any way I can prevent the TabControl to dispose ViewModel objects when tab is inactive ? Meaning to pre-create all ViewModel's object & not dispose them when hidden ? 2) What "workarounds" using this concept exist, that allow me to store "visual tree" of the given tab, so if i navigate away from it & then re-open it, it will store all information on it (such as selected check boxes, written text, etc.)

Would appreciate any help on matter.

regards, Idan

Ddt answered 7/9, 2013 at 14:22 Comment(5)
I've saw that post, but i'm afraid I don't see how "writer" sample in "WpfApplicationFramework" is answer to this problem, since it's use single view for all documents, where-as i use seperate view for each tab (each bound to it's own viewmodel).Ddt
This question may be more appropriate then. #8808576Boltzmann
Thanks :) sounds like the right direction, i'll check it up. Will post reply as I progress.Ddt
The templateSelector solution does not work (still dispose viewmodel on each toggle betwwen tabs). I'm going to try the TabControlEx class solution now, will update on itDdt
The TabControlEx worked, but the link you gave did not solved it, as it's only "parital" solution. It's missing control style template for it to work, which i came across via google @ #9794651 . Thanks again :)Ddt
D
3

Solution to problem is extending TabControl and replacing default behaviour so it will not unload old tabs. The final solution (with include's both control & control template) is @ Stop TabControl from recreating its children

Thanks for Shoe for pointing me in right direction that lead to final solution :)

Ddt answered 19/9, 2013 at 9:33 Comment(0)
C
0

I have the similar problem and i came up with this solution

    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);
        }
    }

I have created an event handler in my ViewModel and my View subscribes to that. My ViewModel objects get added to a collection. Now, whenever a ViewModel is added, my MainViewModel will call this even handler.

Here, we need to find the DataTemplate that would have been defined for the DataType of the ViewModel that we are adding. Once we get hold of that, we can create a tab item and then load the content from the datatemplate.

Since i am using DevExpress TabControl, i have created DXTabItem. A TabItem should also work the same.

Catchall answered 8/1, 2014 at 20:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.