繁体   English   中英

C# - WPF - 防止更新绑定的焦点TextBox

[英]C# - WPF - Prevent an update of a bound focused TextBox

我在Windows桌面WPF应用程序中有一个TextBox绑定到ViewModel的属性。 现在,用户关注TextBox并开始输入新值。 在此期间,后台进程获取相同属性的新值(例如,因为多用户环境中的另一个用户输入新值并且观察者正在检测并传播此更改)并为此属性调用PropertyChanged事件。 现在值发生变化,当前用户输入的内容将丢失。

在TextBox聚焦时是否有内置的方法来阻止更改? 或者我是否必须构建自己的解决方案?

我认为需要自定义控件来实现您描述的行为。 通过覆盖默认WPF TextBox上的几个方法,即使视图模型发生更改,我们也可以保留用户输入。

无论我们的文本框如何更新(键盘事件和视图模型更改)都将调用OnTextChanged方法,但重写OnPreviewKeyDown方法将分离出直接用户输入。 但是, OnPreviewKeyDown不提供对文本框值的轻松访问,因为它还会调用不可打印的控件字符(箭头键,退格键等)

下面,我创建了一个继承自TextBox的WPF控件,并覆盖了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>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM