13

I have a user interface with a TabControl that initially displays a start page. Other items can be added to it by double-clicking on content in, for example, a DataGrid. New tabs should be selected when they are created. If the document corresponding to the item in the grid is already open, then the existing tab for that document should be opened rather than creating a new one.

I know that I should be able to programmatically select a tab by setting the TabControl's SelectedItem or SelectedIndex properties. However, the desired tab never actually activates. If I set one and then inspect the TabControl's state in the debugger, then both fields seem to update properly. However, after I continue execution, I see that the selected tab remains unchanged in the UI, and if I pause and inspect the TabControl's state again I see that the SelectedItem and SelectedIndex have returned to their previous values. Selecting a tab by clicking on it in the UI, on the other hand, works just fine.

Here's the declaration for the TabControl:

<TabControl x:Name="Tabs" >
    <TabItem x:Name="StartPageTab" Header="Start Page" DataContext="{Binding Path=StartPageViewModel}">
        ...
    </TabItem>
</TabControl>

And the code for adding and selecting tabs:

private void _SelectTab(MyViewModel model)
{
    TabItem tab;
    if (_TryFindTab(model, out tab)) Tabs.SelectedItem = tab;
}

private bool _TryFindTab(MyViewModel target, out TabItem tab)
{
    foreach (TabItem item in Tabs.Items)
    {
        MyViewModel model = item.DataContext as MyViewModel;
        if (model != null && model.Equals(target))
        {
            tab = item;
            return true;
        }
    }
    tab = null;
    return false;
}

private void _AddTab(MyViewModel model)
{
    TabItem tab = new TabItem { DataContext = model, Content = new MyView() };
    Binding bind = new Binding { Source = model, Path = new PropertyPath("Name") };
    tab.SetBinding(TabItem.HeaderProperty, bind);

    Tabs.Items.Add(tab);
    Tabs.SelectedItem = tab;
}
Sean U
  • 6,730
  • 1
  • 24
  • 43
  • Don't post answers in your question, post it as an actual answer and accept it, or get rid of the question, just don't let it stick around without an accepted answer. – H.B. May 25 '11 at 22:35

4 Answers4

20

It turned out to be related to something I conveniently omitted from the original problem description:

The DataGrid in question was in the content for StartPageTab. I was handling double-clicks on that DataGrid by capturing its MouseDoubleClick event, searching the visual tree to find what DataGridRow was double-clicked (if any), and then raising an event that would eventually be captured by the main window, which would respond by calling either _SelectTab or _AddTab, depending on whether the document in question was already open.

At which point, the call stack would unroll and get back to that MouseDoubleClick event handler. In that handler, I forgot to set the MouseButtonEventArgs's Handled property to true. So WPF kept searching for someone else to handle that click event - and the element that it eventually found would respond by asking for focus, which in turn meant that the original tab needed to get focus back.

Adding e.Handled = true; stopped that whole mess in its tracks, so the new tab could stay selected.

Sean U
  • 6,730
  • 1
  • 24
  • 43
  • 2
    this is **EXACTLY** what happened to me -- i.e. a DataGrid with a trap on double-click within a tab. Your answer saved me heaps of time tracking down this subtle bug. Good job!!! – Stephen Chung Jun 03 '11 at 08:07
  • thanks a bunch... had this problem for a few days now... happy to found the solution !!! – user1841243 Jul 19 '13 at 12:53
  • Thanks for sharing! And by the way: Same problem for other mouse events (at least MouseLeftButtonUp ...) – Aaginor Jan 28 '14 at 16:42
  • I tried many things to solve this issue until I found the solution. It was also a mouse event in my case. Thank you. – Ondrej Janacek Jul 21 '14 at 07:04
  • Yep, I definitely caught this bug. Except I'm using a Command to handle the event, and a custom parameter. Using a full MVVM strategy. – Israel Lopez May 25 '17 at 19:30
  • This improves the solution in a MVVM context. https://stackoverflow.com/questions/7647625/how-to-prevent-invokecommandaction-from-propagating-event-to-parent-elements – Israel Lopez May 25 '17 at 19:47
4

You could try using tab.focus()

I have tabs in my application and this is a quick way to make your selected tab visible.

JMcCarty
  • 759
  • 5
  • 17
  • Thanks for the suggestion, but it's producing the same behavior - Tabs.SelectedItem and Tabs.SelectedIndex update immediately after the call to tab.Focus(), but the tab never actually gets focus and the values are getting set back sometime after control leaves the code behind. – Sean U May 25 '11 at 17:44
  • 1
    Hmm..seems like something else is taking control then outside of the code you posted. You may want to add an event handler to tabs.SelectionChanged and set a breakpoint. This will allow you look at the stack trace to see what may be causing the change. – JMcCarty May 25 '11 at 17:50
  • Thanks! It turns out that there were still some frames related to handling a mouse event buried in the call stack. Ultimately it turned out to be a bug in my code for getting that DataGrid to respond to a double-click - I'll edit the main post to fill in the details. – Sean U May 25 '11 at 19:09
0

Have you tried binding to TabItem.IsSelected and updating that in you view model?

Rob McCready
  • 1,909
  • 1
  • 19
  • 20
0

In an older C# app I had, using page controls, I was able to force the page active by telling the tab control object to select the tab...

MyTabControlWithMultiplePages.SelectTab(PageIWantShown);
DRapp
  • 47,638
  • 12
  • 72
  • 142