wpf TextBox TextChanged 事件对文本内容的编程与用户更改
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7271101/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
TextBox TextChanged event on programmatic versus user change of text contents
提问by Greg
I would like to differentiate between changing the text programmatically
(for example in a button click handler event) and user input (typing,
cutting and pasting text).
Is it possible?
我想区分以编程方式更改文本(例如在按钮单击处理程序事件中)和用户输入(键入、剪切和粘贴文本)。
是否可以?
采纳答案by JHunz
If you just want to use the built-in WPF TextBox, then I don't believe it's possible.
如果您只想使用内置的 WPF TextBox,那么我认为这是不可能的。
There is a similar discussion on the Silverlight forums here: http://forums.silverlight.net/p/119128/268453.aspxIt's not exactly the same question, but I think the idea similar to that in the original post might do the trick for you. Have a SetText method on a subclassed TextBox, that set a flag before changing the text and then set it back after. You could then check for the flag inside the TextChanged event. This would of course require all of your programmatic text changes to use that method, but if you have enough control over the project to mandate that I think it would work.
Silverlight 论坛上也有类似的讨论:http: //forums.silverlight.net/p/119128/268453.aspx这不是完全相同的问题,但我认为与原始帖子中类似的想法可能会给你的伎俩。在子类 TextBox 上有一个 SetText 方法,在更改文本之前设置一个标志,然后将其设置回原位。然后,您可以检查 TextChanged 事件中的标志。这当然需要您对所有程序化文本进行更改才能使用该方法,但是如果您对项目有足够的控制权,我认为它会起作用。
回答by Fredrik Hedblad
User input in a TextBox
can be identified with
用户在 a 中的输入TextBox
可以用
- Typing : PreviewTextInput event
- Backspace, Delete, Enter : PreviewKeyDown event
- Pasting : DataObject.PastingEvent
- 输入:PreviewTextInput 事件
- 退格、删除、回车:PreviewKeyDown 事件
- 粘贴:DataObject.PastingEvent
Combining these three events with a bool flag to indicate if any of the above occured before the TextChangedevent and you'll know the reason for the update.
将这三个事件与 bool 标志相结合,以指示上述任何事件是否发生在TextChanged事件之前,您就会知道更新的原因。
Typing and Pasting are easy, but Backspace doesn't always trigger TextChanged
(if no text is selected and the cursor is at position 0 for example). So some logic is needed in PreviewTextInput.
键入和粘贴很容易,但 Backspace 并不总是触发TextChanged
(例如,如果没有选择文本并且光标位于位置 0)。所以在PreviewTextInput 中需要一些逻辑。
Here is an Attached Behavior that implements the logic above and executes a command with a bool flag when TextChanged
is raised.
这是一个附加行为,它实现了上述逻辑,并在TextChanged
引发时执行带有 bool 标志的命令。
<TextBox ex:TextChangedBehavior.TextChangedCommand="{Binding TextChangedCommand}" />
And in code you can find out the source for the update like
在代码中,您可以找到更新的来源,例如
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..
}
}
Here is a small sample project demonstrating the effect: SourceOfTextChanged.zip
这是一个演示效果的小示例项目: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
}
回答by dodgy_coder
Similar to JHunz's answer, just add a boolean member variable to your control:
与 JHunz 的回答类似,只需在您的控件中添加一个布尔成员变量:
bool programmaticChange = false;
When you are making programmatic changes do this:
当您进行程序化更改时,请执行以下操作:
programmaticChange = true;
// insert changes to the control text here
programmaticChange = false;
In your event handlers, you just need to inspect the value of programmaticChange
to determine if its a programmatic change or not.
在您的事件处理程序中,您只需要检查 的值programmaticChange
以确定它是否是程序性更改。
Fairly obvious and not very elegant but its also workable and simple.
相当明显,不是很优雅,但它也可行且简单。
回答by Tim Pohlmann
Depending on your exact demands you can use TextBox.IsFocused
in the TextChanged
event to determine manual input. This will obviously not cover all ways of programmatical changes, but works for a lot of examples just fine and is a pretty clean and save way of doing so.
根据您的确切需求,您可以TextBox.IsFocused
在TextChanged
事件中使用来确定手动输入。这显然不会涵盖所有的编程更改方式,但适用于很多示例就好了,并且是一种非常干净且保存的方式。
Basically this works if:
...the programmatical changes are all based on a manual change (e.g. a Button press).
It will not work if:
...the programmatical changes are completely based on code (e.g. a Timer).
基本上这在以下情况下有效:
...程序更改全部基于手动更改(例如按下按钮)。
如果出现以下情况,它将不起作用:
...编程更改完全基于代码(例如计时器)。
Code example:
代码示例:
textBox.TextChanged += (sender, args) =>
if (textBox.IsFocused)
{
//do something for manual input
}
else
{
//do something for programmatical input
}
}
回答by bitbonk
I have cleaned up and modified the TextChangedBehavior
class from Fredrik answerso that it also correctly handles the cut command (ctr+X).
我已经清理并修改了Fredrik 答案中的TextChangedBehavior
类,以便它也能正确处理 cut 命令 ( + )。ctrX
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;
}
}
}
回答by EricG
Partial credits for dodgy_coder (agreed not conform the beautiful design you hope for, but imo the best compromis). Consider everything you want to cover:
dodgy_coder 的部分功劳(同意不符合您希望的漂亮设计,但我认为是最好的妥协)。考虑您想要涵盖的所有内容:
- move text by dragging with mouse from TB2 to TB1
- cut (ctrl-x, programmatic cut, mouse-menu-cut)
- paste (ctrl-v, programmatic paste, mouse-menu-paste)
- undo (ctrl-z, programmatic undo)
- redo (ctrl-Y, programmatic redo)
- delete & backspace
- keyboard text (alfanumeric + symbols + space)
- 通过用鼠标从 TB2 拖动到 TB1 来移动文本
- 剪切(ctrl-x,程序化剪切,鼠标菜单剪切)
- 粘贴(ctrl-v,程序化粘贴,鼠标菜单粘贴)
- 撤消(ctrl-z,程序化撤消)
- 重做(ctrl-Y,程序化重做)
- 删除和退格
- 键盘文本(字母数字 + 符号 + 空格)
Consider what you want to exclude:
考虑您要排除的内容:
- programmatic setting of Text
- 文本的编程设置
Code
代码
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.
}
}
The following is discouraged, but the results of an attempt to cover all:
The alternatives for catching all the events were:
以下是不鼓励的,但尝试覆盖所有的结果:
捕获所有事件的替代方法是:
- DataObject.AddPastingHandler(MyTextBox, MyPasteCommand);
Covers 1 & 3 - OnPreviewTextInput
Covers 7 but not space - OnKeyDown
Covers 7-space
- DataObject.AddPastingHandler(MyTextBox, MyPasteCommand);
封面 1 & 3 - OnPreviewTextInput
覆盖 7 但不覆盖空间 - OnKeyDown
覆盖 7 个空格
Trying to cover 2, 4, 5, 6 & 8 I figured I should go with the easier and consistent solution above :)
试图涵盖 2、4、5、6 和 8,我想我应该采用上面更简单且一致的解决方案:)
回答by Bruno
Thanks to Tim for pointing in the right direction, but for my needs, the check for the IsFocus worked like a charm. It's so simple....
感谢 Tim 指出了正确的方向,但对于我的需要,IsFocus 的检查就像一个魅力。就这么简单......
if (_queryField.IsKeyboardFocused && _queryField.IsKeyboardFocusWithin)
{
//do your things
}
else
{
//whatever
}
回答by Henrik Karlsson
I had this problem too, but for my case it was enough to listen to the (Preview)TextInputevent instead of using Meleak's rather complex solution. I realize that's not a complete solution if you have to listen for programmatic changes aswell, but in my case it worked fine.
我也有这个问题,但就我而言,听(Preview)TextInput事件而不是使用 Meleak 相当复杂的解决方案就足够了。我意识到如果您还必须听取程序更改,这不是一个完整的解决方案,但就我而言,它运行良好。