2
votes

I'm having an issue with a TabControl where I manage (in some special cases) to get two tabs headers selected (only one body showing afaik), and I can't change the selected tab.

Screenshot

Selected tabs have bold header text.

In this image, "Ämnesinformation" and "R43" are both selected.

My application is structured as follows:

I have some views:

  • MainView: The main view, contains the TabControl which only contains one item in the image.
  • SubstanceTabsView: One of these for every tab in MainView.
  • SubstanceView and ClassificationView: the first is used for the "Ämnesinformation", of which there is only one per substance. The second can have multiple instances, like "R43", "R12" etc.

I also have some viewModels:

  • MainViewModel: The VM for the MainView.
  • SubstanceTabsViewModel: The VM for the SubstanceTabsView, contains a set of IViewModels
  • SubstanceViewModel, ClassificationViewModel: both implement IViewModel, are VMs for SubstanceView and ClassificationView

Some relevant xaml code:

Here's the tabcontrol in MainView.xaml

<TabControl SelectedItem="{Binding Path=SelectedTab}" ItemsSource="{Binding Path=Tabs}" >
        <TabControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Header}" >
                    </TextBlock>
                    <local:CrossButton Margin="3" Padding="0" Width="12" Command="{Binding CloseCommand}"/>
                </StackPanel>
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.Resources>
            <Style TargetType="{x:Type TabItem}">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type TabItem}">
                            <Grid>
                                <Border 
                                Name="Border"
                                Margin="0,0,-4,0" 
                                Background="{Binding Path=HeaderBackground}"
                                BorderBrush="#A0A0A0" 
                                BorderThickness="1,1,1,1" 
                                CornerRadius="3,10,0,0" >
                                    <ContentPresenter x:Name="ContentSite"
                                  VerticalAlignment="Center"
                                  HorizontalAlignment="Center"
                                  ContentSource="Header"
                                  Margin="12,2,12,2"
                                  RecognizesAccessKey="True"/>
                                </Border>
                            </Grid>
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsSelected" Value="True">
                                    <Setter Property="FontWeight" Value="Bold" />
                                    <Setter Property="Panel.ZIndex" Value="100" />
                                    <Setter TargetName="Border" Property="Background" Value="{Binding HeaderBackground}" />
                                    <Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
                                </Trigger>
                                <Trigger Property="IsEnabled" Value="False">
                                    <Setter TargetName="Border" Property="Background" Value="Yellow" />
                                    <Setter TargetName="Border" Property="BorderBrush" Value="Black" />
                                    <Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
                                    <Setter Property="Foreground" Value="Green" />
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
            <DataTemplate DataType="{x:Type localViewModels:SubstanceTabsViewModel}">
                <localViews:SubstanceTabsView />
            </DataTemplate>
            </TabControl.Resources>

    </TabControl>

Here's how I control the connection between different views and viewmodels in SubstanceTabsView.xaml

<TabControl SelectedItem="{Binding Path=SelectedTab}"  ItemsSource="{Binding Path=Tabs}">
        <TabControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Header}" />
                    <local:CrossButton Margin="3" Padding="0" Width="12" Command="{Binding CloseCommand}"/>
                </StackPanel>
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.Resources>
            <DataTemplate DataType="{x:Type localViewModels:ClassificationViewModel}">
                <localViews:ClassificationView />
            </DataTemplate>
            <DataTemplate DataType="{x:Type localViewModels:SubstanceViewModel}">
                <localViews:SubstanceView />
            </DataTemplate>
        </TabControl.Resources>
    </TabControl>

Here's the code for SubstanceTabsViewModel.cs which controls the second level tabs, the setter for the selectedTab controls some logic which asks the user about changing from an unsaved tab:

private IViewModel selectedTab;
    public IViewModel SelectedTab
    {
        get
        {
            return selectedTab;
        }
        set
        {
            MessageBoxResult rsltMessageBox = MessageBoxResult.Yes;
            if (selectedTab != null && selectedTab.SaveNeeded() && selectedTab.Id != 0 && value != null && selectedTab is ClassificationViewModel)
            {
                rsltMessageBox = notifyUserService.Ask("Bedömning är ändrad men ej sparad vill du verkligen lämna fliken?", "Bedömning ändrad");
            }
            if (rsltMessageBox == MessageBoxResult.Yes)
            {
                selectedTab = value;
            }
            OnPropertyChanged("SelectedTab");

        }
    }

    private ObservableCollection<IViewModel> tabs;
    public ObservableCollection<IViewModel> Tabs
    {
        get
        {
            return tabs;
        }
        set
        {
            tabs = value;
            OnPropertyChanged("Tabs");
        }
    }

Some things my investigations have resulted in: If I don't do the notifyUserService call (which results in a messagebox.show()), there is no problem, only one tab is selected. If I look at the SelectedItem of the TabControl, it is only one item, the item it "should" be in my situation.

1

1 Answers

1
votes

I finally found someone else having a similar problem, as described here**, "Displaying a message box causes a nested message pump; which means that almost all processing resumes. Of course, we are in the middle of trying to change the selected item, so this can cause all sorts of out-of-order or reentrancy problems. This class of problems is difficult to fix, and we are not going to be able to fix this in our next release." So the problem was with using MessageBox:es in the selectedItem setter.

I guess using some clever workaround is the appropriate solution in this case.

** Update March 2022

  • The URL referenced by the original post is no longer valid. The content can now be found here: WPF TabControl bug