WPF-Tricks: Kontext-Menü per Behavior

Behaviors bieten in WPF die Möglichkeit ein Stück Funktionalität in einer wiederverwendbare Komponente zu verpacken. Behaviors erlauben Anwendungsdesignern – mit wenig Aufwand – Änderungen am Programmverhalten zu bewirken ohne in den Programmcode einzugreifen. Die Entwickler stellen dafür beispielsweise Behaviors für Drag & Drop oder Touch-Interaktion bereit, die dann im “Blend” einfach auf das entsprechende Element angewandt werden. Dieses Tutorial zeigt, wie sich mit Hilfe eines Behaviors ein Kontext-Menü erzeugen lässt.

Das Ziel

Die Inhalte eine Liste sollen vom Nutzer kopiert werden können. Da wir die einzelnen Listenelemente nicht in editierbare Text-Boxen umwandeln möchten, soll ein Kontextmenü das Kopieren möglich machen:

Menüpunkt mit Text "Inhalt kopieren" und Mauszeiger darauf
Abbildung 1: Kontext-Menü zum Kopieren des Inhalts

So geht’s

Wir erstellen ein neues Behavior indem wir von Behavior<T> ableiten. Das Blend-Behavior findet sich in System.Windows.Interactivity. Als Typ geben wir FrameworkElement an – damit funktioniert unser Behavior auf praktisch allen Controls:

    public class InhaltKopierenBahavior : Behavior<FrameworkElement>

Als erstes überschreiben wir OnAttached() und OnDetaching(). Hier können wir reagieren, wenn ein Behavior hinzugefügt oder entfernt wird.

    protected override void OnAttached()
    {
     base.OnAttached();
    }
    protected override void OnDetaching()
    {
     base.OnDetaching();
    }

Das Behavior bietet uns Zugriff auf das Element, an das es ‘attached’ wurde. Dafür gibt es das Property AssociatedObject. Das AssociatedObject ist immer vom Typ, der bei der Klassendefintion angegeben wurde. (hier FrameworkElement). An das AssociatedObject ‘hängen’ wir jetzt unser Kontext-Menü mit den gewünschten Optionen. In unserem Beispiel reicht uns der Befehl “Inhalt kopieren”. Dieser soll den angeklickten Text in die Zwischenablage kopieren. Im Beispiel wird der Menüeintrag auch gleich noch mit einem Icon versehen:

    protected override void OnAttached()
    {
     base.OnAttached();
                
     var uri = new Uri("copy-icon-256.png");
     var logo = new BitmapImage();
     logo.BeginInit();
     logo.DecodePixelWidth = 16;
     logo.UriSource = uri;
     logo.EndInit();
     var menuEintrag = new MenuItem
     {
      Header = "Inhalt kopieren",
      Icon = new Image { Source = logo }
     };
     menuEintrag.Click += MenuEintragOnClick;
     AssociatedObject.ContextMenu = new ContextMenu();
     AssociatedObject.ContextMenu.Items.Add(menuEintrag);
     AssociatedObject.PreviewMouseRightButtonDown += MouseDown;
    }

Das Element, zu dem wir unser Kontext-Menü hinzufügen ist nicht zwingend das Element, dessen Inhalt wir kopieren wollen. Wir wollen ja den Inhalt eines ListViewItems und nicht den gesamten ListView kopieren. Das Behavior auf jedes Listenelement anzuwenden, wäre aber unverhältnismäßig mühevoll. Dem AssociatedObject fügen wir deshalb noch einen Event-Handler hinzu. Beim Rechtsklick soll nicht nur das Menü geöffnet, sondern zugleich auch das “angeklickte” Element ermittelt werden. Das “getroffene” Element speichern wir uns in dem Field ‘_element’.

    private void MouseDown(object sender, MouseButtonEventArgs args)
    {
     _element = args.MouseDevice.DirectlyOver;
     args.Handled = true;
    }

Nun setzen wir die eigentliche Kopier-Funktionalität um, dafür bekommt der Menü-Eintrag einen entsprechenden Event-Handler. Wir gehen davon aus, dass das getroffene Element ein Text-Block ist. Wenn nicht, können wir ohnehin nichts sinnvoll in die Zwischenablage kopieren.

    private void MenuEintragOnClick(object sender, RoutedEventArgs args)
    {
     var textBlock = _getroffenesElement as TextBlock;
     if (textBlock != null)
     {
      Clipboard.SetDataObject(textBlock.Text);
     }
    }

Zu guter Letzt wenden wir das Behavior in unserer Anwendung an. Wichtig ist, dass auch der “Blend” Namespace eingebunden ist: xmlns:i=”http://schemas.microsoft.com/expression/2010/interactivity”

    <ListView>
     <i:Interaction.Behaviors>
      <behaviors:InhaltKopierenBahavior/>
     </i:Interaction.Behaviors>
    </ListView>

Dieser Beitrag wurde verfasst von: