TextBox TextChanged事件,用于编程与用户更改文本内容

时间:2011-09-01 13:35:52

标签: wpf events wpf-controls

我想区分以编程方式更改文本  (例如,在按钮单击处理程序事件中)和用户输入(键入, 剪切和粘贴文本)。
有可能吗?

8 个答案:

答案 0 :(得分:25)

TextBox中的用户输入可以用

标识
  • 键入: PreviewTextInput事件
  • 退格,删除,输入: PreviewKeyDown事件
  • 粘贴: DataObject.PastingEvent

将这三个事件与bool标志结合起来,以指示在 TextChanged 事件之前是否发生了上述任何事件,并且您将知道更新的原因。

键入和粘贴很容易,但Backspace并不总是触发TextChanged(如果没有选择文本,光标位于0位)。因此 PreviewTextInput 需要一些逻辑。

这是一个附加行为,它实现上面的逻辑,并在引发TextChanged时执行带有bool标志的命令。

<TextBox ex:TextChangedBehavior.TextChangedCommand="{Binding TextChangedCommand}" />

在代码中,您可以找到更新的来源,如

private void TextChanged_Executed(object parameter)
{
    object[] parameters = parameter as object[];
    object sender = parameters[0];
    TextChangedEventArgs e = (TextChangedEventArgs)parameters[1];
    bool userInput = (bool)parameters[2];

    if (userInput == true)
    {
        // User input update..
    }
    else
    {
        // Binding, Programatic update..
    }
}

这是一个展示效果的小样本项目:SourceOfTextChanged.zip

TextChangedBehavior

public class TextChangedBehavior
{
    public static DependencyProperty TextChangedCommandProperty =
        DependencyProperty.RegisterAttached("TextChangedCommand",
                                            typeof(ICommand),
                                            typeof(TextChangedBehavior),
                                            new UIPropertyMetadata(TextChangedCommandChanged));

    public static void SetTextChangedCommand(DependencyObject target, ICommand value)
    {
        target.SetValue(TextChangedCommandProperty, value);
    }

    // Subscribe to the events if we have a valid command
    private static void TextChangedCommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
        TextBox textBox = target as TextBox;
        if (textBox != null)
        {
            if ((e.NewValue != null) && (e.OldValue == null))
            {
                textBox.PreviewKeyDown += textBox_PreviewKeyDown;
                textBox.PreviewTextInput += textBox_PreviewTextInput;
                DataObject.AddPastingHandler(textBox, textBox_TextPasted);
                textBox.TextChanged += textBox_TextChanged;
            }
            else if ((e.NewValue == null) && (e.OldValue != null))
            {
                textBox.PreviewKeyDown -= textBox_PreviewKeyDown;
                textBox.PreviewTextInput -= textBox_PreviewTextInput;
                DataObject.RemovePastingHandler(textBox, textBox_TextPasted);
                textBox.TextChanged -= textBox_TextChanged;
            }
        }
    }

    // Catches User input
    private static void textBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        TextBox textBox = sender as TextBox;
        SetUserInput(textBox, true);
    }
    // Catches Backspace, Delete, Enter
    private static void textBox_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        TextBox textBox = sender as TextBox;
        if (e.Key == Key.Return)
        {
            if (textBox.AcceptsReturn == true)
            {
                SetUserInput(textBox, true);
            }
        }
        else if (e.Key == Key.Delete)
        {
            if (textBox.SelectionLength > 0 || textBox.SelectionStart < textBox.Text.Length)
            {
                SetUserInput(textBox, true);
            }
        }
        else if (e.Key == Key.Back)
        {
            if (textBox.SelectionLength > 0 || textBox.SelectionStart > 0)
            {
                SetUserInput(textBox, true);
            }
        }
    }
    // Catches pasting
    private static void textBox_TextPasted(object sender, DataObjectPastingEventArgs e)
    {
        TextBox textBox = sender as TextBox;
        if (e.SourceDataObject.GetDataPresent(DataFormats.Text, true) == false)
        {
            return;
        }
        SetUserInput(textBox, true);
    }
    private static void textBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        TextBox textBox = sender as TextBox;
        TextChangedFired(textBox, e);
        SetUserInput(textBox, false);
    }

    private static void TextChangedFired(TextBox sender, TextChangedEventArgs e)
    {
        ICommand command = (ICommand)sender.GetValue(TextChangedCommandProperty);
        object[] arguments = new object[] { sender, e, GetUserInput(sender) };
        command.Execute(arguments);
    }

    #region UserInput

    private static DependencyProperty UserInputProperty =
        DependencyProperty.RegisterAttached("UserInput",
                                            typeof(bool),
                                            typeof(TextChangedBehavior));
    private static void SetUserInput(DependencyObject target, bool value)
    {
        target.SetValue(UserInputProperty, value);
    }
    private static bool GetUserInput(DependencyObject target)
    {
        return (bool)target.GetValue(UserInputProperty);
    }

    #endregion // UserInput
}

