title: Managing persistent navigation views (MVVM, WPF)
body: <p>I'm slowly learning MVVM in WPF. I code a GBA game editor. The editor consist of a main window (Editor.xaml) and depending which editor is selected in the menu, I'd like to display the corresponding persistent view (that is not destroyer when switching).</p>
  
  <p>I'm trying to get working the TabControlEx class found in a few posts on SO such as <a href="https://stackoverflow.com/questions/7837587/wpf-preserve-tab-control-state">here</a> and <a href="https://stackoverflow.com/questions/12432062/binding-to-itemssource-of-tabcontrol-in-wpf">here</a>.</p>
  
  <p>However I run into two problem: first, <em>tabControlEx selectedItem does not change</em> (fixed see edit) and second it seems on the TabItem OnMouseOver I lose the View (having a color Background property for the TabItem will switch to white color).</p>
  
  <p>Now I'm pretty sure I'm missing something more or less obvious,  but being new to MVVM I don't really know where to look. So here the code breakdown.</p>
  
  <p><strong>TabControlEx.cs</strong></p>
  
  <pre class="lang-cs prettyprint-override"><code>[TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))]
  public class TabControlEx : System.Windows.Controls.TabControl
  {
      // Holds all items, but only marks the current tab's item as visible
      private Panel _itemsHolder = null;
  
      // Temporaily holds deleted item in case this was a drag/drop operation
      private object _deletedObject = null;
  
      public TabControlEx()
          : base()
      {
          // this is necessary so that we get the initial databound selected item
          this.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
      }
  
      /// &lt;summary&gt;
      /// if containers are done, generate the selected item
      /// &lt;/summary&gt;
      /// &lt;param name="sender"&gt;&lt;/param&gt;
      /// &lt;param name="e"&gt;&lt;/param&gt;
      void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
      {
          if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
          {
              this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;
              UpdateSelectedItem();
          }
      }
  
      /// &lt;summary&gt;
      /// get the ItemsHolder and generate any children
      /// &lt;/summary&gt;
      public override void OnApplyTemplate()
      {
          base.OnApplyTemplate();
          _itemsHolder = GetTemplateChild("PART_ItemsHolder") as Panel;
          UpdateSelectedItem();
      }
  
      /// &lt;summary&gt;
      /// when the items change we remove any generated panel children and add any new ones as necessary
      /// &lt;/summary&gt;
      /// &lt;param name="e"&gt;&lt;/param&gt;
      protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
      {
          base.OnItemsChanged(e);
  
          if (_itemsHolder == null)
          {
              return;
          }
  
          switch (e.Action)
          {
              case NotifyCollectionChangedAction.Reset:
                  _itemsHolder.Children.Clear();
  
                  if (base.Items.Count &gt; 0)
                  {
                      base.SelectedItem = base.Items[0];
                      UpdateSelectedItem();
                  }
  
                  break;
  
              case NotifyCollectionChangedAction.Add:
              case NotifyCollectionChangedAction.Remove:
  
                  // Search for recently deleted items caused by a Drag/Drop operation
                  if (e.NewItems != null &amp;&amp; _deletedObject != null)
                  {
                      foreach (var item in e.NewItems)
                      {
                          if (_deletedObject == item)
                          {
                              // If the new item is the same as the recently deleted one (i.e. a drag/drop event)
                              // then cancel the deletion and reuse the ContentPresenter so it doesn't have to be 
                              // redrawn. We do need to link the presenter to the new item though (using the Tag)
                              ContentPresenter cp = FindChildContentPresenter(_deletedObject);
                              if (cp != null)
                              {
                                  int index = _itemsHolder.Children.IndexOf(cp);
  
                                  (_itemsHolder.Children[index] as ContentPresenter).Tag =
                                      (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));
                              }
                              _deletedObject = null;
                          }
                      }
                  }
  
                  if (e.OldItems != null)
                  {
                      foreach (var item in e.OldItems)
                      {
  
                          _deletedObject = item;
  
                          // We want to run this at a slightly later priority in case this
                          // is a drag/drop operation so that we can reuse the template
                          this.Dispatcher.BeginInvoke(DispatcherPriority.DataBind,
                              new Action(delegate ()
                              {
                                  if (_deletedObject != null)
                                  {
                                      ContentPresenter cp = FindChildContentPresenter(_deletedObject);
                                      if (cp != null)
                                      {
                                          this._itemsHolder.Children.Remove(cp);
                                      }
                                  }
                              }
                          ));
                      }
                  }
  
                  UpdateSelectedItem();
                  break;
  
              case NotifyCollectionChangedAction.Replace:
                  throw new NotImplementedException("Replace not implemented yet");
          }
      }
  
      /// &lt;summary&gt;
      /// update the visible child in the ItemsHolder
      /// &lt;/summary&gt;
      /// &lt;param name="e"&gt;&lt;/param&gt;
      protected override void OnSelectionChanged(SelectionChangedEventArgs e)
      {
          base.OnSelectionChanged(e);
          UpdateSelectedItem();
      }
  
      /// &lt;summary&gt;
      /// generate a ContentPresenter for the selected item
      /// &lt;/summary&gt;
      void UpdateSelectedItem()
      {
          if (_itemsHolder == null)
          {
              return;
          }
  
          // generate a ContentPresenter if necessary
          TabItem item = GetSelectedTabItem();
          if (item != null)
          {
              CreateChildContentPresenter(item);
          }
  
          // show the right child
          foreach (ContentPresenter child in _itemsHolder.Children)
          {
              child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed;
          }
      }
  
      /// &lt;summary&gt;
      /// create the child ContentPresenter for the given item (could be data or a TabItem)
      /// &lt;/summary&gt;
      /// &lt;param name="item"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      ContentPresenter CreateChildContentPresenter(object item)
      {
          if (item == null)
          {
              return null;
          }
  
          ContentPresenter cp = FindChildContentPresenter(item);
  
          if (cp != null)
          {
              return cp;
          }
  
          // the actual child to be added.  cp.Tag is a reference to the TabItem
          cp = new ContentPresenter();
          cp.Content = (item is TabItem) ? (item as TabItem).Content : item;
          cp.ContentTemplate = this.SelectedContentTemplate;
          cp.ContentTemplateSelector = this.SelectedContentTemplateSelector;
          cp.ContentStringFormat = this.SelectedContentStringFormat;
          cp.Visibility = Visibility.Collapsed;
          cp.Tag = (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));
          _itemsHolder.Children.Add(cp);
          return cp;
      }
  
      /// &lt;summary&gt;
      /// Find the CP for the given object.  data could be a TabItem or a piece of data
      /// &lt;/summary&gt;
      /// &lt;param name="data"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      ContentPresenter FindChildContentPresenter(object data)
      {
          if (data is TabItem)
          {
              data = (data as TabItem).Content;
          }
  
          if (data == null)
          {
              return null;
          }
  
          if (_itemsHolder == null)
          {
              return null;
          }
  
          foreach (ContentPresenter cp in _itemsHolder.Children)
          {
              if (cp.Content == data)
              {
                  return cp;
              }
          }
  
          return null;
      }
  
      /// &lt;summary&gt;
      /// copied from TabControl; wish it were protected in that class instead of private
      /// &lt;/summary&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      protected TabItem GetSelectedTabItem()
      {
          object selectedItem = base.SelectedItem;
          if (selectedItem == null)
          {
              return null;
          }
  
          if (_deletedObject == selectedItem)
          {
  
          }
  
          TabItem item = selectedItem as TabItem;
          if (item == null)
          {
              item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem;
          }
          return item;
      }
  }
  </code></pre>
  
  <p>My main view:</p>
  
  <p><strong>Editor.xaml</strong>
  </p>
  
  <pre><code>&lt;Window.Resources&gt;
      &lt;ResourceDictionary Source="Resources/GlobalDictionary.xaml" /&gt;
  &lt;/Window.Resources&gt;
  &lt;DockPanel&gt;
      &lt;DockPanel DockPanel.Dock="Top" KeyboardNavigation.TabNavigation="None"&gt;
          &lt;Menu Height="20" KeyboardNavigation.TabNavigation="Cycle"&gt;
              &lt;MenuItem Header="{StaticResource MenuFile}"&gt;
                  &lt;MenuItem Header="{StaticResource MenuOpen}" /&gt;
                  &lt;MenuItem Header="{StaticResource MenuSave}" /&gt;
                  &lt;MenuItem Header="{StaticResource MenuExit}" Command="{Binding Path=CloseCommand}" /&gt;
              &lt;/MenuItem&gt;
              &lt;MenuItem Header="{StaticResource MenuEditors}"&gt;
                  &lt;MenuItem Header="{StaticResource MenuMonster}" Command="{Binding Path=OpenMonsterEditor}"/&gt;
              &lt;/MenuItem&gt;
              &lt;MenuItem Header="{StaticResource MenuHelp}"&gt;
                  &lt;MenuItem Header="{StaticResource MenuAbout}" /&gt;
                  &lt;MenuItem Header="{StaticResource MenuContact}" /&gt;
              &lt;/MenuItem&gt;
          &lt;/Menu&gt;
      &lt;/DockPanel&gt;
      &lt;controls:TabControlEx ItemsSource="{Binding AvailableEditors}"
                  SelectedItem="{Binding CurrentEditor}"
                  Style="{StaticResource BlankTabControlTemplate}"&gt;
      &lt;/controls:TabControlEx&gt;
  &lt;/DockPanel&gt;
  </code></pre>
  
  <p><strong>GlobalDictionary.xaml</strong>
  </p>
  
  <pre><code>&lt;DataTemplate DataType="{x:Type vm:GBARomViewModel}"&gt;
      &lt;vw:GBARomView /&gt;
  &lt;/DataTemplate&gt;
  
  &lt;DataTemplate DataType="{x:Type vm:MonsterViewModel}"&gt;
      &lt;vw:MonsterView /&gt;
  &lt;/DataTemplate&gt;
  
  &lt;Style x:Key="BlankTabControlTemplate" TargetType="{x:Type control:TabControlEx}"&gt;
      &lt;Setter Property="SnapsToDevicePixels" Value="true"/&gt;
      &lt;Setter Property="Template"&gt;
          &lt;Setter.Value&gt;
              &lt;ControlTemplate TargetType="{x:Type control:TabControlEx}"&gt;
                  &lt;DockPanel&gt;
                      &lt;!-- This is needed to draw TabControls with Bound items --&gt;
                      &lt;StackPanel IsItemsHost="True" Height="0" Width="0" /&gt;
                      &lt;Grid x:Name="PART_ItemsHolder" /&gt;
                  &lt;/DockPanel&gt;
              &lt;/ControlTemplate&gt;
          &lt;/Setter.Value&gt;
      &lt;/Setter&gt;
  &lt;/Style&gt;
  </code></pre>
  
  <p>I'm not sure if the following is right, but each view extent TabItem as follow. The first view on startup is shown correctly.</p>
  
  <p><strong>GBARomView.xaml</strong>
  </p>
  
  <pre><code>&lt;TabItem x:Class="FF6AE.View.GBARomView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
        xmlns:local="clr-namespace:FF6AE.View"
        mc:Ignorable="d" 
        d:DesignHeight="380" d:DesignWidth="646"
        Background="BlueViolet"&gt;
  
      &lt;Grid &gt;
  
      &lt;/Grid&gt;
  &lt;/TabItem&gt;
  </code></pre>
  
  <p><strong>GBARomView.xaml.cs</strong></p>
  
  <pre class="lang-cs prettyprint-override"><code>public partial class GBARomView : TabItem
  {
      public GBARomView()
      {
          InitializeComponent();
      }
  }
  </code></pre>
  
  <p>Finally my main viewModel:</p>
  
  <p><strong>EDitorViewModel.cs</strong></p>
  
  <pre class="lang-cs prettyprint-override"><code>public class EditorViewModel: ViewModelBase
  {
      ViewModelBase _currentEditor;
      ObservableCollection&lt;ViewModelBase&gt; _availableEditors;
      RelayCommand _closeCommand;
      RelayCommand _OpenMonsEditorCommand;
  
      public EditorViewModel()
      {
          base.DisplayName = (string)AppInst.GetResource("EditorName");
  
          _availableEditors = new ObservableCollection&lt;ViewModelBase&gt;();
          _availableEditors.Add(new GBARomViewModel());
          _availableEditors.Add(new MonsterViewModel());
          _availableEditors.CollectionChanged += this.OnAvailableEditorsChanged;
  
          _currentEditor = _availableEditors[0];
          _currentEditor.PropertyChanged += this.OnSubEditorChanged;
      }
  
      public ViewModelBase CurrentEditor
      {
          get
          {
              if (_currentEditor == null)
              {
                  _currentEditor = new GBARomViewModel();
                  _currentEditor.PropertyChanged += this.OnSubEditorChanged;
              }
  
              return _currentEditor;
          }
          set
          {
              _currentEditor = value;
               // this is the thing I was missing
               OnPropertyChanged("CurrentEditor");
          }
      }
  
      void OnSubEditorChanged(object sender, PropertyChangedEventArgs e)
      {
          // For future use
      }
  
      public ObservableCollection&lt;ViewModelBase&gt; AvailableEditors
      {
          get
          {
              return _availableEditors;
          }
      }
  
      void OnAvailableEditorsChanged(object sender, NotifyCollectionChangedEventArgs e)
      {
          // For future use
      }
  
      public ICommand OpenMonsterEditor
      {
          get
          {
              if (_OpenMonsEditorCommand == null)
                  _OpenMonsEditorCommand = new RelayCommand(param =&gt; this.OpenRequestOpen());
  
              return _OpenMonsEditorCommand;
          }
      }
      void OpenRequestOpen()
      {
          _currentEditor = _availableEditors[1];
      }
  
      public ICommand CloseCommand
      {
          get
          {
              if (_closeCommand == null)
                  _closeCommand = new RelayCommand(param =&gt; this.OnRequestClose());
  
              return _closeCommand;
          }
      }
  
      public event EventHandler RequestClose;
  
      void OnRequestClose()
      {
          EventHandler handler = this.RequestClose;
          if (handler != null)
              handler(this, EventArgs.Empty);
      }
  }
  </code></pre>
  
  <p>So basically <em>I am lost why clicking on the monster editor in the menu does not switch the view though currentEditor value is changed</em> (fixed) and why on mouse over on the tabItem I lose the background testing color of the view (it turns to white). &lt;- still not fixed!</p>
  
  <p>Any help would be appreciated. Thanks in advance.</p>
  
  <p>Edit: I was missing the <em>OnPropertyChanged("CurrentEditor");</em> in the setter of <em>CurrentEditor</em>.</p>