在WPF TextBox中粘贴事件

77

我创建了一个继承自TextBox的自定义控件。这个自定义控件是一个数字TextBox,只支持输入数字。

我使用OnPreviewTextInput来检查每个新输入的字符是否是有效的输入。这个方法很好用。但是,如果我把文本粘贴到TextBox中,OnPreviewTextInput就不会被触发。

最好的方法是如何捕获TextBox中的粘贴文本呢?

此外,当按下退格键时,我无法确定它会触发哪个事件。 OnPreviewTextInput没有被触发!

有什么办法可以在WPF TextBox中捕获粘贴文本和退格事件吗?


1
由于您正在使用WPF,我建议将文本框绑定到视图模型中的属性。这样,您可以捕获来自粘贴、键盘等的所有文本更改。您还可以实现IDataErrorInfo接口,以便在输入验证失败时在屏幕上显示错误信息。 - Alexandru Dicu
7个回答

150

以下是我曾经写下以备不时之需的一些代码,也许能对你有所帮助。

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;
    ...
}

1
@Matt,我该如何覆盖richtextbox的默认复制/粘贴功能? - raym0nd
9
在无效的情况下,我花了一点时间才找到e.CancelCommand()。此外,这个问题对编辑文本框中粘贴内容很有帮助。 - Simon F
我真的无法让它正常工作。在if语句之后该怎么做,我有点困惑。有人能总结一下新文本变量应该做什么吗? - Max Mazur
1
@MaxMazur 可以将 text 变量分配给 tb 文本字段。对于我的用例,我用逗号替换了字符串中的所有换行符。然后调用 e.CancelCommand(),如Simon F建议的那样,以抑制对粘贴命令做出反应的任何其他处理程序。 - Cardin
1
在OnPaste事件中不使用e.FormatToApply = DataFormats.UnicodeText;的原因是什么? - Alina B.

16
试图拦截和捕获可能导致TextBox.Text属性更改的所有单个事件是有问题的,因为存在许多这样的事件:
- TextInput:用户键入 - KeyDown:删除、退格、回车、IME - Command Gestures:Ctrl-X、Ctrl-Y、Ctrl-V、Ctrl-X - MouseDown:粘贴按钮、剪切按钮、撤消按钮等 - Click:当焦点在粘贴、剪切、撤消按钮上时按下空格键 - RaiseEvent:代码引发粘贴、剪切、撤消、重做命令 - 辅助功能:语音命令、盲文键盘等
试图可靠地拦截所有这些事件是徒劳无功的。一个更好的解决方案是监视TextBox.TextChanged事件,拒绝您不喜欢的更改。
此答案中,我展示了如何为特定的场景实现TextBoxRestriction类。这种技术可以概括为用于任何您想要放置在TextBox控件上的限制。
例如,在您的情况下,您可以类似于该代码中的RestrictDeleteTo属性实现一个RestrictValidChars附加属性。它将相同,除了内部循环将检查插入而不是删除。它将像这样使用:
<TextBox my:TextBoxRestriction.RestrictValidChars="0123456789" />

这只是一个处理方式的想法。根据您的需求,有许多不同的代码结构方法可供选择。例如,您可以更改TextBoxRestriction以调用自己的代码进行验证,使用一个包含委托或事件对象的附加属性。

请参阅其他答案,了解在使用TextBoxRestriction类时如何绑定文本属性,以便在您不需要限制时不触发限制。


2
仅供参考。我刚刚测试了一个文本框,发现在鼠标粘贴时 TextChanged 事件没有触发。(WPF,C#,.Net 4.5) - Daniel

11

对于退格键,请检查PreviewKeyDown事件。

对于粘贴命令,请添加到ApplicationCommands.Paste的命令绑定,并将参数设置为handled,如果你不想对其进行任何处理:

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

在后台代码中:

private void PasteExecuted(object sender, ExecutedRoutedEventArgs e)
{
    e.Handled = true;
}

1
这将禁用粘贴而不是捕获文本。它也不能解决TextBox中的所有其他文本更改方式,例如可访问性、撤消命令等,这些都是code-zoop尚未询问的。我认为最好使用基于TextChanged事件的通用解决方案,因为它可以清晰地处理所有可能的情况。我发布了一个答案,展示了如何做到这一点。 - Ray Burns
想要使用此方法捕获剪贴板文本的人,可以调用 Clipboard.GetText(); - alexleen

4

这可能不是你想要的确切答案,但以下是如何处理粘贴的文本(如果用户使用上下文菜单粘贴,也适用):

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
        }

对于处理退格键,请使用PreviewKeyDown事件。


2
你可以通过PreviewKeyDown事件和TextChanged事件来实现此操作。
PreviewKeyDown中捕获粘贴操作。
if(Key.V == e.Key && Keyboard.Modifiers == ModifierKeys.Control)
{
   strPreviousString = this.txtNumber.Text;
   bIsPasteOperation = true;
}

TextChanged 事件中。
if (true == bIsPasteOperation)
{

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

IsNumber 方法用于验证输入的文本是否为数字。

private bool IsNumber(string text)
{
   int number;

   //Allowing only numbers
   if (!(int.TryParse(text, out number)))
   {
      return false;
   }
   return true
}

0

这对我来说非常有效。我想在用户更改内容时更改文本框的颜色。

  • 接受数字,包括小数点和负号字符
  • 键入的键:删除,退格,ctrl-V(粘贴),ctrl-X(剪切)
  • 右键单击进行粘贴和剪切

我能够通过以下3个事件实现它:

    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;
        }
    }

0
以下代码对我有效,希望能帮到其他人。
如果您正在使用Xceed RichTextBox控件,请使用以下代码。
 <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;
            }
        }
    } 

如果您正在使用TextBlock,则请使用以下代码。
TextBlock textBlock = (TextBlock)sender;

用这个代替

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

对于 TextBlock,其余所有代码都可以保持与上述相同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接