答案 1 :(得分:5)

如果您只想使用内置的WPF TextBox,那么我认为不可能。

此处有关Silverlight论坛的类似讨论:http://forums.silverlight.net/p/119128/268453.aspx 这不是完全相同的问题,但我认为类似于原始帖子中的想法可能会为您解决问题。在子类TextBox上有一个SetText方法,它在更改文本之前设置一个标志,然后在之后重新设置。然后,您可以检查TextChanged事件中的标志。这当然要求所有程序化文本更改都使用该方法,但如果您对项目有足够的控制权,我认为它可以工作。

答案 2 :(得分:4)

与JHunz的答案类似,只需在控件中添加一个布尔成员变量:

bool programmaticChange = false;

进行程序化更改时,请执行以下操作:

programmaticChange = true;
// insert changes to the control text here
programmaticChange = false;

在您的事件处理程序中,您只需检查programmaticChange的值,以确定它是否是程序更改。

相当明显而且不是很优雅,但它也可行且简单。

答案 3 :(得分:3)

dodgy_coder的部分信用(同意不符合您希望的漂亮设计,但是最好的妥协)。 考虑您想要涵盖的所有内容:

  1. 通过鼠标从TB2拖动到TB1来移动文本
  2. cut(ctrl-x,programmatic cut,mouse-menu-cut)
  3. 粘贴(ctrl-v,程序化粘贴,鼠标菜单粘贴)
  4. undo(ctrl-z,programmatic undo)
  5. redo(ctrl-Y,programmatic redo)
  6. 删除&amp;退格
  7. 键盘文字(alfanumeric + symbols + space)
  8. 考虑要排除的内容:

    1. 文字的编程设置
    2. 代码

      public class MyTB : TextBox
      {
          private bool _isTextProgrammaticallySet = false;
      
          public new string Text
          {
              set
              {
                  _isTextProgrammaticallySet = true;
                  base.Text = value;
                  _isTextProgrammaticallySet = false;
              }
          }
      
          protected override void OnTextChanged(TextChangedEventArgs e)
          {
              base.OnTextChanged(e);
      
              // .. on programmatic or on user
      
      
              // .. on programmatic
              if (_isTextProgrammaticallySet)
              {
      
      
                  return;
              }
      
              // .. on user
              OnTextChangedByUser(e);
          }
      
          protected void OnTextChangedByUser(TextChangedEventArgs e)
          {
              // Do whatever you want.
          }
      }
      

      以下是不鼓励的,但试图涵盖所有的结果:
      捕获所有事件的替代方案是:

      • DataObject.AddPastingHandler(MyTextBox,MyPasteCommand);
        封面1&amp; 3
      • OnPreviewTextInput
        涵盖7但不是空间
      • 的onkeydown
        涵盖7空间

      试图涵盖2,4,5,6及2; 8我认为我应该采用上述更简单一致的解决方案:)

答案 4 :(得分:3)

根据您的具体要求,您可以在TextBox.IsFocused事件中使用TextChanged来确定手动输入。这显然不会涵盖程序化更改的所有方式,但很多例子都可以正常使用,并且这样做非常干净且省钱。

基本上这适用于:
......程序上的变化都是基于手动变化(例如按下按钮) 如果出现以下情况则无效:
......程序化的变化完全基于代码(例如,定时器)。

代码示例:

textBox.TextChanged += (sender, args) =>
    if (textBox.IsFocused)
    {
        //do something for manual input
    }
    else
    {
        //do something for programmatical input
    }
}

答案 5 :(得分:2)

