2

I have a tabcontrol like this:

<TabControl>
    <local:TabItem x:Name="son" Header="son">
        <local_son:SonView />
    </local:TabItem>
    <local:TabItem x:Name="daughter" Header="daughter">
        <local_daughter:DaughterView />
    </local:TabItem>
</TabControl>

There is a button in DaughterView, I want to click this button to switch to son tab. My questions is how can I reach to tabindex of son tab in the DaughterView?

Thank you in advance!

2 Answers2

0

Seems kinda odd to have a button inside of a tab switch to another tab. I would think that would be a toolbar button or something like that. I.e. outside of the tab. But if you insist :)... I'd use the messenger / event aggregator pattern and post an event and have the view subscribe and switch the tab. I wouldn't have the child view do it byitself.

SledgeHammer
  • 7,338
  • 6
  • 41
  • 86
0

You need to bind "SelectedIndex" to a property in your view model. I personally like keeping things type-safe and able to be unit-tested, so when I need to manipulate TabControls in code I usually start by declaring an enum with one value for each tab:

public enum MyTabs : int
{
    [Description("Tab 1")]
    Tab1,

    [Description("Tab 2")]
    Tab2,

    [Description("Tab 3")]
    Tab3
}

The description attribute is the text I want displayed in the tab header, more on that in a moment. My view model contains a member of type MyTabs which is updated whenever the user clicks a tab and which I can also set manually myself via code:

public class MyViewModel : ViewModelBase
{
    private MyTabs _CurrentTab;
    public MyTabs CurrentTab
    {
        get { return this._CurrentTab;}
        set { this._CurrentTab = value; RaisePropertyChanged(() => this.CurrentTab); }
    }       
}

Now you need to bind your TabControl to this property:

<TabControl
    ItemsSource="{Binding Source={StaticResource MyTabs}}"
    SelectedIndex="{Binding CurrentTab, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
    <TabControl.Resources>
        <Style TargetType="{x:Type TabItem}">
            <Setter Property="Header" Value="{Binding Path=., Converter={StaticResource EnumDescriptionConverter}}" />
        </Style>
    </TabControl.Resources>
</TabControl>

Unfortunately WPF binding isn't smart enough to work with integer enums, so I'm also using a converter to cast between enums and integers:

public class EnumToIntConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return (int)value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return Enum.ToObject(targetType, value);
    }
}

There are a few other things going on here...first of all you'll notice that I'm not actually declaring the TabItems anywhere. That's because I'm generating them automatically from the Enum values themselves; if I add a new value to the MyTab enum then a tab for it magically appears! In this case I'm binding to a static resource with the key "MyTabs", that's a ObjectDataProvider that enumerates the values in my enum:

    <ObjectDataProvider x:Key="MyTabs" MethodName="GetValues" ObjectType="{x:Type sys:Enum}">
        <ObjectDataProvider.MethodParameters>
            <x:Type TypeName="local:MyTabs"/>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>

This raises the question of how the tabs know what to display in their headers and on the tabitem content areas. The headers use the "Description" attribute declared in the enum, the code for the EnumDescriptionConverter is on another page on this site. To specify the content for each page I create a ControlTemplate for each of my enum values and key it to the enum value itself. A data template is then used to select the appropriate one to use for each tab:

<Window.Resources>

    <ControlTemplate x:Key="{x:Static local:MyTabs.Tab1}">
        <TextBlock Text="This is the first tab" />
    </ControlTemplate>

    <ControlTemplate x:Key="{x:Static local:MyTabs.Tab2}">
        <TextBlock Text="This is the second tab" />
    </ControlTemplate>

    <ControlTemplate x:Key="{x:Static local:MyTabs.Tab3}">
        <TextBlock Text="This is the third tab" />
    </ControlTemplate>

    <DataTemplate DataType="{x:Type local:MyTabs}">
        <ContentControl>
            <ContentControl.Template>
                <MultiBinding Converter="{StaticResource ResourceKey=BindingToResourceConverter}">
                    <Binding RelativeSource="{RelativeSource AncestorType={x:Type Window}}" Path="Resources" />
                    <Binding />     
                </MultiBinding>
                </ContentControl.Template>
            </ContentControl>
        </DataTemplate>

</Window.Resources>

The final piece of the puzzle is the BindingToResourceConverter which simply takes a binding (i.e. one of the enum values) and uses it to look up the appropriate ControlTemplate:

public class BindingToResourceConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return (values[0] as ResourceDictionary)[values[1]];
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

And that's it! Every time I want to add a new page to a TabControl I simply add a value to it's corresponding enum and create a ContentControl key'd to that value. Everything else happens automatically, and best of all it's both type-safe and unit-testable.

Community
  • 1
  • 1
Mark Feldman
  • 15,731
  • 3
  • 31
  • 58
  • No offense intended :), but I don't get your implementation here. It really makes no sense. You just hard-coded the 3 tabs, but moved part of the view's "hard-coding" (XAML) to the VM. You're already binding to a collection, it would have been much simpler to make the Header a property of the collection. You also create the need for a bunch of un-necessary templates, you disallow UI editing of the tabs / tab order since its all hard-coded and you also kinda break XAML localization if you need that. And of course you can't add/remove tabs at runtime with this. – SledgeHammer Aug 14 '15 at 02:48
  • No offense taken! :) Yes, moving the XAML to the VM is in fact the whole point of the exercise as it facilitates unit testing e.g. does clicking the button actually cause the relevant tab to be activated? This specific example enumerated the enum to generate the tabs but I'm using data templates so there's nothing to stop you using an ObservableCollection containing these enums (boxed) and other data types, which also solves the problem of adding and removing tabs. Totally agree with you re localization, in reality I don't use the Description attribute but I wanted to keep things simple. :) – Mark Feldman Aug 14 '15 at 02:58