65.9K
CodeProject 正在变化。 阅读更多。
Home

一个神奇的编辑菜单管理器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (77投票s)

2004 年 1 月 30 日

CPOL

9分钟阅读

viewsIcon

373236

downloadIcon

1207

一个神奇的编辑菜单,无需与项目其他部分连接即可工作

摘要

本文中的代码创建了一个功能齐全的编辑菜单,您可以通过调用一个函数将其添加到 WinForms 应用程序中。通过在显示非文本编辑对象的控件上实现一个简单的接口,您可以添加对非文本编辑功能的支持。

引言

因此,您几乎完成了新应用程序的编写,剩下的只是最后的润色。您知道需要一个编辑菜单,但如何将其最好地集成到您的项目中?如果您只有一个编辑界面,这可能不费吹灰之力,但如果您的项目像我的项目一样,您将拥有许多不同的控件,这些控件会随着用户上下文的变化而动态加载,并且它们中的任何一个都可能包含可编辑的子控件。您可能还会在导航栏中有可编辑的任务窗格和控件,这些控件会不断变化。

如果您尝试跟踪所有这些控件的状态,更新菜单的启用和可见属性,并对菜单单击事件做出适当响应,您的代码可能会变得一团糟。本文提出了一种 UI 设计模式,该模式将这种复杂情况变得出奇地(好吧,至少对我来说是出奇地)简单,方法是将菜单操作推迟到菜单显示的那一刻。您最终会得到一个与承载编辑菜单的应用程序一无所知的独立组件。步骤如下:

  • 响应菜单即将显示时的 (Edit menu expanded or clicked) 事件。
  • 使用 Win32 API 确定当前具有焦点的窗口。
  • 使用 Control.FromHandle() .NET Framework 方法获取与焦点窗口关联的 .NET 控件的引用。
  • 确定具有焦点的控件的类型。
  • 根据控件的类型和状态,启用或禁用以及隐藏或显示各种编辑菜单项。
  • 当用户选择一个菜单项时,将命令传递给具有焦点的控件。

当然,一如既往,细节决定成败。在本文中,我们将逐步介绍创建神奇编辑菜单管理器的过程,并解决沿途遇到的各种小问题。完成后,您将拥有一个可以在任何 WinForms 项目中轻松重用的组件。

背景

与我之前的文章一样,我相信读者会有很多批评、评论和改进建议。请随时分享,我将尽最大努力相应地更新文章。

创建菜单

我们的目标是创建一个可以轻松重用于任何 C# 应用程序的独立模块。为此,我们将在我们的模块内创建和管理编辑菜单。调用应用程序需要做的就是传入顶级编辑菜单。编辑菜单管理器会填充它。

首先创建一个名为 MenuManagers 的新 Windows 应用程序项目。删除 Form1.cs 并添加一个名为 EditMenuManager 的新 Class。我们将使用它来定义和管理编辑菜单项。添加一些 WinForms 命名空间的 using 指令。

using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

在类中,声明菜单项。

// Declare the MenuItems to insert under the Edit menu
private MenuItem m_miEdit;
private MenuItem m_miUndo;
private MenuItem m_miRedo;
private MenuItem m_miCut;
private MenuItem m_miCopy;
private MenuItem m_miPaste;
private MenuItem m_miDelete;
private MenuItem m_miDividerRedoCut;
private MenuItem m_miDividerPasteDelete;
private MenuItem m_miSelectAll;
private MenuItem m_miDividerPropertiesSelectAll;
private MenuItem m_miProperties;

创建一个与菜单中菜单项位置对应的枚举。这使得响应菜单命令更容易一些。

[Flags]
private enum MenuIndex
{
  Undo = 0,
  Redo = 1, 
  Divider1 = 2,
  Cut = 3,
  Copy = 4,
  Paste = 5,
  Divider2 = 6,
  Delete = 7,
  SelectAll = 8,
  Divider3 = 9,
  Properties = 10
}

创建一个函数来创建和初始化菜单项,并在构造函数中调用它。

