简体   繁体   中英

Can i define a standard ContextMenu for WPF CustomControls?

Because i'm making a bunch of WPF CustomControls and want use a standard ContextMenu for These controls, i wish to know, if i can define that ContextMenu as a resource.

And how has the style of such a ContextMenu to be defined? If it is possible, i could overwrite the control's Contextmenu with something like that:

ContextMenu="{StaticResource standardcontextmenu}"

Thanks in advance!

Define this in a XAML resource dictionary that's merged into App.xaml, so it's available throughout your application. It's easy to define a context menu as a resource, but if it's a resource, you need to do extra work to let it know what its context is. With most WPF controls you'd do a RelativeSource AncestorType binding, but the contextmenu is not in the VisualTree so that doesn't work.

It says here that ContextMenu.PlacementTarget will be set to the context menu's owner when the menu opens, but the watch window on my desktop says they're just kidding about that. If you define a context menu like this, its DataContext is this instance of local:Bar :

<local:Bar >
    <local:Bar.ContextMenu>
        <ContextMenu>
            <MenuItem 
                Header="{Binding ArbitraryProperty}" 
                />
        </ContextMenu>
    </local:Bar.ContextMenu>
</local:Bar>

...but that doesn't work when the context menu is a resource. In that case, you need to set the DataContext yourself. That turns out not to be too awfully painful. We'll do it in the ContextMenuOpening event on the custom control. We'll define two custom controls. In one we'll set up ContextMenuOpening via an EventSetter in the Style , and in the other we'll handle ContextMenuOpening with a lambda in the constructor. I like the EventSetter version because while it's a little more work, you can toss that handler on absolutely anything you can put a Style on. You could write an attached property/behavior to set a handler like that as well, which would be even easier to use.

Themes/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>

Themes/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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM