【问题标题】:C# - WPF - Prevent an update of a bound focused TextBoxC# - WPF - 防止更新绑定的焦点文本框
【发布时间】:2017-01-06 21:11:05
【问题描述】:

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

是否有一种内置方法可以防止在 TextBox 聚焦时发生更改?还是我必须构建自己的解决方案?

【问题讨论】:

  • 如果你想要一个丑陋的修复,你可以使用 javascript/jQuery 覆盖 eventListener 对吗?
  • @yardpenalty - 问题是针对 C# 和 WPF
  • 是的,但是用于 WPF 的 .NET/C# 库在某些情况下(您的)会在框架的表示层中创建 javascript,因此您提出问题的方式就像您需要快速修复一样。
  • @yardpenalty - 好的,我更新了问题以明确我有一个普通的 Windows 桌面 WPF 应用程序...
  • NP,我很确定这正是您需要做的。覆盖默认行为。如果需要,在调试中进行一些重构。 stackoverflow.com/a/27973861/1870110

标签: c# wpf data-binding


【解决方案1】:

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

无论我们的文本框如何更新(键盘事件和视图模型更改),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>

【讨论】:

  • 感谢您的宝贵时间!
猜你喜欢
  • 2013-01-31
  • 1970-01-01
  • 2011-02-08
  • 2011-09-22
  • 1970-01-01
  • 1970-01-01
  • 2012-08-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多