WPF上下文菜单

时间:2014-04-25 15:33:44

标签: wpf xaml contextmenu

TextBox有一个默认的上下文菜单。我想添加一个项目。好的,这意味着克隆默认的一个,并添加一个额外的项目。

我想在这里重用一些代码。我有五个文本框。每个都需要其上下文菜单上的附加项。项目需要对单击的文本框执行操作。我知道“复制和粘贴”是WPF中推荐的代码重用方法,但如果可能的话,我宁愿不在XAML中定义五个菜单,在后面的代码中定义五个命令。

在WPF中有没有合理清洁和快速的方法?

public partial class MyGhastlyView
{
    /* blah blah */

    private void MenuCut_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            (sender as MenuItem).GetPlacementTarget<TextBox>().Cut();
        }
        catch (Exception)
        {
        }
    }

    /* blah blah */
}

public static class FurshlugginerExtensions
{
    public static bool TryGetPlacementTarget<TTargetType>(this MenuItem mi, 
        out TTargetType target) where TTargetType : class
    {
        target = null;

        var cm = mi.GetContextMenu();

        if (null != cm)
        {
            target = cm.PlacementTarget as TTargetType;
        }

        return null != target;
    }

    public static TTargetType GetPlacementTarget<TTargetType>(this MenuItem mi) 
        where TTargetType : class
    {
        var cm = mi.GetContextMenu();

        return (cm == null)
                ? null
                : cm.PlacementTarget as TTargetType;
    }

    public static ContextMenu GetContextMenu(this MenuItem mi)
    {
        var logicalParent = LogicalTreeHelper.GetParent(mi);

        if (logicalParent is ContextMenu)
        {
            return logicalParent as ContextMenu;
        }
        else if (logicalParent is MenuItem)
        {
            return (logicalParent as MenuItem).GetContextMenu();
        }

        return null;
    }
}

更新

我正在寻找的东西原来是一个RoutedUICommand,在XAML中有一些不可思议的东西。它知道你点击了什么(由于事件冒泡导致了一些Kafkaesque异常 - 但是可以在ContextMenu上设置CommandParameter)。

2 个答案:

答案 0 :(得分:2)

不幸的是, ContextMenuOpening 事件在这里不起作用。无论出于何种原因, TextBox 不会公开其上下文菜单,除非您使用自己的上下文菜单设置它,否则它始终为null。也许它只是在鼠标右键单击时弹出一个私人菜单。

Charles Petzold使用 RichTextBox here. TextBox RichTextBox )来自 TextBoxBase ,似乎定义了这种行为)

您似乎必须创建自己的,并复制现有项目。

有几篇文章证明了这一点,就像here一样。

希望这有帮助。


编辑:

但是,如果您坚持编辑当前菜单,则有人已经这样做here(使用扩展方法和反射)。

在对上述尝试进行进一步调查后,作者似乎正在创建一个 EditorContextMenu 的实例(私有类派生自系统中的 ContextMenu 。 Windows.Documents )并将其分配给 TextBox ContextMenu 属性,然后将参数菜单项添加到新创建的菜单中。实际上,覆盖当前菜单。虽然你确实得到了原始的实现,但我不确定我是否赞成这个解决方案。


编辑2:

