Hook on Default "Paste" Event of Winforms Textbox Control

hook on default Paste event of WinForms TextBox control

While I would normally not suggest dropping to low level Windows API, and this may not be the only way of doing this, it does do the trick:

using System;
using System.Windows.Forms;

public class ClipboardEventArgs : EventArgs
{
public string ClipboardText { get; set; }
public ClipboardEventArgs(string clipboardText)
{
ClipboardText = clipboardText;
}
}

class MyTextBox : TextBox
{
public event EventHandler<ClipboardEventArgs> Pasted;

private const int WM_PASTE = 0x0302;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_PASTE)
{
var evt = Pasted;
if (evt != null)
{
evt(this, new ClipboardEventArgs(Clipboard.GetText()));
// don't let the base control handle the event again
return;
}
}

base.WndProc(ref m);
}
}

static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

var tb = new MyTextBox();
tb.Pasted += (sender, args) => MessageBox.Show("Pasted: " + args.ClipboardText);

var form = new Form();
form.Controls.Add(tb);

Application.Run(form);
}
}

Ultimately the WinForms toolkit is not very good. It is a thin-ish wrapper around Win32 and the Common Controls. It exposes the 80% of the API that is most useful. The other 20% is often missing or not exposed in a way that is obvious. I would suggest moving away from WinForms and to WPF if possible as WPF seems to be a better architected framework for .NET GUIs.

Override Paste Into TextBox

That's possible, you can intercept the low-level Windows message that the native TextBox control gets that tells it to paste from the clipboard. The WM_PASTE message. Generated both when you press Ctrl+V with the keyboard or use the context menu's Paste command. You catch it by overriding the control's WndProc() method, performing the paste as desired and not pass it on to the base class.

Add a new class to your project and copy/paste the code shown below. Compile. Drop the new control from the top of the toolbox onto your form, replacing the existing one.

using System;
using System.Windows.Forms;

class MyTextBox : TextBox {
protected override void WndProc(ref Message m) {
// Trap WM_PASTE:
if (m.Msg == 0x302 && Clipboard.ContainsText()) {
this.SelectedText = Clipboard.GetText().Replace('\n', ' ');
return;
}
base.WndProc(ref m);
}
}

Detecting if paste event occurred inside a rich text box

It's a little bit tricky to detect a paste operation in the RichTextBox.

First solution may be to detect the WM_PASTE message overriding the WndProc but unfortunately the control doesn't send that message to itself when it performs a paste operation.

Naïve detection

To detect the keyboard events may work (you have to override the OnKeyDown function) then check if the key combinations (CTRL+V and SHIFT+INS). Something like this:

protected override OnKeyDown(KeyEventArgs e)
{
bool ctrlV = e.Modifiers == Keys.Control && e.KeyCode == Keys.V;
bool shiftIns = e.Modifiers == Keys.Shift && e.KeyCode == Keys.Insert;

if (ctrlV || shiftIns)
DoSomething();
}

It works well but you can't catch the paste operation made using the mouse (right click to open the context menu) and the paste operations made via drag & drop. If you do not need them you can use this solution (at least it's simply and straightforward).

Better detection

Assumption: when user types inside the RichTextBox he inserts one character per time. How can you use this? Well, when you detect a bigger change you detected a paste operation because user can't type more than once character per time (ok, you can argue that it's not always true because of Unicode surrogates). See also VB.NET version and more details about Unicode stuff.

private int _previousLength = 0;

private void richTextBox_TextChanged(object sender, EventArgs e)
{
int currentLength = richTextBox.Text.Length;
if (Math.Abs(currentLength - _previousLength) > 1)
ProcessAllLines();

_previousLength = currentLength;
}

Please note that you can't (because of how different IMEs work) use OnKeyDown (or similar). This works well only for western languages but it has problems with Unicode stuff (because, for example, String.Length property may be increased by two Char when user typed a single character. See also this post for much more details about this (well it's a strongly suggested reading even, even if - in this case - you don't care about it). In that post you'll also find code for a better algorithm to determine string length. In short you have to replace:

   int currentLength = richTextBox.Text.Length;

