77

I have created a custom control inheriting TextBox. This custom control is a numeric TextBox, only supporting numbers.

I am using OnPreviewTextInput to check each new character being typed to see if the character is a valid input. This works great. However, if I paste the text into the TextBox, OnPreviewTextInput is not fired.

What is the best way to capture pasted text in a TextBox?

Also, I have a problem when the back space is pressed, I can't figure out what event this will fire. OnPreviewTextInput is not fired!

Any ideas how to capture pasted text and back space events in WPF TextBox?

Arcturus
  • 26,677
  • 10
  • 92
  • 107
code-zoop
  • 7,312
  • 8
  • 47
  • 56
  • 1
    Since you are using WPF, I would bind the textbox to a property from your view model. This way you can catch all changes of the text, from a paste, keyboard, etc. You can also implement IDataErrorInfo so that you can display on screen errors if the input validation fail. – Alexandru Dicu Nov 22 '18 at 09:00

7 Answers7

149

Here's some code I had lying around in case I ever needed it. Might help you.

public Window1()
{
    InitializeComponent();

    // "tb" is a TextBox
    DataObject.AddPastingHandler(tb, OnPaste);
}

private void OnPaste(object sender, DataObjectPastingEventArgs e)
{
    var isText = e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true);
    if (!isText) return;

    var text = e.SourceDataObject.GetData(DataFormats.UnicodeText) as string;
    ...
}
BlueRaja - Danny Pflughoeft
  • 84,206
  • 33
  • 197
  • 283