private void CreateMenus()
{
  // Create
  m_miUndo = new MenuItem();
  m_miRedo = new MenuItem();
  m_miCut = new MenuItem();
  m_miCopy = new MenuItem();
  m_miPaste = new MenuItem();
  m_miDelete = new MenuItem();
  m_miDividerRedoCut = new MenuItem();
  m_miDividerPasteDelete = new MenuItem();
  m_miSelectAll = new MenuItem();
  m_miDividerPropertiesSelectAll = new MenuItem();
  m_miProperties = new MenuItem();

  // Initialize
  m_miUndo.Index = (int)MenuIndex.Undo;
  m_miUndo.Text = "&Undo";
  m_miUndo.Shortcut = Shortcut.CtrlZ;
  m_miRedo.Index = (int)MenuIndex.Redo;
  m_miRedo.Text = "&Redo";
  m_miRedo.Shortcut = Shortcut.CtrlY;
  m_miDividerRedoCut.Index = (int)MenuIndex.Divider1;
  m_miDividerRedoCut.Text = "-";
  m_miCut.Index = (int)MenuIndex.Cut;
  m_miCut.Text = "Cu&t";
  m_miCut.Shortcut = Shortcut.CtrlX;
  m_miCopy.Index = (int)MenuIndex.Copy;
  m_miCopy.Text = "&Copy";
  m_miCopy.Shortcut = Shortcut.CtrlC;
  m_miPaste.Index = (int)MenuIndex.Paste;
  m_miPaste.Text = "&Paste";
  m_miPaste.Shortcut = Shortcut.CtrlV;
  m_miDividerPasteDelete.Index = (int)MenuIndex.Divider2;
  m_miDividerPasteDelete.Text = "-";
  m_miDelete.Index = (int)MenuIndex.Delete;
  m_miDelete.Text = "&Delete";
  m_miDelete.Shortcut = Shortcut.Del;
  m_miSelectAll.Index = (int)MenuIndex.SelectAll;
  m_miSelectAll.Text = "Select A&ll";
  m_miSelectAll.Shortcut = Shortcut.CtrlA;
  m_miDividerPropertiesSelectAll.Index = (int)MenuIndex.Divider3;
  m_miDividerPropertiesSelectAll.Text = "-";
  m_miProperties.Index = (int)MenuIndex.Properties;
  m_miProperties.Text = "Pr&operties";
  m_miProperties.Shortcut = Shortcut.F4;
}

现在,添加一个 public 方法让调用应用程序将这些菜单添加到其顶级编辑菜单中。同时,我们还将挂钩编辑菜单上的 Popup 事件,以便在用户单击菜单时进行通知,并挂钩其他菜单项上的 Click 事件。

// The main entry point for this module. Used for initializing
// the Edit menu of the Windows Forms application.
public void ConnectMenus(MenuItem miEdit)
{
  // Subsequent calls are ignored.
  if( m_miEdit == null )
  {
    CreateMenus();
    m_miEdit = miEdit;
    m_miEdit.MenuItems.AddRange(
      new MenuItem[] {
        m_miUndo,
        m_miRedo,
        m_miDividerRedoCut,
        m_miCut,
        m_miCopy,
        m_miPaste,
        m_miDividerPasteDelete,
        m_miDelete,
        m_miSelectAll,
        m_miDividerPropertiesSelectAll,
        m_miProperties});

    m_miEdit.Popup += new System.EventHandler(Edit_Popup);
    m_miUndo.Click += new System.EventHandler(Menu_Click);
    m_miRedo.Click += new System.EventHandler(Menu_Click);
    m_miCut.Click += new System.EventHandler(Menu_Click);
    m_miCopy.Click += new System.EventHandler(Menu_Click);
    m_miPaste.Click += new System.EventHandler(Menu_Click);
    m_miDelete.Click += new System.EventHandler(Menu_Click);
    m_miSelectAll.Click += new System.EventHandler(Menu_Click);
    m_miProperties.Click += new System.EventHandler(Menu_Click);
  }
}

这是您需要从应用程序调用的唯一方法。

Win32 API 实用程序

