Laden...

WPF - Ribbon mit MVVM-Pattern nutzen

Erstellt von trashkid2000 vor 4 Jahren Letzter Beitrag vor 4 Jahren 4.479 Views
T
trashkid2000 Themenstarter:in
156 Beiträge seit 2010
vor 4 Jahren
WPF - Ribbon mit MVVM-Pattern nutzen

Hi,

ich dachte mir mal, ich poste mal ein Tutorial.
Und das für die Verwendung des WPF - Ribbon unter der Verwendung des MVVM-Pattern.

Vorwort: Es gibt von M$ ein paar Samples zu diesem Thema, eine Suche nach 'MicrosoftRibbonForWPFSourceAndSamples' findet diese.

Soweit so gut, aber dies ist nicht das, nach dem ich suchte...
In den Samples werden (umfangreiche) Objekte im ViewModel zusammengebaut. Aber MVVM-konform ist das nicht. Es ist so einfach nicht möglich, das ganze Konstrukt einfach umzubiegen...

Beruflich habe ich eine Umsetzung benötigt, in der wie gewöhnt, einfach ein ViewModel und das entsprechende DataTemplate definiert werden kann.

Also, nach ein paar fails hier eine Lösung des Ganzen.
Überschrieben wurde dabei die Definition, wie Das jeweilige ItemsControl seine Items erzeugt.

Bei dem Ribbon ist es das ControlTemplate, was bei der Erzeugung der Tabs überschrieben wurde.
Bei dem ApplicationMenu einfach der Container, der zur Anzeige eines Items erzeugt wird.


public class CustomRibbon : Ribbon
{
   /// <summary>
   /// Bereitet den RibbonTab so vor, dass der Content auf das Item gesetzt wird und ein definiertes DataTemplate zur Anzeige genutzt wird
    /// </summary>
    /// <param name="element">Der Tab, der zur Anzeige vorbereitet werden soll</param>
    /// <param name="item">Objekt, das als Content gesetzt wird</param>
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        var tab = (RibbonTab)element;

        var controlTemplate = new ControlTemplate(typeof(RibbonTab));
        var contentControlFactory = new FrameworkElementFactory(typeof(ContentControl));
        contentControlFactory.SetBinding(ContentControl.ContentProperty, new Binding() { Source = item });

        contentControlFactory.SetBinding(VisibilityProperty, new Binding() 
        { 
            UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged, Source = tab, 
            Path = new PropertyPath(nameof(tab.IsSelected)), Converter = new BooleanToValueConverter() { FalseValue = Visibility.Collapsed, TrueValue = Visibility.Visible } 
        });
        
        controlTemplate.VisualTree = contentControlFactory;
        contentControlFactory.SetValue(IsTabStopProperty, false);
        tab.Template = controlTemplate;

        tab.Header = item;
   }
}


public class CustomRibbonApplicationMenu : RibbonApplicationMenu
{
   /// <summary>
   /// Erstellt einen neuen Container für einen Eintrag im Menü
   /// </summary>
    /// <returns>Den erstellten Container</returns>
   protected override DependencyObject GetContainerForItemOverride()
   {
      return new ContentControl() { IsTabStop = false };
   }

   /// <summary>
   /// Bereitet den Menüeintrag für die Anzeige vor
   /// </summary>
   /// <param name="element">Das <see cref="ContentControl"/>, das als Container genutzt wird</param>
   /// <param name="item">Der Content</param>
   protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
   {
       var contentControl = ((ContentControl)element);
       contentControl.Content = item;
    }

   /// <summary>
   /// Behebt das Verhalten, dass das Menü geschlossen wird, wenn ein Menuitem, das disabled ist, angeklickt wird.
   /// </summary>
   /// <param name="e">Die <see cref="MouseButtonEventArgs"/></param>
   protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
   {
      if (e.OriginalSource is ContentPresenter applicationMenuItemContentPresenter)
      {
         if (VisualTreeHelper.GetChild(applicationMenuItemContentPresenter, 0) is RibbonApplicationMenuItem applicationMenuItem)
         {
            if (!applicationMenuItem.IsEnabled)
            {
                e.Handled = true;
             }
          }
       }

       base.OnPreviewMouseLeftButtonUp(e);
   }

   /// <summary>
   /// Behebt das Verhalten, dass in dem Menü manchmal direkt das ContentControl des ContainerForItemOverride angesprungen wurden und das AppMenuItem dann nicht fokussiert wurde. Gerade bei Up und Down war das aufgefallen.
    /// </summary>
    /// <param name="e">Die <see cref="KeyboardFocusChangedEventArgs"/></param>
    protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
    {
        if (e.OriginalSource is ContentControl contentControl)
        {
            var applicationMenuItem = (UIElement)contentControl.FindVisualChildren<DependencyObject>().FirstOrDefault(x => x.GetType().GetProperty(nameof(QuickAccessToolBarId)) != null);
            if (applicationMenuItem != null)
            {
                applicationMenuItem.Focus();
             }
         }

         base.OnGotKeyboardFocus(e);
     }
}

Eingebunden wird dies dann wie folgt:


<Controls:CustomRibbon ItemsSource="{Binding RibbonTabItems}">
   <Controls:CustomRibbon.ApplicationMenu>
      <Controls:CustomRibbonApplicationMenu ItemsSource="{Binding ApplicationMenuItems}" FooterPaneContent="{Binding CloseApplicationFooterPaneContent}" AuxiliaryPaneContent="{Binding AuxiliaryPaneContent}"/>
   </Controls:CustomRibbon.ApplicationMenu>
</Controls:CustomRibbon>

Ein Sample-Projekt kann unter
https://drive.google.com/file/d/1d2OckuDTLwnZ86p50Keyh_67zz2eWwFC/view?usp=sharing
heruntergeladen werden.

Viel Spaß damit,
Marko