Matt Hamilton
  • 200,371
  • 61
  • 386
  • 320
  • 1
    @Matt how can I override the richtextbox default copy/paste functions ? – raym0nd Aug 30 '11 at 13:55
  • 9
    It took me a moment to find e.CancelCommand() for the invalid case. Also [this question](http://stackoverflow.com/questions/7616274/change-paste-contents-in-textbox) was helpful for editing the paste contents. – Simon F May 21 '14 at 16:02
  • I can't really get this to work. I'm abit confused what to do after the if statement. Can anyone summon up what is to be done with the new text variable? – Max Mazur Jun 06 '14 at 10:24
  • 1
    @MaxMazur the `text` variable may be assigned to the `tb` textfield. For my use case, I replaced all newlines in the string with commas. And then called `e.CancelCommand()` as suggested by Simon F in order to suppress any other handlers reacting to the Paste command. – Cardin Apr 27 '16 at 02:51
  • 1
    What is the reason not to use: `e.FormatToApply = DataFormats.UnicodeText;` in the OnPaste event? – Alina B. Aug 07 '17 at 08:39
16

The trouble with trying to intercept and trap all the individual events that might cause a TextBox.Text property to change is that there are many such events:

  • TextInput: User types
  • KeyDown: Delete, Backspace, Enter, IME
  • Command Gestures: Ctrl-X, Ctrl-Y, Ctrl-V, Ctrl-X
  • MouseDown: Paste button, Cut button, Undo button, ...
  • Click: Space bar pressed when Paste, Cut, Undo buttons have local focus
  • RaiseEvent: Code raises Paste, Cut, Undo, Redo commands
  • Accessiblity: Voice commands, Braille keyboards, etc

Trying to reliably intercept all of these is an exercise in futility. A much better solution is to monitor TextBox.TextChanged and reject changes that you don't like.

In this answer I show how to implement a TextBoxRestriction class for the particular scenario being asked about. This same technique can be generalized for use with any restrictions you want to place on your TextBox control.

For example, in your case you might implemnt a RestrictValidChars attached property similarly to the RestrictDeleteTo property in that code. It would be the same except that the inner loop would check inserts, not deletes. It would be used like this:

<TextBox my:TextBoxRestriction.RestrictValidChars="0123456789" />

This is just an idea of how it could be handled. There are many ways to structure your code depending on what you want. For example you could change TextBoxRestriction to call your own code to validate using an attached property that takes a delegate or an object containing an event.

See the other answer for details on how to bind the Text property when you are using the TextBoxRestriction class so it won't trigger the restriction when you don't want it to.

Community
  • 1
  • 1
Ray Burns
  • 62,163
  • 12
  • 140
  • 141
  • 2
    Just a note. I just tested a textbox and the TextChanged event did not fire in response to a mouse paste. (WPF, C#, .Net 4.5) – Daniel Jul 22 '13 at 19:21
11

For backspace, please check the PreviewKeyDown event

For paste command, add a command binding to the ApplicationCommands.Paste, and set the argument to handled, if you do not wish to do anything with it:

<Window.CommandBindings>
  <CommandBinding Command="ApplicationCommands.Paste"
                  Executed="PasteExecuted" />
</Window.CommandBindings>

And in code behind:

private void PasteExecuted(object sender, ExecutedRoutedEventArgs e)
{
    e.Handled = true;
}
Arcturus
  • 26,677
  • 10
  • 92
  • 107
  • 1
    This disables paste rather than capturing the text. It also fails to address all the other ways text could be changed in a TextBox that code-zoop didn't ask about yet, such as accessibility, the Undo command, etc. I think it is better to use a general solution based on the TextChanged event because it cleanly handles all possible scenarios. I posted an answer that shows how to do this. – Ray Burns Jun 17 '10 at 13:43
  • For those who would like to capture the clipboard's text using this method, you can call [`Clipboard.GetText();`](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.clipboard.gettext?view=netframework-4.8) – alexleen Sep 20 '19 at 19:46
4

This might not be the exact answer your looking for but here is how to handle pasted text (this also works if user pasted using a the context menu):

InitializeComponent();

                // "DescriptionTextBox" is a TextBox
                DataObject.AddPastingHandler(DescriptionTextBox, OnDescriptionPaste);

private void OnDescriptionPaste(object sender, DataObjectPastingEventArgs e)
        {
            if (!e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true))
                return;

            var pastedText = e.SourceDataObject.GetData(DataFormats.UnicodeText) as string;
            if (string.IsNullOrEmpty(pastedText))
                return;

            var txtBox = (TextBox) sender;

            var before = ""; //Text before pasted text
            var after = txtBox.Text; //Text after pasted text

            //Get before and after text
            if (txtBox.CaretIndex > 0)
            {
                before = txtBox.Text.Substring(0, txtBox.CaretIndex);
                after = txtBox.Text.Substring(txtBox.CaretIndex);
            }

            //Do custom logic for handling the pasted text.
            //Split sentences ending with . into new line.
            var parts = pastedText.Split(new []{'.'}, StringSplitOptions.RemoveEmptyEntries);
            if (parts.Length > 1)
            {
                pastedText = parts.Select(x => x.Trim()).ToArray().ToStringX(".\r\n");
                pastedText += ".";
            }

            var newCaretIndex = before.Length + pastedText.Length;

            e.CancelCommand(); //Cancels the paste, we do it manually
            txtBox.Text = $"{before}{pastedText}{after}"; //Set new text
            txtBox.CaretIndex = newCaretIndex; //Set new caret index
        }

For handling backspace use PreviewKeyDown event.

2

You can achieve this with PreviewKeyDown event and TextChanged event.

In PreviewKeyDown capture the Paste operation

if(Key.V == e.Key && Keyboard.Modifiers == ModifierKeys.Control)
{
   strPreviousString = this.txtNumber.Text;
   bIsPasteOperation = true;
}

In TextChanged event

if (true == bIsPasteOperation)
{

   if (false == this.IsNumber(this.txtNumber.Text))
   {
      this.txtNumber.Text = strPreviousString;
      e.Handled = true;
   }
   bIsPasteOperation = false;
}

Where IsNumber method validates the entered text is Number or not

private bool IsNumber(string text)
{
   int number;

   //Allowing only numbers
   if (!(int.TryParse(text, out number)))
   {
      return false;
   }
   return true
}
dandan78
  • 13,328
  • 13
  • 64
  • 78
Syed
  • 953
  • 1
  • 8
  • 11
0

This works pretty good for me. I wanted to changed the color of the textbox when the user made a change to the contents.

  • accept numbers, including period and negative characters
  • keys typed: delete, backspace, ctrl-V (paste), ctrl-X (cut)
  • right mouse click for paste and cut

I was able to achieve it with the 3 events below:

    public bool IsDirty {
        set {
            if(value) {
                txtValue.Background = Brushes.LightBlue;
            } else {
                txtValue.Background = IsReadOnly ? Brushes.White : Brushes.LightYellow;
            }
        }
        get {
            return txtValue.Background == Brushes.LightBlue;
        }
    }

    private void PreviewTextInput(object sender, TextCompositionEventArgs e) {
        TextBox tb = ((TextBox)sender);
        string originalText = tb.Text;
        string newVal = "";

        //handle negative
        if (e.Text=="-") {
            if(originalText.IndexOf("-") > -1 || tb.CaretIndex != 0 || originalText == "" || originalText == "0") {
                //already has a negative or the caret is not at the front where the - should go
                //then ignore the entry
                e.Handled = true;
                return;
            }

            //put it at the front
            newVal = e.Text + originalText;
        } else {
            //normal typed number
            newVal = originalText + e.Text;
        }

        //check if it's a valid double if so then dirty
        double dVal;
        e.Handled = !double.TryParse(newVal, out dVal);
        if(!e.Handled) {
            IsDirty = true;
        }
    }

    private void PreviewKeyUp(object sender, KeyEventArgs e) {
        //handle paste
        if ((Key.V == e.Key || Key.X == e.Key) && Keyboard.Modifiers ==  ModifierKeys.Control) {
            IsDirty = true;
        }
        //handle delete and backspace
        if (e.Key == Key.Delete || e.Key == Key.Back) {
            IsDirty = true;
        }
    }


    private void PreviewExecuted(object sender, ExecutedRoutedEventArgs e) {
        //handle context menu cut/paste
        if (e.Command == ApplicationCommands.Cut || e.Command == ApplicationCommands.Paste) {
            IsDirty = true;
        }
    }
JJ_Coder4Hire
  • 4,706
  • 1
  • 37
  • 25
0

The below code worked for me. I hope, it will help someone.

Use the below code if you are using Xceed RichTextBox control.

 <xctk:RichTextBox Name="Description" CommandManager.PreviewExecuted="CommandExecuted_PreviewExecuted"> 

 private void CommandExecuted_PreviewExecuted(object sender, RoutedEventArgs e)
    {
        Xceed.Wpf.Toolkit.RichTextBox richTextBox =  (Xceed.Wpf.Toolkit.RichTextBox)sender;

        string rtbtext = StringFromRichTextBox(richTextBox);
        if ((e as ExecutedRoutedEventArgs).Command == ApplicationCommands.Paste)
        {
            // verify that the textbox handled the paste command
            if (Clipboard.GetText() > 2500)//Get copied text from clipboard
            {
                e.Handled = true;// prevent paste if length is more than 2500.
                return;
            }
        }
    } 

If you are using TextBlock, then use below code

TextBlock textBlock = (TextBlock)sender;

instead of this

Xceed.Wpf.Toolkit.RichTextBox richTextBox =  (Xceed.Wpf.Toolkit.RichTextBox)sender;

Rest all codes can remain the same as above for TextBlock as well.

Rajeev Kumar
  • 371
  • 2
  • 9