我已经从Fredrik answer清理并修改了TextChangedBehavior类,以便它也能正确处理剪切命令( ctr + X

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

public class TextChangedBehavior
{
    public static readonly DependencyProperty TextChangedCommandProperty =
        DependencyProperty.RegisterAttached("TextChangedCommand",
                                            typeof (ICommand),
                                            typeof (TextChangedBehavior),
                                            new UIPropertyMetadata(TextChangedCommandChanged));

    private static readonly DependencyProperty UserInputProperty =
        DependencyProperty.RegisterAttached("UserInput",
                                            typeof (bool),
                                            typeof (TextChangedBehavior));

    public static void SetTextChangedCommand(DependencyObject target, ICommand value)
    {
        target.SetValue(TextChangedCommandProperty, value);
    }

    private static void ExecuteTextChangedCommand(TextBox sender, TextChangedEventArgs e)
    {
        var command = (ICommand)sender.GetValue(TextChangedCommandProperty);
        var arguments = new object[] { sender, e, GetUserInput(sender) };
        command.Execute(arguments);
    }

    private static bool GetUserInput(DependencyObject target)
    {
        return (bool)target.GetValue(UserInputProperty);
    }

    private static void SetUserInput(DependencyObject target, bool value)
    {
        target.SetValue(UserInputProperty, value);
    }

    private static void TextBoxOnPreviewExecuted(object sender, ExecutedRoutedEventArgs e)
    {
        if (e.Command != ApplicationCommands.Cut)
        {
            return;
        }

        var textBox = sender as TextBox;
        if (textBox == null)
        {
            return;
        }

        SetUserInput(textBox, true);
    }

    private static void TextBoxOnPreviewKeyDown(object sender, KeyEventArgs e)
    {
        var textBox = (TextBox)sender;
        switch (e.Key)
        {
            case Key.Return:
                if (textBox.AcceptsReturn)
                {
                    SetUserInput(textBox, true);
                }
                break;

            case Key.Delete:
                if (textBox.SelectionLength > 0 || textBox.SelectionStart < textBox.Text.Length)
                {
                    SetUserInput(textBox, true);
                }
                break;

            case Key.Back:
                if (textBox.SelectionLength > 0 || textBox.SelectionStart > 0)
                {
                    SetUserInput(textBox, true);
                }
                break;
        }
    }

    private static void TextBoxOnPreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        SetUserInput((TextBox)sender, true);
    }

    private static void TextBoxOnTextChanged(object sender, TextChangedEventArgs e)
    {
        var textBox = (TextBox)sender;
        ExecuteTextChangedCommand(textBox, e);
        SetUserInput(textBox, false);
    }

    private static void TextBoxOnTextPasted(object sender, DataObjectPastingEventArgs e)
    {
        var textBox = (TextBox)sender;
        if (e.SourceDataObject.GetDataPresent(DataFormats.Text, true) == false)
        {
            return;
        }

        SetUserInput(textBox, true);
    }

    private static void TextChangedCommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
        var textBox = target as TextBox;
        if (textBox == null)
        {
            return;
        }

        if (e.OldValue != null)
        {
            textBox.PreviewKeyDown -= TextBoxOnPreviewKeyDown;
            textBox.PreviewTextInput -= TextBoxOnPreviewTextInput;
            CommandManager.RemovePreviewExecutedHandler(textBox, TextBoxOnPreviewExecuted);
            DataObject.RemovePastingHandler(textBox, TextBoxOnTextPasted);
            textBox.TextChanged -= TextBoxOnTextChanged;
        }

        if (e.NewValue != null)
        {
            textBox.PreviewKeyDown += TextBoxOnPreviewKeyDown;
            textBox.PreviewTextInput += TextBoxOnPreviewTextInput;
            CommandManager.AddPreviewExecutedHandler(textBox, TextBoxOnPreviewExecuted);
            DataObject.AddPastingHandler(textBox, TextBoxOnTextPasted);
            textBox.TextChanged += TextBoxOnTextChanged;
        }
    }
}

答案 6 :(得分:1)

感谢蒂姆指向正确的方向,但是根据我的需要,对IsFocus的检查就像一个魅力。它太简单了......

 if (_queryField.IsKeyboardFocused && _queryField.IsKeyboardFocusWithin)
 {
     //do your things
 }
 else 
 { 
     //whatever 
 }

答案 7 :(得分:0)

我也有这个问题,但就我的情况而言,听取(Preview)TextInput事件而不是使用Meleak相当复杂的解决方案就足够了。我意识到,如果你不得不倾听程序化的变化,那么这不是一个完整的解决方案,但在我的情况下它运作良好。