2016-06-11 3 views
0

Ich lerne langsam MVVM in WPF. Ich kodiere einen GBA-Spieleditor. Der Editor besteht aus einem Hauptfenster (Editor.xaml) und abhängig davon, welcher Editor im Menü ausgewählt ist, möchte ich die entsprechende persistente Ansicht anzeigen (die beim Umschalten nicht zerstört wird).Verwalten von persistenten Navigationsansichten (MVVM, WPF)

Ich versuche zu arbeiten, die TabControlEx-Klasse in einigen Posts auf SO wie here und here gefunden.

Jedoch habe ich in zwei Probleme führen: Erstens, tabControlEx selectedItem nicht (fest siehe Bearbeiten) ändern und zweiten scheint es auf dem TabItem OnMouseOver verliere ich die Ansicht (eine Farbe Hintergrund-Eigenschaft für das TabItem mit Weiß wechselt Farbe).

Jetzt bin ich mir ziemlich sicher, dass mir etwas mehr oder weniger offensichtlich fehlt, aber als MVVM-Neuling weiß ich nicht wirklich, wo ich hinschauen soll. Also hier der Code-Breakdown.

TabControlEx.cs

[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; 
    } 

    /// <summary> 
    /// if containers are done, generate the selected item 
    /// </summary> 
    /// <param name="sender"></param> 
    /// <param name="e"></param> 
    void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) 
    { 
     if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) 
     { 
      this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged; 
      UpdateSelectedItem(); 
     } 
    } 

    /// <summary> 
    /// get the ItemsHolder and generate any children 
    /// </summary> 
    public override void OnApplyTemplate() 
    { 
     base.OnApplyTemplate(); 
     _itemsHolder = GetTemplateChild("PART_ItemsHolder") as Panel; 
     UpdateSelectedItem(); 
    } 

    /// <summary> 
    /// when the items change we remove any generated panel children and add any new ones as necessary 
    /// </summary> 
    /// <param name="e"></param> 
    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 > 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 && _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"); 
     } 
    } 

    /// <summary> 
    /// update the visible child in the ItemsHolder 
    /// </summary> 
    /// <param name="e"></param> 
    protected override void OnSelectionChanged(SelectionChangedEventArgs e) 
    { 
     base.OnSelectionChanged(e); 
     UpdateSelectedItem(); 
    } 

    /// <summary> 
    /// generate a ContentPresenter for the selected item 
    /// </summary> 
    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; 
     } 
    } 

    /// <summary> 
    /// create the child ContentPresenter for the given item (could be data or a TabItem) 
    /// </summary> 
    /// <param name="item"></param> 
    /// <returns></returns> 
    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; 
    } 

    /// <summary> 
    /// Find the CP for the given object. data could be a TabItem or a piece of data 
    /// </summary> 
    /// <param name="data"></param> 
    /// <returns></returns> 
    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; 
    } 

    /// <summary> 
    /// copied from TabControl; wish it were protected in that class instead of private 
    /// </summary> 
    /// <returns></returns> 
    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; 
    } 
} 

Meine Hauptansicht:

Editor.xaml

<Window.Resources> 
    <ResourceDictionary Source="Resources/GlobalDictionary.xaml" /> 
</Window.Resources> 
<DockPanel> 
    <DockPanel DockPanel.Dock="Top" KeyboardNavigation.TabNavigation="None"> 
     <Menu Height="20" KeyboardNavigation.TabNavigation="Cycle"> 
      <MenuItem Header="{StaticResource MenuFile}"> 
       <MenuItem Header="{StaticResource MenuOpen}" /> 
       <MenuItem Header="{StaticResource MenuSave}" /> 
       <MenuItem Header="{StaticResource MenuExit}" Command="{Binding Path=CloseCommand}" /> 
      </MenuItem> 
      <MenuItem Header="{StaticResource MenuEditors}"> 
       <MenuItem Header="{StaticResource MenuMonster}" Command="{Binding Path=OpenMonsterEditor}"/> 
      </MenuItem> 
      <MenuItem Header="{StaticResource MenuHelp}"> 
       <MenuItem Header="{StaticResource MenuAbout}" /> 
       <MenuItem Header="{StaticResource MenuContact}" /> 
      </MenuItem> 
     </Menu> 
    </DockPanel> 
    <controls:TabControlEx ItemsSource="{Binding AvailableEditors}" 
       SelectedItem="{Binding CurrentEditor}" 
       Style="{StaticResource BlankTabControlTemplate}"> 
    </controls:TabControlEx> 
