Search code examples
wpfmvvmtabcontroltabitemselectedindex

How to select first TabItem after TabControl ItemsSource update?


My application is using TabItems to show different UserControls like login/products/settings page. When you open application only one tab is avaible and selected, that is login page, after successful login this page is removed and other pages are loaded with first one selected. When I log out all pages are removed and login page is loaded again but is not selected. I'm using MVVM pattern so don't want to write in ViewModel what to do in UI.

Is it possible to select ItemTab if it's the only one in the list? For example, to write a method in code behind and connect it to the TabControl SelectedIndex?

I tried to create Event Handler for SourceUpdated and TargetUpdated in code behind but was unsuccessful.

MainWindow.xaml

<TabControl x:Name="ApplicationTabs"
    ItemsSource="{Binding Tabs}"                   
    SelectedIndex="0">

<TabControl.Resources>
    <Style TargetType="{x:Type TabPanel}">
        <Setter Property="HorizontalAlignment" Value="Center" />
    </Style>

    <DataTemplate DataType="{x:Type VMod:LoginViewModel}">
        <Pages:LoginView />
    </DataTemplate>
    <DataTemplate DataType="{x:Type VMod:AdminViewModel}">
        <Pages:AdminView />
    </DataTemplate>
    <DataTemplate DataType="{x:Type VMod:ProductsViewModel}">
        <Pages:ProductsView />
    </DataTemplate>                    
</TabControl.Resources>

<TabControl.ItemTemplate>
    <DataTemplate DataType="{x:Type inter:ITab}">
        <TextBlock>
            <Run Text="{Binding TabName}" />
        </TextBlock>
    </DataTemplate>
</TabControl.ItemTemplate> </TabControl>

MainWindowViewModel.cs (BaseViewModel have INotifyPropertyChanged)

class MainWindowViewModel : BaseViewModel
{
    public static IList<ITab> Tabs { get; set; }        
    public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;

    public MainWindowViewModel()
    {
        Tabs = new ObservableCollection<ITab>();
        LoadLoginPage();
    }

    public static void RaiseStaticPropertyChanged (string PropertyName)
    {StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(PropertyName));}

    public static void LoadLoginPage()
    {
        if (Tabs != null) Tabs.Clear();

        Tabs.Add(new LoginViewModel());     

        RaiseStaticPropertyChanged("Tabs");
    } // LoadTabs() create and load login page to the tab collection

    public static void LoadTabs()
    {
        if (Tabs != null) Tabs.Clear();

        Tabs.Add(new AdminViewModel());
        Tabs.Add(new ProductsViewModel());          

        RaiseStaticPropertyChanged("Tabs");         
    } // LoadTabs() method loads specific tabs to the collection

    public static void LogIn(object x)
    {
        LoggedInUser = (User)x;
        LoadTabs();
    }

    public static void LogOut(object x)
    {
        LoggedInUser = null;
        LoadLoginPage();            
    }   
}

Solution

  • After many trials and errors finally found that my initial idea about Event Handler for TargetUpdated in code behind was right. I just didn't know that my ItemSource Binding required NotifyOnTargetUpdated. Thanks to that I didn't break MVVM rules.

    Corrected Binding:

    <TabControl x:Name="ApplicationTabs"
                    ItemsSource="{Binding Tabs,                                                                                                        
                                  NotifyOnTargetUpdated=True}"
                    Grid.Row="1"                   
                    TargetUpdated="TabsUpdated"> (...) </TabControl>
    

    Code behind:

    private void TabsUpdated(object sender, DataTransferEventArgs e)
        {
            if(ApplicationTabs.Items.Count == 1)
            {
                ApplicationTabs.SelectedIndex = 0;
            }
        }