我想区分以编程方式更改文本
(例如,在按钮单击处理程序事件中)和用户输入(键入,
剪切和粘贴文本)。
有可能吗?
答案 0 :(得分:25)
TextBox
中的用户输入可以用
将这三个事件与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的部分信用(同意不符合您希望的漂亮设计,但是最好的妥协)。 考虑您想要涵盖的所有内容:
考虑要排除的内容:
代码
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.
}
}
以下是不鼓励的,但试图涵盖所有的结果:
捕获所有事件的替代方案是:
试图涵盖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相当复杂的解决方案就足够了。我意识到,如果你不得不倾听程序化的变化,那么这不是一个完整的解决方案,但在我的情况下它运作良好。