2014-04-25 4 views
1

TextBox имеет контекстное меню по умолчанию. Я хотел бы добавить элемент к нему. ОК, это означает клонирование по умолчанию и добавление к нему дополнительного элемента. Может быть, даже с радикальными, космическими особенностями далекого будущего, такими как быстрые клавиши, но давайте не будем жадными. Дебилы, которые разработали структуру, увидели все, что может потребоваться программисту, и предприняли шаги, чтобы предотвратить это.Контекстное меню WPF

Теперь я хотел бы использовать код здесь. У меня есть пять текстовых полей. Каждый из них нуждается в дополнительном элементе в своем контекстном меню. Элемент должен действовать в текстовом поле, которое было нажато. Я знаю, что «копировать и вставлять» является рекомендуемым методом повторного использования кода в WPF, но, если возможно, я бы предпочел не определять пять меню в XAML и пять команд в коде.

Есть ли способ сделать это в WPF, не потратив полтора дня на какую-то смехотворную чудовищность Rube Goldberg WTF, которая требует четырех часов для исследования, восемь часов для реализации и включает в себя 300 строк кода и 400 строк XAML ?

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; 
    } 
} 

UPDATE

То, что я ищу, оказывается, чтобы быть RoutedUICommand, с некоторыми возиться вокруг в XAML. Он знает, на что вы нажали (с некоторыми исключениями Kafkaesque из-за пузыря событий, но может просто установить CommandParameter в ContextMenu).

+4

Я чувствую, что ваши отношения с WPF является ... сложным. – McGarnagle

+0

@McGarnagle lol! «Сложный» - это одно слово для него ... –

ответ

1

Возможно с AttachedProperty и обработкой события ContextMenuOpening. Посмотрите here и here. Необходимо взять около 100 строк кода и одну строку в xaml.

Ради completenes:

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> 
+0

Событие ContextMenuOpening здесь не будет работать. -1 для тестирования. – Jeff

+0

С небольшим чтением по предоставленным ссылкам должно быть ясно, что вы должны предоставить свое собственное контекстное меню. – user3411327

+0

Справедливо, но вопрос задал вопрос, можно ли добавлять элементы в контекстное меню по умолчанию (оригинал). – Jeff

1

К сожалению, ContextMenuOpening событие не будет работать здесь. По какой-то причине TextBox не раскрывает контекстное меню и всегда имеет значение null, если вы не установите его самостоятельно. Возможно, он просто выталкивает частное меню при щелчке правой кнопкой мыши.

Чарльз Петцольд говорит о том, что с RichTextBoxhere. (Оба TextBox и RichTextBox проистекают из TextBoxBase, который появляется, чтобы определить, что поведение)

Кажется, вам придется создать свой собственный, и дублировать существующие элементы.

Несколько статей демонстрируют именно это, как один here.

Надеюсь, это поможет.


EDIT:

Однако, если вы настаиваете на редактирование текущего меню, оно появляется кто-то сделал так here (с использованием метода расширения и отражения).

После дальнейшего изучения вышеуказанной попытки, кажется, что автор создает экземпляр EditorContextMenu (частного класса, производных от ContextMenu в System.Windows.Documents) и присваивает его TextBox ContextMenu, а затем добавьте пункты меню параметров в только что созданное меню. Фактически, переопределение текущего меню. Хотя вы действительно получаете первоначальную реализацию, я не уверен, что буду одобрять это решение.


EDIT 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

+0

Я вообще не настаиваю на редактировании текущего меню; Я просто хочу получить эту десятиминутную микро-функцию, сделанную за один день. –

+0

Отлично, потому что EditorContextMenu является частным классом. Имеет ли эта 2-я ссылка достаточное направление? – Jeff

+0

Второй жесткий код целевого TextBox в обработчиках событий. Если контекстменю в WPF разделяется между элементами управления, может быть очень болезненно узнать, что именно пользователь нажал. По крайней мере, TextBox будет иметь фокус (какой-то), когда отображается меню. –

Смежные вопросы