Thursday, August 12, 2010

Cut, Copy, Paste events on TextBox & ComboBox in C# .NET

Well, I have been wondering why .NET hasn't got the Cut, Copy, Paste events on editor controls like TextBox, RichTextEditor, ListView, ComboBox and TreeView ???
Anyways, here I have got these events for you...
First we will extend / inherit the standard TextBox to TextBoxEx as in TextBoxEx.cs below
using System;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.Text;
using System.Windows.Forms;

namespace KurapatySolutions.Controls
{
 public partial class TextBoxEx : TextBox
 {
  #region - Constants -
  private const int WM_CUT = 0x0300;
  private const int WM_COPY = 0x0301;
  private const int WM_PASTE = 0x0302;
  #endregion

  #region - Constructor -
  public TextBoxEx()
  {
   InitializeComponent();
  }
  #endregion

  #region - Private Methods -
  private void DoOnCutText()
  {
   if (OnCutText != null)
    OnCutText(this, new ClipboardEventArgs(SelectedText));
  }
  private void DoOnCopyText()
  {
   if (OnCopyText != null)
    OnCopyText(this, new ClipboardEventArgs(SelectedText));
  }
  private void DoOnPasteText()
  {
  if (OnPasteText != null)
  OnPasteText(this, new ClipboardEventArgs(Clipboard.GetText()));
  }
  #endregion

  #region - Event Handlers -
  public event ClipboardEventHandler OnCutText;
  public event ClipboardEventHandler OnCopyText;
  public event ClipboardEventHandler OnPasteText;
  #endregion

  #region - WndProc - Handler -
  protected override void WndProc(ref Message m)
  {
   base.WndProc(ref m);
   if (m.Msg == WM_CUT)
   {
    //Cut Event
    DoOnCutText();
   }
   else if (m.Msg == WM_COPY)
   {
    //Copy Event
    DoOnCopyText();
   }
   else if (m.Msg == WM_PASTE)
   {
    //Paste Event
    DoOnPasteText();
   }   
  }
  #endregion
 }
 public class ClipboardEventArgs : EventArgs
 {
  public string ClipboardText { get; set; }

  public ClipboardEventArgs(string clipboardText)
  {
   ClipboardText = clipboardText;
  }
 }
public delegate void ClipboardEventHandler(object sender, ClipboardEventArgs e);
}
Now that the new control is ready, build your project and use the new events.

In second example, we will inherit standard ComboBox to ComboBoxEx control, this would require some kind of hacking with NativeWindow... see the code below and you will understand as its self explanatory.
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.ComponentModel;

namespace KurapatySolutions.Controls
{
 /// 
 /// ComboBox with extended functionality to support Cut, Copy & Paste events
 /// 
 public partial class ComboBoxEx : ComboBox
 {
  #region - Constants -
  private const int WM_CUT = 0x0300;
  private const int WM_COPY = 0x0301;
  private const int WM_PASTE = 0x0302;
  private const UInt32 CB_GETCOMBOBOXINFO = 0x0164;
  #endregion

  #region - DLL Import -
  [DllImport("USER32", EntryPoint = "SendMessage")]
  extern static void SendCbInfoMsg(IntPtr wnd, UInt32 msg, IntPtr wParam, [In, Out] ref ComboBoxInfo lParam);
  #endregion

  #region - Constructor -
  public ComboBoxEx()
  {
   InitializeComponent();
  }
  public ComboBoxEx(IContainer container)
  {
   container.Add(this);
   InitializeComponent();
  }
  #endregion

  #region - Private Members -
  private NativeWindowEx _nativeWindowEx;
  #endregion

  #region - Private Methods -
  private void DoOnCutText(object sender, EventArgs e)
  {
   if (OnCutText != null)
    OnCutText(sender, new ClipboardEventArgs(SelectedText));
  }
  private void DoOnCopyText(object sender, EventArgs e)
  {
   if (OnCopyText != null)
    OnCopyText(sender, new ClipboardEventArgs(SelectedText));
  }
  private void DoOnPasteText(object sender, ClipboardEventArgs e)
  {
   if (OnPasteText != null)
    OnPasteText(this, e);
  }
  #endregion

