Definieren Sie dies in einem XAML-Ressourcenwörterbuch, das mit App.xaml zusammengeführt wurde. Es ist also in Ihrer gesamten Anwendung verfügbar. Es ist einfach, ein Kontextmenü als Ressource zu definieren, aber wenn es sich um eine Ressource handelt, müssen Sie zusätzliche Arbeit leisten, um sie über ihren Kontext zu informieren. Mit den meisten WPF-Steuerelementen würden Sie eine RelativeSource AncestorType
Bindung durchführen, aber das Kontextmenü ist nicht im VisualTree, so dass es nicht funktioniert.
It says here that ContextMenu.PlacementTarget will be set to the context menu's owner wenn das Menü geöffnet wird, aber das Überwachungsfenster auf meinem Desktop sagt, dass sie nur darüber scherzen. Wenn Sie ein Kontextmenü wie folgt definieren, seine DataContext
ist diese Instanz von local:Bar
:
<local:Bar >
<local:Bar.ContextMenu>
<ContextMenu>
<MenuItem
Header="{Binding ArbitraryProperty}"
/>
</ContextMenu>
</local:Bar.ContextMenu>
</local:Bar>
... aber das funktioniert nicht, wenn das Kontextmenü eine Ressource ist. In diesem Fall müssen Sie DataContext
selbst einstellen. Das stellt sich als nicht zu schmerzhaft heraus. Wir machen es im ContextMenuOpening
Ereignis auf dem benutzerdefinierten Steuerelement. Wir definieren zwei benutzerdefinierte Steuerelemente. In einem werden wir ContextMenuOpening
über eine EventSetter
in der Style
einrichten, und in der anderen werden wir ContextMenuOpening
mit einem Lambda im Konstruktor behandeln. Ich mag die EventSetter
Version, weil, während es ein wenig mehr Arbeit ist, Sie diesen Handler auf absolut alles werfen können, können Sie eine Style
auf setzen. Sie könnten eine angehängte Eigenschaft/ein angehängtes Verhalten schreiben, um einen solchen Handler zu setzen, der noch einfacher zu verwenden wäre.
Themen/generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SharedContextMenuTest"
x:Class="SharedContextMenuTest.Themes.Generic"
>
<ContextMenu x:Key="SharedContextMenu">
<MenuItem Header="{Binding ArbitraryProperty}" />
</ContextMenu>
<Style TargetType="{x:Type local:Foo}">
<!-- IMPORTANT -->
<Setter Property="ContextMenu" Value="{StaticResource SharedContextMenu}" />
<EventSetter Event="ContextMenuOpening" Handler="FooBar_ContextMenuOpening" />
<!-- !IMPORTANT -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Foo}">
<Border
Background="GhostWhite"
BorderBrush="DodgerBlue"
BorderThickness="1"
Margin="1"
>
<Label
Content="{TemplateBinding ArbitraryProperty}"
Padding="20"
/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:Bar}">
<!-- IMPORTANT -->
<!-- Bar sets up the ContextMenuOpening handler in its constructor -->
<Setter Property="ContextMenu" Value="{StaticResource SharedContextMenu}" />
<!-- !IMPORTANT -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Bar}">
<Border
Background="GhostWhite"
BorderBrush="ForestGreen"
BorderThickness="1"
Margin="1"
>
<Label
Content="{TemplateBinding ArbitraryProperty}"
Padding="20"
/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Themen/Generic.xaml.cs
namespace SharedContextMenuTest.Themes
{
public partial class Generic
{
private void FooBar_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
(e.Source as FrameworkElement).ContextMenu.DataContext = e.Source;
}
}
}
MyCustomControls.cs
namespace SharedContextMenuTest
{
public class Foo : Control
{
public static readonly DependencyProperty ArbitraryPropertyProperty =
DependencyProperty.Register("ArbitraryProperty", typeof(String), typeof(Foo),
new PropertyMetadata(nameof(Foo)));
}
public class Bar : Control
{
public Bar()
{
// Foo has an EventSetter in its Style; here we illustrate a quicker way.
ContextMenuOpening += (s, e) => ContextMenu.DataContext = this;
}
public static readonly DependencyProperty ArbitraryPropertyProperty =
DependencyProperty.Register("ArbitraryProperty", typeof(String), typeof(Bar),
new PropertyMetadata(nameof(Bar)));
}
}
MainWindow.xaml
<StackPanel Orientation="Vertical">
<local:Foo />
<local:Bar />
</StackPanel>
Hallo Ed! Ich bin mir nicht sicher, ob ich dich richtig verstanden habe. Bedeutet das, dass ich es für jedes CustomControl definieren muss? –
Oh, gotcha, wenn Sie das gleiche Menü für eine Reihe von verschiedenen benutzerdefinierten Steuerelementtypen möchten, die auch machbar sind - definieren Sie es als eigene Ressource. Zurück in ein bisschen damit bin ich auf meinem Telefon atm. –
@PatrickPirzer OK, ich bin jetzt wach. Es gibt einen Haken: Es ist einfach, ein gemeinsames Menü zu definieren. Es ist jedoch ein absoluter Alptraum, irgendetwas auf den Menüpunkten an irgendetwas auf der Steuerung zu binden, zu der das Menü gehört. Es ist einer der wenigen Teile von WPF, die sie einfach schrecklich vermasselt haben. WPF funktioniert wirklich sehr, wirklich zu verhindern, dass Kontextmenüs herausfinden, was ihr Kontext ist. –