C# - WPF - 防止绑定的聚焦文本框更新

4
我在一个Windows桌面WPF应用程序中拥有一个TextBox,它绑定到ViewModel的属性。现在用户集中焦点于TextBox并开始输入新值。此期间,后台进程获取了相同属性的新值(例如,因为多用户环境中的另一个用户输入了新值并且观察者正在检测和传播此更改),并为此属性调用PropertyChanged事件。现在值已更改,当前用户刚刚输入的内容就会丢失。
是否有一种内置的方法可以防止TextBox聚焦时的更改?还是我必须构建自己的解决方案?

如果你想要一个丑陋的解决方案,你可以使用JavaScript/jQuery来覆盖事件监听器,对吧? - yardpenalty.com
@yardpenalty - 这个问题是关于C#和WPF的。 - Markus
正确,但是.NET/C#的WPF库在某些情况下(你的情况)会在框架的表示层创建JavaScript,所以你提出问题的方式就像你需要一个快速解决方案。 - yardpenalty.com
@yardpenalty - 好的,我更新了问题以澄清我有一个纯Windows桌面WPF应用程序... - Markus
@yardpenalty - 抱歉,不行,因为创建一个新的依赖属性并绑定到该属性会导致相同的问题。如果在编辑时从后台线程更改新属性,则会出现相同的问题。 - Markus
显示剩余3条评论
1个回答

0

我认为需要一个自定义控件来实现您描述的行为。通过重写默认的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 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>

感谢您的时间! - Markus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接