  #region - Event Handlers -
  public event ClipboardEventHandler OnCutText;
  public event ClipboardEventHandler OnCopyText;
  public event ClipboardEventHandler OnPasteText;
  #endregion
        
  #region - Private Structures & Class -
  struct Rect
  {
   public int Left;
   public int Top;
   public int Right;
   public int Bottom;
  }

  struct ComboBoxInfo
  {
   public Int32 cbSize;
   public Rect rcItem;
   public Rect rcButton;
   public Int32 buttonState;
   public IntPtr hwndCombo;
   public IntPtr hwndEdit;
   public IntPtr hwndList;
  }

  /// 
  /// This window is inherited from NativeWindow and overrides WndProc.
  /// 
  class NativeWindowEx : NativeWindow 
  {
   #region - Event Handlers -
   public event EventHandler OnCutText;
   public event EventHandler OnCopyText;
   public event ClipboardEventHandler OnPasteText;
   #endregion

   #region - Private Methods -
   private void DoOnCutText()
   {
    if (OnCutText != null)
     OnCutText(this, new EventArgs());
   }

   private void DoOnCopyText()
   {
    if (OnCopyText != null)
     OnCopyText(this, new EventArgs());
   }

   private void DoOnPasteText()
   {
    if (OnPasteText != null)
     OnPasteText(this, new ClipboardEventArgs(Clipboard.GetText()));
   }
   #endregion

   #region - WndProc - Handler -
   protected override void WndProc(ref Message m)
   {
    if (m.Msg == WM_CUT)
    {
     DoOnCutText();
    }
    if (m.Msg == WM_COPY)
    {
     DoOnCopyText();
    }
    if (m.Msg == WM_PASTE)
    {
     DoOnPasteText();
    }
    base.WndProc(ref m);
   }
   #endregion
  }
  #endregion

  #region - Overrides -
  protected override void OnHandleCreated(EventArgs e)
  {
   var cbInfo = new ComboBoxInfo();
   cbInfo.cbSize = Marshal.SizeOf(cbInfo);
   SendCbInfoMsg(Handle, CB_GETCOMBOBOXINFO, IntPtr.Zero, ref cbInfo);
   _nativeWindowEx = new NativeWindowEx();
   _nativeWindowEx.AssignHandle(cbInfo.hwndEdit);
   _nativeWindowEx.OnCutText += DoOnCutText;
   _nativeWindowEx.OnCopyText += DoOnCopyText;
   _nativeWindowEx.OnPasteText += DoOnPasteText;
   base.OnHandleCreated(e);

  }
  #endregion
 }
 public class ClipboardEventArgs : EventArgs
 {
  public string ClipboardText { get; set; }

  public ClipboardEventArgs(string clipboardText)
  {
   ClipboardText = clipboardText;
  }
 }
 public delegate void ClipboardEventHandler(object sender, ClipboardEventArgs e);
}

You can move the shared EventArgs & Delegate to EventDelegate.cs as mentioned below:
using System;

namespace KurapatySolutions.Controls
{
 public class ClipboardEventArgs : EventArgs
 {
  public string ClipboardText { get; set; }

  public ClipboardEventArgs(string clipboardText)
  {
   ClipboardText = clipboardText;
  }
 }
 public delegate void ClipboardEventHandler(object sender, ClipboardEventArgs e);
}


Hope it helps!

Development Continues...

4 comments:

Ryan said...

The code for Combobox is exactly what I needed to understand how to interact and intercept messages to the native window of the Combobox. Thank you for the excellent sample code.

KIRAN said...

I've just updated WndProc method, this should work fine.

Kramii said...

Thanks for the post. I find it surprising that Microsoft still hasn't implemented this functionality out of the box. Great to have a solution.

Unknown said...

I use a TextBox, but InitializeComponent() doesn't exist in this context. What i need to do?