With this:

   int currentLength = StringInfo.GetTextElementEnumerator(richTextBox.Text)
.Cast<string>()
.Count();

After all this effort you may realize that...user can even paste a single character and it may go undetected. You're right, that's why this is a better detection instead of a perfect solution.

Perfect solution

The perfect solution (if you're running on Windows 8) of course exists, the native rich edit control sends an EN_CLIPFORMAT notification message. It's intended to notify a rich edit control's parent window that a paste occurred with a particular clipboard format. You can then override the WndProc of its parent to detect the WM_NOTIFY message for this notification. Anyway it's not few lines of code, check this MSDN article for details.

Winforms textbox paste unreliable?

I have a fancy firewall that is also able block applications from looking at data in the clipboard, on per-application basis.

It does not prevent writing; the point of the feature is to stop malware from stealing important information that might end up in the clipboad, such as a password.

It can also block any given application from doing various other system tasks, e.g. taking a screenshot or navigating to a web page with the default system browser.

The customer might have something like that installed, with Notepad allowed in the rules.

Pasting of serialnumber over multiple textfields

In the first textbox, I would put a large limit.

On the 'text changed', check the length. If the change is greater than 4 (your maximum). Delete the extra stuff and spread it over your textboxes.

If you copy-paste, it'll text change of 32, and it would work. You could also change the cursor (I think its .Focus() but I could be wrong) so it automatically 'hops' between the boxes.

Block Paste event in a DomainUpDown control

Using a NativeWindow you can handle messages of the inner TextBox which is used in DomainUpDown and NumericUpDown controls.

In the follwowing code, I've handled WM_PASTE method and just played a beep in response:

using System;
using System.Windows.Forms;
public class MyDomainUpDown : DomainUpDown
{
MyWindoHelper wh;
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
wh = new MyWindoHelper(Controls[1]);
}
protected override void Dispose(bool disposing)
{
if (wh != null)
wh.DestroyHandle();
base.Dispose(disposing);
}
class MyWindoHelper : NativeWindow
{
Control c; //For future reference if needed.
public MyWindoHelper(Control control)
{
c = control;
this.AssignHandle(c.Handle);
}
protected override void WndProc(ref Message m)
{
if (m.Msg != 0x0302 /*WM_PASTE*/)
base.WndProc(ref m);
else
System.Media.SystemSounds.Beep.Play();
}
}
}

If you may want to validate the input and paste a sanitized text to the control, take a look at the following post:

  • Textbox - Validate input before paste

Winforms MaskedTextBox - Reformatting pasted text to match mask

While this is a hammer solution, there are limitations to the mask string and i don't see another way around it. What you need is to capture the paste event and process the text before it gets in the textbox. See below a simplistic example

   class MyMaskedTextbox : MaskedTextBox
{
const int WM_PASTE = 0x0302;

protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_PASTE:
if (Clipboard.ContainsText())
{
string text = Clipboard.GetText();
text = text.Replace(' ', '-');
//put your processing here
Clipboard.SetText(text);
}
break;
}
base.WndProc(ref m);
}
}

detect enterline when copy big text data into textbox

you can create hook on default “Paste” event of WinForms TextBox control like below by creating custom textbox specially for your requirement.

using System.Windows.Forms;

namespace WindowsFormsApplication1
{
public class MyTextBox : TextBox
{
protected override void WndProc(ref Message m)
{
// Trap WM_PASTE:
if (m.Msg == 0x302 && Clipboard.ContainsText())
{
var pastText = Clipboard.GetText().Replace('\n', ' ');
if (pastText.Length > MaxLength)
{
//Do Something
}
else
{
//Do Something
}
this.SelectedText = pastText;
return;
}
base.WndProc(ref m);
}
}
}


Related Topics



Leave a reply



Submit