Search code examples
c#wpfribbon

WPF IsSelected binding for RibbonTab stops working upon ctrl+tab


This is a minimized error reproduction of RibbonTab IsSelected not working.

Here is the main window of the demonstration application:

enter image description here

When we click the application menu (blue arrow), a menu pops down.

enter image description here

If we click main view, a tab item is added to a tabpanel.

enter image description here

"main view" is both the header of the tab and the contents of the tab item, nothing strange.

If we also click extra view, we got this:

enter image description here

We can see that a tab has became visible and extra stuff shows in the ribbon. If we switch between the two tabpanel tabs, the extra RibbonTab is only visible when "extra view" is selected.

Now, if we pick the "extra view" tab so that the extra RibbonTab is visible and click the textbox so we have a blinking cursor in it and press ctrl+TAB a couple of times to navigate the elements in tab order, we soon have the tab with the header "extra view" selected again. The extra RibbonTab will then look different from what we expect.

enter image description here

The only thing that is left is the tab text "extra tab", the textbox is not there any longer. Why not?

If we click the tabs in the tabpanel (showing "main view" and "extra view", "extra tab" is only visible when "extra view" is selected. This is an expected behavior. However, the textbox and the ribbon tab delimiter is not there any longer which is unexpected.

The visibility of the RibbonTab is bound to the type of selected TabItem in the TabPanel, using a converter that returns Visibility.Visible if the tab item is of type ExtraView but not if the type is something else (like MainView).

The IsSelected property of the RibbonTab is also bound to the selected tabitem, using a converter that returns true if the tab item is of type ExtraView.

This is as mentioned a minimal example of a program where the menu tabs will be more than just "extra tab" so it is important that it will become selected when the type of the tabpanels content is correct. I say this to stress that IsSelected is needed (in addition to Visibility).

Any help is greatly appreciated.

MainWindow.xaml:

<RibbonWindow x:Class="TestProblematicRibbons.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:test="clr-namespace:TestProblematicRibbons"
    xmlns:Converters="clr-namespace:TestProblematicRibbons.Converters"
    Height="300" Width="350">
  <RibbonWindow.DataContext>
    <test:MainWindowViewModel/>
  </RibbonWindow.DataContext>
  <RibbonWindow.Resources>
    <Converters:ExtraRibbonTabBooleanConverter x:Key="ExtraRibbonTabBoolean" />
    <Converters:ExtraRibbonTabVisibilityConverter x:Key="ExtraRibbonTabVisibility" />
  </RibbonWindow.Resources>
  <DockPanel>
    <Ribbon DockPanel.Dock="Top">
      <Ribbon.ApplicationMenu>
        <RibbonApplicationMenu>
          <RibbonApplicationMenuItem Header="main view"
            Command="{Binding CreateMainViewCommand}"/>
          <RibbonApplicationMenuItem Header="extra view"
            Command="{Binding CreateExtraViewCommand}"/>
        </RibbonApplicationMenu>
      </Ribbon.ApplicationMenu>

      <RibbonTab Header="extra tab"
          IsSelected="{Binding SelectedTab, Mode=OneWay, Converter={StaticResource ExtraRibbonTabBoolean}}"
          Visibility="{Binding SelectedTab, Mode=OneWay, Converter={StaticResource ExtraRibbonTabVisibility}}">
        <RibbonGroup>
          <RibbonTextBox />
        </RibbonGroup>
      </RibbonTab>
    </Ribbon>
    <TabControl
      ItemsSource="{Binding Tabs}" SelectedItem="{Binding SelectedTab}" />
  </DockPanel>
</RibbonWindow>

MainWindowModel.cs:

using System.Collections.ObjectModel;
using System.Windows.Controls;
using System.Windows.Input;
using ReactiveUI;

namespace TestProblematicRibbons
{
  public class MainWindowViewModel: ReactiveObject
  {
    private ObservableCollection<TabItem> _tabs
      = new ObservableCollection<TabItem>();

    public ObservableCollection<TabItem> Tabs
    {
      get { return _tabs; }
    }

    private TabItem _SelectedTab;
    public TabItem SelectedTab
    {
      get { return _SelectedTab; }
      set { this.RaiseAndSetIfChanged(x => x.SelectedTab, value); }
    }

    public ICommand CreateMainViewCommand
    { get { return new ActionCommand(CreateMainView); } }

    public ICommand CreateExtraViewCommand
    { get { return new ActionCommand(CreateExtraView); } }

    private void CreateMainView()
    {
      var view = new MainView();
      AddTab(view, "main view");
    }

    private void CreateExtraView()
    {
      var view = new ExtraView();
      AddTab(view, "extra view");
    }

    private void AddTab(UserControl view, string header)
    {
      var tab = new TabItem();
      tab.Header = header;
      tab.Content = view;
      _tabs.Add(tab);

      SelectedTab = tab;
    }
  }
}

ExtraRibbonTabBooleanConverter:

using System;
using System.Globalization;
using System.Windows.Controls;
using System.Windows.Data;

namespace TestProblematicRibbons.Converters
{
  public class ExtraRibbonTabBooleanConverter: IValueConverter
  {
    public object Convert
      (object value, Type targetType, object parameter, CultureInfo culture)
    {
      var item = value as TabItem;

      return item != null && item.Content is ExtraView;
    }

    public object ConvertBack
      (object value, Type targetType, object parameter, CultureInfo culture)
    {
      throw new NotImplementedException();
    }
  }
}

ExtraRibbonTabVisibilityConverter:

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace TestProblematicRibbons.Converters
{
  public class ExtraRibbonTabVisibilityConverter: IValueConverter
  {
    public object Convert
      (object value, Type targetType, object parameter, CultureInfo culture)
    {
      var item = value as TabItem;

      return item != null && item.Content is ExtraView
        ? Visibility.Visible
        : Visibility.Collapsed;
    }

    public object ConvertBack
      (object value, Type targetType, object parameter, CultureInfo culture)
    {
      throw new NotImplementedException();
    }
  }
}

MainView and ExtraView are only two UserControl:s with labels on them, displaying "main view" and "extra view" respectively, so there is little point pasting the code for them here.


Solution

  • I have solved my problem by implementing bindings manually in some cases. Seems like bindings in combination cannot be fully relied upon. They can disturb each other apparently!