Paste Event in a WPF TextBox
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 Tex开发者_开发问答tBox
?
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;
...
}
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.
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;
}
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.
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
}
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;
}
}
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.
精彩评论