我认为需要一个自定义控件来实现您描述的行为。通过重写默认的WPF TextBox上的一些方法,我们可以保留用户输入,即使ViewModel发生变化。
OnTextChanged
方法将被调用,无论我们的文本框如何更新(包括键盘事件和ViewModel更改),但重写OnPreviewKeyDown
方法将分离出直接的用户输入。但是,OnPreviewKeyDown
不提供访问文本框值的简单方法,因为它也被调用非可打印字符控制(箭头键、退格键等)的情况。
下面,我创建了一个WPF控件,该控件继承自TextBox并覆盖了OnPreviewKeyDown
方法,以捕获最后一次用户按键的确切时间。OnTextChanged
检查时间,并仅在两个事件快速连续发生时更新文本。
如果最后一个键盘事件发生的时间超过几毫秒,那么更新可能不是来自我们的用户。
public class StickyTextBox : TextBox
{
private string _lastText = string.Empty;
private long _ticksAtLastKeyDown;
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
_ticksAtLastKeyDown = DateTime.Now.Ticks;
base.OnPreviewKeyDown(e);
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
if (!IsInitialized)
_lastText = Text;
if (IsFocused)
{
var elapsed = new TimeSpan(DateTime.Now.Ticks - _ticksAtLastKeyDown);
if (elapsed.TotalMilliseconds <= 5) {
_lastText = Text;
}
else {
Text = _lastText;
e.Handled = true;
}
}
base.OnTextChanged(e);
}
}
这是一个我用于测试的示例视图模型。它会每隔10秒从一个单独的线程更新自己的属性5次,以模拟来自其他用户的后台更新。
class ViewModelMain : ViewModelBase, INotifyPropertyChanged
{
private delegate void UpdateText(ViewModelMain vm);
private string _textProperty;
public string TextProperty
{
get { return _textProperty; }
set
{
if (_textProperty != value)
{
_textProperty = value;
RaisePropertyChanged("TextProperty");
}
}
}
public ViewModelMain()
{
TextProperty = "Type here";
for (int i = 1; i <= 5; i++)
{
var sleep = 10000 * i;
var copy = i;
var updateTextDelegate = new UpdateText(vm =>
vm.TextProperty = string.Format("New Value #{0}", copy));
new System.Threading.Thread(() =>
{
System.Threading.Thread.Sleep(sleep);
updateTextDelegate.Invoke(this);
}).Start();
}
}
}
这段XAML代码创建了我们自定义的StickyTextBox
和一个普通的TextBox
,它们都绑定到同一个属性,以展示它们之间行为上的差异:
<StackPanel>
<TextBox Text="{Binding TextProperty, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBlock FontWeight="Bold" Margin="5" Text="The 'sticky' text box">
<local:StickyTextBox Text="{Binding TextProperty}" MinWidth="200" />
</TextBlock>
</StackPanel>