我在Windows桌面WPF应用程序中有一个绑定到ViewModel属性的TextBox。现在,用户关注TextBox并开始输入新值。在此期间,后台进程为同一属性获取新值(例如,因为多用户环境中的另一个用户输入新值并且观察者正在检测并传播此更改)并为此属性调用PropertyChanged事件。现在值发生变化,当前用户输入的内容将丢失。
TextBox聚焦时是否有内置的方法来阻止更改?或者我是否必须构建自己的解决方案?
答案 0 :(得分:1)
我认为需要自定义控件来实现您描述的行为。通过覆盖默认WPF TextBox上的几个方法,即使视图模型发生更改,我们也可以保留用户输入。
无论我们的文本框如何更新(键盘事件和视图模型更改)都将调用OnTextChanged
方法,但覆盖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 the time between the last keydown event and our text change is
// very short, we can be fairly certain than text change was caused
// by the user. We update the _lastText to store their new user input
if (elapsed.TotalMilliseconds <= 5) {
_lastText = Text;
}
else {
// if our last keydown event was more than a few seconds ago,
// it was probably an external change
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>