当用户单击“编辑”菜单时,我们需要做的第一件事是找出当前哪个控件具有焦点。为此,我们需要调用 Win32 API 方法。由于我们将需要一些 Win32 方法,让我们花点时间为它们创建一个实用程序类。在一个单独的文件中创建一个名为 Win32API 的新类。我们将把所有 API 调用和相关的实用方法放在里面。

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

现在向类添加一些声明和导入。

public class Win32API
{
  [StructLayout(LayoutKind.Sequential)]
  public struct GETTEXTLENGTHEX
  {
    public Int32 uiFlags;
    public Int32 uiCodePage;
  }

  public const int WM_USER = 0x400;
  public const int EM_CUT = 0x300;
  public const int EM_COPY = 0x301;
  public const int EM_PASTE = 0x302;
  public const int EM_CLEAR = 0x303;
  public const int EM_UNDO = 0x304;
  public const int EM_CANUNDO = 0xC6;
  public const int EM_CANPASTE = WM_USER + 50;
  public const int EM_GETTEXTLENGTHEX = WM_USER + 95;

  /// Windows API SendMessage functions
  [DllImport("user32.dll", CharSet = CharSet.Auto, 
    SetLastError = true)]
  public static extern int SendMessage(IntPtr hWnd, 
    int msg, int wParam, int lParam);
  [DllImport("user32.dll", EntryPoint="SendMessage", 
    CharSet=CharSet.Auto )]
  public static extern int SendMessage( IntPtr hWnd, int Msg,
    ref GETTEXTLENGTHEX wParam, IntPtr lParam);
  
  // Return the handle of the window that has the focus.
  [DllImport("user32.dll")]
  public static extern IntPtr GetFocus();
  
  /// Windows API GetParent function
  [DllImport("user32", SetLastError = true)]
  public extern static IntPtr GetParent(IntPtr hwnd);

