【问题标题】:In WPF, how can I have binding on LostFocus but validation on PropertyChanged?在 WPF 中,如何在 LostFocus 上进行绑定但在 PropertyChanged 上进行验证?
【发布时间】:2021-03-24 12:33:00
【问题描述】:

我希望能够绑定一个将 UpdateSourceTrigger 设置为 LostFocus(默认值)的 TextBox,但在用户键入文本时执行验证。我能想到的最好办法是处理 TextChanged 事件并在视图模型上调用验证方法。我想知道是否有更好的解决方案。

我的视图模型侦听模型中的属性更改以更新自身(包括格式化)。我不想将 UpdateSourceTrigger 设置为 PropertyChanged 进行绑定,因为这会导致在用户键入后立即格式化文本(例如,用户可能希望在键入“1”后立即键入“1.2” " 由于视图模型的自动格式化,文本变为 "1.0")。

【问题讨论】:

  • 保持UpdateSourceTrigger = PropertyChanged,并添加Delay = 300(毫秒)
  • @ASh 从可用性的角度来看,这不是很尴尬吗?用户可能会输入“1”,如果他们没有足够快地输入“.2”,他们会看到文本变为“1.0”,我认为这会很烦人。此外,验证不会立即进行,但也会延迟(因为它发生在绑定时)。
  • IMO,验证被延迟是一个很好的副作用(当我仍在输入时,我个人不喜欢红色边框/感叹号)。也许使用 StringFormat 进行格式化?
  • 请问您申请的是哪种验证?一般来说,有这样的要求对我来说似乎有点奇怪。让视图模型控制文本的格式并不好。这个逻辑应该在控件中实现。从数据的角度来看,数字 1 被视为 1 还是 1.0 并不重要。它总是1。在你的情况下,它是关于演示的。您想将 1 显示为 1.0。这完全独立于向模型推送 1 的视图模型。显然,它甚至独立于验证,证明小数要求对于数据有效不是强制性的
  • 所以要强制十进制数字,您应该覆盖UIElement.OnLostFocus 或处理元素的路由事件LostFocus 以在输入完成后应用格式规则。让视图模型规范化数据以供呈现是很好的:当从模型中读取 1 时,视图模型会在将数据暴露给视图之前将其规范化为 1.0。但是视图模型不应该直接调整用户输入。您应该将这种装饰委托给收集输入并了解相关输入事件的控件。这也可以解决您的问题。

标签: c# wpf data-binding


【解决方案1】:

详细说明我留下的评论,这是一个如何完成的示例。

仅供参考,我使用 nuget 包 MvvmLight 进行管道。

MainWindow.xaml

<StackPanel>
    <TextBox x:Name="myTextBox" Text="{Binding SomeNumberViewModel.IntermediateText, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Width="100" Margin="5"/>
    <Button Content="Hi" HorizontalAlignment="Center" Padding="5,15" Margin="5"/>
</StackPanel>

MainWindow.xaml.cs

public MainViewModel ViewModel
{
    get
    {
        return this.DataContext as MainViewModel;
    }
}
public MainWindow()
{
    InitializeComponent();
    this.myTextBox.LostFocus += MyTextBox_LostFocus;
}

private void MyTextBox_LostFocus(object sender, RoutedEventArgs e)
{
    // If the IntermediateText has no validation errors, then update your model.
    if (string.IsNullOrEmpty(this.ViewModel.SomeNumberViewModel[nameof(this.ViewModel.SomeNumberViewModel.IntermediateText)]))
    {
        // Update your model and it gets formatted result
        this.ViewModel.SomeNumberViewModel.ModelValue = this.ViewModel.SomeNumberViewModel.IntermediateText;

        // Then, update your IntermediateText to update the UI.
        this.ViewModel.SomeNumberViewModel.IntermediateText = this.ViewModel.SomeNumberViewModel.ModelValue;
    }
}

MainViewModel.cs

public class MainViewModel : ViewModelBase
{
    private SomeNumberViewModel someNumberViewModel;

    public string MyTitle { get => "Stack Overflow Question 65279367"; }

    public SomeNumberViewModel SomeNumberViewModel
    {
        get
        {
            if (this.someNumberViewModel == null)
                this.someNumberViewModel = new SomeNumberViewModel(new MyModel());
            return this.someNumberViewModel;
        }
    }
}

SomeNumberViewModel.cs

public class SomeNumberViewModel : ViewModelBase, IDataErrorInfo
{
    public SomeNumberViewModel(MyModel model)
    {
        this.Model = model;
    }

    private string intermediateText;
    public string IntermediateText { get => this.intermediateText; set { this.intermediateText = value; RaisePropertyChanged(); } }
    public string ModelValue 
    { 
        get => this.Model.SomeNumber.ToString("0.00"); 
        
        set 
        {
            try
            {
                this.Model.SomeNumber = Convert.ToDouble(value);
                RaisePropertyChanged();
            }
            catch
            {
            }
        } 
    }

    public MyModel Model { get; private set; }
    
    public string Error { get => null; }

    public string this[string columnName]
    {
        get
        {
            switch (columnName)
            {
                case "IntermediateText":
                    if (!string.IsNullOrEmpty(this.IntermediateText) && FormatErrors(this.IntermediateText))
                        return "Format errors";
                    break;
            }

            return string.Empty;
        }
    }

    /// <summary>
    /// Only allow numbers to be \d+, or \d+\.\d+
    /// For Example: 1, 1.0, 11.23, etc.
    /// Anything else is a format violation.
    /// </summary>
    /// <param name="numberText"></param>
    /// <returns></returns>
    private bool FormatErrors(string numberText)
    {
        var valid = (Regex.IsMatch(numberText, @"^(\d+|\d+\.\d+)$"));
        return !valid;
    }
}

MyModel.cs

public class MyModel
{
    public double SomeNumber { get; set; }
}

【讨论】:

  • 我认为这是一个很好的解决方案,除了我会尝试使逻辑更具可重用性,以便可以将其应用于其他TextBox而无需重复代码。
  • @redcurry 明白了。这是第一步的解决方案,而不是稳健的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-19
  • 1970-01-01
  • 2019-09-05
  • 2011-12-30
  • 2018-02-09
  • 2020-04-02
相关资源
最近更新 更多