bobsyauncle
bobsyauncle

Reputation: 162

Tab control not selecting first item in observable collection WPF

I notice that the TabControl that has its ItemsSource property bound to an ObservableCollection does not select the first item in the collection if you do the following:

  1. Load TabControl when the ObservableCollection has a count of 0 (tab items are not showing)
  2. Add an item to the ObservableCollection, observe that one TabItem is now present but not selected

If you add an item to the ObservableCollection before the TabControl comes into focus for the first time, the one item in the TabControl has its TabItem selected. It only seems that when the tab count hits 0 then increases again does it seem to lose its ability to select the first item.

It seems this is by design. Is there any way to get around this issue?

I have the following tabcontrol definition in XAML as follows:

<TabControl ItemsSource="{Binding Sels}" />

Upvotes: 0

Views: 634

Answers (2)

Killian Lomax
Killian Lomax

Reputation: 27

In addition to Corentin's answer I just want to add that, depending on the way you fill that ObservableCollection, you might also just assign the first item to a designated property in the viewmodel.

In my case which is pretty similar to yours I process exported plugins collected from a MEF container in code. When that is finished I just update a property SelectedPlugin in the viewmodel and in XAML I then do a TwoWay-binding on SelectedItem of the TabControl to this property.

This works perfectly and serves exactly your use-case.

Upvotes: 0

Corentin Pane
Corentin Pane

Reputation: 4943

If you give the name tabControl to your TabControl, then you could subscribe to the changes in its items collection in code-behind:

<TabControl ItemsSource="{Binding Sels}" x:Name="tabControl" />
tabControl.ItemContainerGenerator.ItemsChanged += ItemContainerGenerator_ItemsChanged;

and then you can force it to select the first tab if none are selected:

private async void ItemContainerGenerator_ItemsChanged(object sender, ItemsChangedEventArgs e) {
    await Task.Yield();
    if (tabControl.SelectedIndex == -1) {
        if (tabControl.Items.Count > 0) {
            tabControl.SelectedIndex = 0;
        }
    }
}

The call to await Task.Yield() is needed to let WPF finish handling internal TabControl updates before changing its SelectedIndex property.


To be extra fancy,

you could put this in an attached DependencyProperty and in fine have a cleaner XAML and no extra code-behind:

<TabControl ItemsSource="{Binding Sels}" local:AutoSelect.AutoSelectFirstTab="true"/>

Here is the same behaviour but wrapped using WPF attached DependencyProperty:

public static class AutoSelect {
    public static readonly DependencyProperty AutoSelectFirstTabProperty = DependencyProperty.RegisterAttached(
        "AutoSelectFirstTab",
        typeof(bool),
        typeof(AutoSelect),
        new PropertyMetadata(false, AutoSelectFirstTabChanged));

    public static bool GetAutoSelectFirstTab(TabControl obj) => (bool)obj.GetValue(AutoSelectFirstTabProperty);
    public static void SetAutoSelectFirstTab(TabControl obj, bool value) => obj.SetValue(AutoSelectFirstTabProperty, value);

    private static void AutoSelectFirstTabChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
        var tabControl = (TabControl)d;
        tabControl.ItemContainerGenerator.ItemsChanged += ItemContainerGenerator_ItemsChanged;
        async void ItemContainerGenerator_ItemsChanged(object _1, ItemsChangedEventArgs _2) {
            await Task.Yield();
            if (GetAutoSelectFirstTab(tabControl)) {
                if (tabControl.SelectedIndex == -1) {
                    if (tabControl.Items.Count > 0) {
                        tabControl.SelectedIndex = 0;
                    }
                }
            }
        }
    }
}

Upvotes: 1

Related Questions