  // The constructor.  Not used since all methods are static.
  public Win32API(){}

添加一个方法来获取与当前焦点控件关联的框架控件。

// Return the Framework control associated with the specified handle.
public static Control GetFrameworkControl(IntPtr hControl)
{
  Control rv = null;
  if( hControl.ToInt32() != 0 )
  {
    rv = Control.FromHandle(hControl);
    // Try the parent, since with a ComboBox, we get a 
    // handle to an inner control.
    if( rv == null )
    rv = Control.FromHandle(GetParent(hControl));
  }
  return rv;
}

最后,我们添加了一些方法来填补 WinForms API 中的空白。这些将用于为 ComboBox 控件提供剪切/复制/粘贴/撤销功能,并解决 RichTextBox 控件中的一个 bug,该 bug 会在 读取(!) TextTextLength 属性时清除整个撤销/重做缓冲区。

// Edit commands for the inner textbox in the ComboBox control.
public static void Undo(IntPtr hEdit)   
{
  SendMessage(hEdit, EM_UNDO, 0, 0);
}
public static void Cut(IntPtr hEdit)    
{
  SendMessage(hEdit, EM_CUT, 0, 0);
}
public static void Copy(IntPtr hEdit)   
{
  SendMessage(hEdit, EM_COPY, 0, 0);
}
public static void Paste(IntPtr hEdit)  
{
  SendMessage(hEdit, EM_PASTE, 0, 0);
}
public static bool CanUndo(IntPtr hEdit)
{
  return SendMessage(hEdit, EM_CANUNDO, 0, 0) != 0;
}

// Determine whether the clipboard contains any format 
// that can be pasted into a rich text box.
public static bool CanPasteAnyFormat(IntPtr hRichText)
{
  return SendMessage(hRichText, EM_CANPASTE, 0,0) != 0;
}
// Determine the length of a control's Text. Required since using the 
// RichTextBox .Length property wipes out the Undo/Redo buffer.
public static int GetTextLength(IntPtr hControl)
{
  GETTEXTLENGTHEX lpGTL = new GETTEXTLENGTHEX();
  lpGTL.uiFlags = 0;
  lpGTL.uiCodePage = 1200; // Unicode
  return SendMessage(hControl, EM_GETTEXTLENGTHEX, 
    ref lpGTL, IntPtr.Zero);
}

响应编辑菜单弹出事件

现在我们拥有了在用户单击编辑菜单项时设置菜单所需的一切。我们使用 GetFocus() API 调用来获取当前焦点控件的句柄,然后调用实用函数 GetFrameworkControl() 来查找该控件的第一个对应于框架对象的父级。通常,这是具有 GetFocus() 返回的句柄的控件,但有时(例如对于 ComboBox)它是父级。

启用或禁用菜单项

现在我们已经获得了焦点控件的引用,我们可以根据其状态启用或禁用编辑菜单项。但在处理 Popup 事件之前,让我们定义一个标志 enum 来表示控件的编辑状态。这样,一个单独的位字段变量就可以轻松地描述菜单的条件。我们使用 [Flags] 属性,因为这些值将被组合在一个位标志中。

// Declare an enumeration to represent the 
// visible/invisible enabled/disabled
// state of the menu.
[Flags]
private enum EditState
{
  None = 0x0,
  UndoVisible = 0x1,
  RedoVisible = 0x2,
  UndoEnabled = 0x4,
  RedoEnabled = 0x8,
  CutEnabled = 0x10,
  CopyEnabled = 0x20,
  PasteEnabled = 0x40,
  SelectAllEnabled = 0x80,
  DeleteEnabled = 0x100,
  RenameEnabled = 0x200,
  PropertiesEnabled = 0x400
}

文本框和 RichTextBox 菜单

现在,编写 Popup 事件处理程序。我们将从 TextBoxBase 开始,以处理 TextBoxRichTextBox 控件,并调用函数 GetTextBoxEditState() 来返回这些类控件的状态。稍后,我们将添加对其他类型控件的支持。在获得确定 TextBoxBase 可编辑状态的位标志后,我们将相应的属性设置在菜单项上。

// When the user clicks on the Edit menu, determine the focused control,
// call a method to get state flags, then setup the edit menu based 
// on the flags.
private void Edit_Popup(object sender, System.EventArgs e)
{
  IntPtr hFocus = Win32API.GetFocus();
  Control ctlFocus = Win32API.GetFrameworkControl(hFocus);
  EditState eEditState = EditState.None;

  if( ctlFocus is TextBoxBase )
    eEditState = GetTextBoxEditState((TextBoxBase)ctlFocus);
  
  // Show or hide and enable or disable menu 
  // controls according to eEditState.
  m_miUndo.Visible = (eEditState & EditState.UndoVisible) != 0;
  m_miRedo.Visible = (eEditState & EditState.RedoVisible) != 0;
  m_miDividerRedoCut.Visible = (m_miUndo.Visible == true 
      || m_miRedo.Visible == true);
  m_miUndo.Enabled = (eEditState & EditState.UndoEnabled) != 0;
  m_miRedo.Enabled = (eEditState & EditState.RedoEnabled) != 0;
  m_miCut.Enabled = (eEditState & EditState.CutEnabled) != 0;
  m_miCopy.Enabled = (eEditState & EditState.CopyEnabled) != 0;
  m_miPaste.Enabled = (eEditState & EditState.PasteEnabled) != 0;
  m_miDelete.Enabled = (eEditState & EditState.DeleteEnabled) != 0;
  m_miSelectAll.Enabled = (eEditState & EditState.SelectAllEnabled) != 0;
  m_miProperties.Enabled = (eEditState & EditState.PropertiesEnabled) != 0;
}

GetTextBoxEditState() 方法执行此操作

// Obtain EditState flags for TextBox and RichTextBox controls.
private EditState GetTextBoxEditState(TextBoxBase textbox)
{
  // Set Booleans defining the textbox state.
  bool bWritable = (textbox.ReadOnly == false && textbox.Enabled == true);
  bool bTextSelected = (textbox.SelectionLength > 0);
  // Cannot use textbox.TextLength, because that 
  // wipes out Undo/Redo buffer in RichTextBox
  bool bHasText = Win32API.GetTextLength(textbox.Handle) > 0;
  bool bIsRichText = (textbox is RichTextBox);

  // Use the Booleans to set the EditState flags.
  EditState eState = EditState.UndoVisible;
  if( bIsRichText )
  {
    eState |= EditState.RedoVisible;
    if( ((RichTextBox)textbox).CanRedo )
      eState |= EditState.RedoEnabled;
  }
  if( textbox.CanUndo )
    eState |= EditState.UndoEnabled;
  if( textbox.CanSelect )
    eState |= EditState.SelectAllEnabled;
  if( bTextSelected )
    eState |= EditState.CopyEnabled;
  if( bWritable )
  {
    if( bTextSelected )
    {
      eState |= EditState.CutEnabled;
      eState |= EditState.DeleteEnabled;
    }
    if( bIsRichText )
    {
      if( Win32API.CanPasteAnyFormat(textbox.Handle) )
        eState |= EditState.PasteEnabled;
    }
    else // TextBox
    {
      if( Clipboard.GetDataObject().GetDataPresent(DataFormats.Text) ) 
      eState |= EditState.PasteEnabled;
    }
  }
  return eState;
}

该方法查看 TextBox(或 RichTextBox)状态的各个方面,例如它是否已启用,选择了多少文本等等。基于此,它确定是否可以对包含的文本执行各种编辑操作。其中大部分都相当直接。不明显的一个技巧是,您不能使用 TextBoxBase.TextLength 方法来确定是否有任何文本可以被选中。问题在于 RichTextBox 中的一个 bug 会导致读取此属性(或 RichTextBox.Text 属性)会清除 RichTextBox 中的整个撤销/重做缓冲区。在生产代码中,您可以考虑从 RichTextBox 派生一个控件,并重写 TextTextLength 方法,使其通过 API 获取数据。

ComboBox (下拉样式) 控件

另一个我们希望编辑菜单能够操作的常见控件是 ComboBox,当其样式为 DropDown 时。我们首先向 Edit_Popup 事件处理程序添加一个新的函数调用。

...
else if( ctlFocus is ComboBox && 
        ((ComboBox)ctlFocus).DropDownStyle == ComboBoxStyle.DropDown )
  eEditState = GetComboBoxEditState(hFocus, (ComboBox)ctlFocus);

...

并编写代码以获取 ComboBox 状态。

// Obtain EditState flags for ComboBox controls.
private EditState GetComboBoxEditState(IntPtr hEdit, ComboBox combobox)
{
  // Set Booleans defining the ComboBox state.
  bool bWritable = combobox.Enabled;
  bool bClipboardText = 
    Clipboard.GetDataObject().GetDataPresent(DataFormats.Text);
  bool bTextSelected = combobox.SelectionLength > 0;
  bool bHasText = combobox.Text.Length > 0;

  // Use the Booleans to set the EditState flags.
  EditState eState = EditState.UndoVisible;
  if( Win32API.CanUndo(hEdit) )
    eState |= EditState.UndoEnabled;

  if( bWritable )
  {
    if( bTextSelected )
    { 
      eState |= EditState.CutEnabled;
      eState |= EditState.DeleteEnabled;
    }
    if( bClipboardText )
      eState |= EditState.PasteEnabled;
  }
  if( bTextSelected )
    eState |= EditState.CopyEnabled;
  if( bHasText )
    eState |= EditState.SelectAllEnabled;
  return eState;
}

TextBox 一样,我们再次查看 ComboBox 的各个方面来确定哪些菜单操作是可能的,并相应地设置标志。由于 ComboBox 上没有 CanUndo 属性,我们将底层 TextBox 控件的句柄传递给 Win32API.CanUndo() 方法来确定是否可以撤销上一个操作。

自定义控件

虽然对标准文本编辑控件的支持是自动的,但这很好,但许多有趣的编辑行为是针对您定义的控件中的非文本对象。例如,您可能正在实现一个支持剪切/复制和粘贴的图形编辑器。只要它们实现了允许我们获取状态信息并向控件发出相应命令的接口,我们的菜单就可以轻松地与这些控件配合使用。我们将定义一个名为 ISupportsEditpublic 接口,并在文件末尾定义它。

// Define the public interface for user defined editable controls.
public interface ISupportsEdit
{
  bool UndoVisible { get;}
  bool CanUndo { get; }
  void Undo();
  bool RedoVisible {get;}
  bool CanRedo { get; }
  void Redo();
  bool CanCut { get;}
  void Cut();
  bool CanCopy { get; }
  void Copy();
  bool CanPaste { get; }
  void Paste();
  bool CanSelectAll { get; }
  void SelectAll();
  bool CanDelete { get; }
  void Delete();
  bool CanShowProperties { get; }
  void ShowProperties();
}

现在,让我们修改 Edit_Popup 事件处理程序以检查支持此接口的控件。在检查 TextBoxBaseComboBox 之后,我们添加以下代码:

else
{ 
  // If this is not a simple control, search up the parent chain for 
  // a custom editable control.
  ISupportsEdit ctlEdit = GetISupportsEditControl(ctlFocus);
  if( ctlEdit != null )
    eEditState = GetISupportsEditState(ctlEdit);
}

GetISupportsEditControl() 方法检查传入的控件是否实现了 ISupportsEdit 接口。如果未实现,它会向上遍历父级链,直到找到一个实现了该接口的控件,或者失败,返回 null。如果我们找到了,我们就调用 GetISupportsEditState() 方法来查询它以获取菜单状态。

// Takes a control and traverses the parent chain until it finds a control
// that supports the ISupportsEdit interface. Returns that control, or null
// if none is found.
private ISupportsEdit GetISupportsEditControl(Control ctlFocus)
{
  while( !(ctlFocus is ISupportsEdit) && ctlFocus != null )
    ctlFocus = ctlFocus.Parent;
  return (ISupportsEdit)ctlFocus;
}

// Set EditState flags for ISupportsEdit controls.
private EditState GetISupportsEditState(ISupportsEdit control)
{
  EditState eState = EditState.None;
  if( control.UndoVisible ) eState |= EditState.UndoVisible;
  if( control.CanUndo ) eState |= EditState.UndoEnabled;
  if( control.RedoVisible ) eState |= EditState.RedoVisible;
  if( control.CanRedo ) eState |= EditState.RedoEnabled;
  if( control.CanCut ) eState |= EditState.CutEnabled;
  if( control.CanCopy ) eState |= EditState.CopyEnabled;
  if( control.CanPaste ) eState |= EditState.PasteEnabled;
  if( control.CanSelectAll ) eState |= EditState.SelectAllEnabled;
  if( control.CanDelete ) eState |= EditState.DeleteEnabled;
  if( control.CanShowProperties ) eState |= EditState.PropertiesEnabled;
  return eState;
}

发出编辑命令

我们几乎完成了。剩下的就是为菜单单击事件编写一个事件处理程序,并调用各种控件的方法来执行请求的编辑操作。我们从编写菜单单击事件的事件处理程序开始。

// Click handler for all edit menus. Determine the focused window
// and framework control, then call a method to take the appropriate 
// action, based on the type of the control.
private void Menu_Click(object sender, System.EventArgs e)
{
  MenuItem miClicked = sender as MenuItem;
  IntPtr hFocus = Win32API.GetFocus();
  Control ctlFocus = Win32API.GetFrameworkControl(hFocus);
  MenuIndex menuIndex = (MenuIndex)miClicked.Index;
  if( ctlFocus is TextBoxBase )
    DoTextBoxCommand((TextBoxBase)ctlFocus, menuIndex);
  else if( ctlFocus is ComboBox && 
    ((ComboBox)ctlFocus).DropDownStyle == ComboBoxStyle.DropDown ) 
    DoComboBoxCommand(hFocus, (ComboBox)ctlFocus, menuIndex);
  else
  {
    ISupportsEdit ctlEdit = GetISupportsEditControl(ctlFocus);
    if (ctlEdit != null )
      DoISupportsEditCommand(ctlEdit, menuIndex);
  }
}

此代码获取焦点控件,确定其类型,并将控件和菜单命令传递给执行命令的方法。TextboxRichTextBox 的方法如下所示:

// Perform the command associated with MenuIndex on the specified TextBoxBase.
private void DoTextBoxCommand(TextBoxBase textbox, MenuIndex menuIndex)
{
  switch(menuIndex)
  {
    case MenuIndex.Undo: textbox.Undo(); break;
    case MenuIndex.Redo:
      if( textbox is RichTextBox )
      {
        RichTextBox rt = (RichTextBox)textbox;
        rt.Redo();
      }
      break;
    case MenuIndex.Cut: textbox.Cut(); break;
    case MenuIndex.Copy: textbox.Copy(); break;
    case MenuIndex.Paste: textbox.Paste(); break;
    case MenuIndex.Delete: textbox.SelectedText = ""; break;
    case MenuIndex.SelectAll: textbox.SelectAll(); break;
    case MenuIndex.Properties: break;
  }
}

代码非常简单,TextBoxRichTextBox 之间的唯一区别是 RichTextBox 支持 Redo(),因此这一点被单独调用。

ComboBoxISupportsEdit 的方法同样直观:

// Perform the command associated with MenuIndex 
// on the specified ComboBox.
private void DoComboBoxCommand(IntPtr hEdit, 
    ComboBox combobox, MenuIndex menuIndex)
{
  switch(menuIndex)
  {
    case MenuIndex.Undo: Win32API.Undo(hEdit); break;
    case MenuIndex.Cut: Win32API.Cut(hEdit); break;
    case MenuIndex.Copy: Win32API.Copy(hEdit); break;
    case MenuIndex.Paste: Win32API.Paste(hEdit); break;
    case MenuIndex.SelectAll: combobox.SelectAll(); break;
    case MenuIndex.Delete: combobox.SelectedText = ""; break;
  }
}

// Perform the command associated with MenuIndex on 
// the specified ISupportsEdit control.
private void DoISupportsEditCommand(
    ISupportsEdit control, MenuIndex menuIndex)
{
  switch(menuIndex)
  {
    case MenuIndex.Undo: control.Undo(); break;
    case MenuIndex.Redo: control.Redo(); break;
    case MenuIndex.Cut: control.Cut(); break;
    case MenuIndex.Copy: control.Copy(); break;
    case MenuIndex.Paste: control.Paste(); break;
    case MenuIndex.SelectAll: control.SelectAll(); break;
    case MenuIndex.Delete: control.Delete(); break;
    case MenuIndex.Properties: control.ShowProperties(); break;
  }
}

测试项目

我在下载中包含了一个演示如何使用此代码的测试项目。它有一个窗体,上面有 TextBoxRichTextBoxComboBox 和自定义的 TreeView 基于控件(TestEditableUserControl)。TestEditableUserControl 支持 MenuManagers.ISupportsEdit 接口,该接口允许您在树之间剪切、复制和粘贴节点。它还实现了一个简单的 Property 对话框(一个 MessageBox),该对话框显示节点 Text 作为 Properties 菜单的示例。

结论

好了,信不信由你,我们完成了。如果您使用的是 WinForms 菜单,您所要做的就是将此项目包含在您的解决方案中,定义一个顶级编辑菜单,然后将其传递给 ConnectMenus() 方法。如果您有任何支持编辑功能的自定义控件,您应该在它们上实现 ISupportsEdit 接口。在我的例子中,这很容易,因为控件已经有了包含所有编辑命令的上下文菜单。

如果您使用的是其他菜单系统,您需要相应地修改代码,但本文的所有基本思想仍然适用。在这方面,有一个警告。如果您使用的菜单系统会抢占焦点(不应该这样),那么本文中的代码将不起作用,因为焦点控件将始终显示为菜单本身。

总之,祝您好运,玩得开心,如果您确实使用了此代码,请告诉我结果。明确地说,您可以出于任何目的使用此代码,无论是个人目的还是商业目的。

历史

  • 2004年1月30日 - 第一个版本
  • 2004年2月2日 - 第二个版本。对文章文本进行了少量编辑。
  • 2004年2月9日 - 第三个版本。修复了 Dreamwolf 指出的 bug(谢谢)。对文本进行了更多编辑。
© . All rights reserved.