</DockPanel> 

GlobalDictionary.xaml

Ich bin mir nicht sicher, ob das Folgende richtig ist, aber jede Ansicht erstreckt TabItem wie folgt. Die erste Ansicht beim Start wird korrekt angezeigt.

GBARomView.xaml

<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"> 

    <Grid > 

    </Grid> 
</TabItem> 

GBARomView.xaml.cs

public partial class GBARomView : TabItem 
{ 
    public GBARomView() 
    { 
     InitializeComponent(); 
    } 
} 

schließlich mein Hauptansichtsmodell:

EDitorViewModel.cs

public class EditorViewModel: ViewModelBase 
{ 
    ViewModelBase _currentEditor; 
    ObservableCollection<ViewModelBase> _availableEditors; 
    RelayCommand _closeCommand; 
    RelayCommand _OpenMonsEditorCommand; 

    public EditorViewModel() 
    { 
     base.DisplayName = (string)AppInst.GetResource("EditorName"); 

     _availableEditors = new ObservableCollection<ViewModelBase>(); 
     _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<ViewModelBase> AvailableEditors 
    { 
     get 
     { 
      return _availableEditors; 
     } 
    } 

    void OnAvailableEditorsChanged(object sender, NotifyCollectionChangedEventArgs e) 
    { 
     // For future use 
    } 

    public ICommand OpenMonsterEditor 
    { 
     get 
     { 
      if (_OpenMonsEditorCommand == null) 
       _OpenMonsEditorCommand = new RelayCommand(param => this.OpenRequestOpen()); 

      return _OpenMonsEditorCommand; 
     } 
    } 
    void OpenRequestOpen() 
    { 
     _currentEditor = _availableEditors[1]; 
    } 

    public ICommand CloseCommand 
    { 
     get 
     { 
      if (_closeCommand == null) 
       _closeCommand = new RelayCommand(param => this.OnRequestClose()); 

      return _closeCommand; 
     } 
    } 

    public event EventHandler RequestClose; 

    void OnRequestClose() 
    { 
     EventHandler handler = this.RequestClose; 
     if (handler != null) 
      handler(this, EventArgs.Empty); 
    } 
} 

Also im Grunde bin ich verloren, warum auf dem Monster-Editor im Menü klicken schalte nicht die Ansicht, obwohl currentEditor Wert geändert wird (fest) und warum auf der Maus über auf der TabItem verliere ich die Hintergrundprüfung Farbe die Aussicht (es wird weiß). < - immer noch nicht behoben!

Jede Hilfe wäre willkommen. Danke im Voraus.

Bearbeiten: Ich vermisste die OnPropertyChanged ("CurrentEditor"); im Setter von CurrentEditor.

Antwort

0

Nun im Grunde waren meine beiden Fragen Missverständnisse. Zuerst muss das CurrentEditor-Attribut OnPropertyChanged in EditorViewModel.cs wie folgt aufgerufen werden.Dies ruft die ViewModelBase Klasse OnPropertyChanged Methode:

public ViewModelBase CurrentEditor 
{ 
    get 
    { 
     if (_currentEditor == null) 
     { 
      _currentEditor = new GBARomViewModel(); 
      _currentEditor.PropertyChanged += this.OnSubEditorChanged; 
     } 

     return _currentEditor; 
    } 
    set 
    { 
     _currentEditor = value; 
     OnPropertyChanged("CurrentEditor"); 
    } 
} 

Mein zweites Problem länger dauerte, zu lösen. Ich brauchte meine Ansichten nicht auf TabItem, sondern auf etwas wie DockPanel. Dadurch wird sichergestellt, dass das gesamte Fenster nicht das Verhalten von TabItem IsMouseOver hat, das die Farbe auf den Standardwert zurücksetzt. Setze ein Hintergrundbild wie zB Arbeit folgen und ich kann durch die Ansicht navigieren. Ich konnte das nicht mit einer Kontrollvorlage von TabItem lösen.

GbaRomView.xaml.cs

public partial class GBARomView : DockPanel 
{ 
    public GBARomView() 
    { 
     InitializeComponent(); 
    } 
} 

GbaRomView.xaml

<DockPanel 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" 
     d:DesignHeight="380" d:DesignWidth="646"> 
    <DockPanel.Background> 
     <ImageBrush ImageSource="/Resources/Images/DefaultBackground.png"></ImageBrush> 
    </DockPanel.Background> 
</DockPanel>