以下代码将仅创建一个自定义菜单实例,将Ctrl-D与相关的ContextMenu项目绑定到文本框。

    public static RoutedCommand ItemActionCommand = new RoutedCommand();

    public MainWindow()
    {
        InitializeComponent();

        CommandBinding commandBinding = new CommandBinding(ItemActionCommand, new ExecutedRoutedEventHandler(ItemActionCommandEventHandler));
        KeyBinding keyBinding = new KeyBinding(ItemActionCommand, new KeyGesture(Key.D, ModifierKeys.Control));

        MenuItem item = new MenuItem();
        item.Click += CustomContextMenuItem_Click;  // not really necessary
        item.Header = "Custom Menu Item";
        item.InputGestureText = "Ctrl+D";
        item.Command = ItemActionCommand;

        ContextMenu menu = new ContextMenu();
        menu.Items.Add(item);

        Grid container = new Grid();
        this.Content = container;

        for (int i = 0; i < 5; i++)
            container.Children.Add(this.CreateTextBox("Value: " + i.ToString(), (i + 1) * 30.0d, menu, commandBinding, keyBinding));
    }

    private void ItemActionCommandEventHandler(object sender, ExecutedRoutedEventArgs e)
    {
        TextBox textBox = e.Source as TextBox;
        Debug.Assert(textBox != null);
        // perform actions against textbox here
    }

    private void CustomContextMenuItem_Click(object sender, RoutedEventArgs e)
    {
        MenuItem item = sender as MenuItem;
        Debug.Assert(item != null);
        TextBox textBox = ((ContextMenu)item.Parent).PlacementTarget as TextBox;
        Debug.Assert(textBox != null);
        // no need to do anything here since the command handler above will fire
        // but for the sake of completeness
    }

    private TextBox CreateTextBox(string text, double topOffset, ContextMenu menu, CommandBinding commandBinding, KeyBinding keyBinding)
    {
        TextBox textbox = new TextBox();
        textbox.HorizontalAlignment = HorizontalAlignment.Center;
        textbox.VerticalAlignment = VerticalAlignment.Top;
        textbox.Margin = new Thickness(0.0d, topOffset, 0.0d, 0.0d);
        textbox.CommandBindings.Add(commandBinding);
        textbox.InputBindings.Add(keyBinding);
        textbox.ContextMenu = menu;
        textbox.Width = 150.0d;
        textbox.Height = 25.0d;
        textbox.Text = text;
        return textbox;
    }

截图:

ctx menu

答案 1 :(得分:1)

可以使用AttachedProperty和ContextMenuOpening事件的处理。看herehere。应该在xaml中占用大约100行代码和一行。

为了完整起见:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WpfApplication1
{
    public class CustomMenuAction
    {
        public static bool GetHasMenuItemAction(DependencyObject obj)
        {
            return (bool)obj.GetValue(HasMenuItemActionProperty);
        }

        public static void SetHasMenuItemAction(DependencyObject obj, bool value)
        {
            obj.SetValue(HasMenuItemActionProperty, value);
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HasMenuItemActionProperty =
            DependencyProperty.RegisterAttached("HasMenuItemAction", typeof(bool), typeof(CustomMenuAction), new PropertyMetadata(default(bool),OnPropertyChanged));

        private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
           if((bool)e.NewValue)
           {
               var textbox = d as TextBox;
               if(textbox != null)
               {
                   textbox.ContextMenu = GetCustomContextMenu();
                   textbox.ContextMenuOpening += textbox_ContextMenuOpening;
               }
           }
        }

        private static ContextMenu GetCustomContextMenu()
        {
            var contextMenu = new ContextMenu();
            var standardCommands = GetStandardCommands();
            foreach (var item in standardCommands)
            {
                contextMenu.Items.Add(item);
            }
            return contextMenu;
        }

        private static IList<MenuItem> GetStandardCommands()
        {
            //From https://stackoverflow.com/a/210981/3411327
            List<MenuItem> standardCommands = new List<MenuItem>();
            MenuItem item = new MenuItem();
            item.Command = ApplicationCommands.Cut;
            standardCommands.Add(item);
            item = new MenuItem();
            item.Command = ApplicationCommands.Copy;
            standardCommands.Add(item);
            item = new MenuItem();
            item.Command = ApplicationCommands.Paste;
            standardCommands.Add(item);
            return standardCommands;
        }


        static void textbox_ContextMenuOpening(object sender, ContextMenuEventArgs e)
        {
            //From MSDN example: http://msdn.microsoft.com/en-us/library/bb613568.aspx
            var textbox = e.Source as TextBox;
            ContextMenu cm = textbox.ContextMenu;
            foreach (MenuItem mi in cm.Items)
            {
                if ((String)mi.Header == "Item4") return;
            }
            MenuItem mi4 = new MenuItem();
            mi4.Header = "Item4";
            mi4.Click += (o, args) =>
                {
                    var menuItem = o as MenuItem;
                    MessageBox.Show(menuItem.Header.ToString(), textbox.Text);
                };
            textbox.ContextMenu.Items.Add(mi4);
        }   
    }
}

<TextBox namespace:CustomMenuAction.HasMenuItemAction="True"></TextBox>