Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/327.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在WPF中,如何对LostFocus进行绑定,而对PropertyChanged进行验证?_C#_Wpf_Data Binding - Fatal编程技术网

C# 在WPF中,如何对LostFocus进行绑定,而对PropertyChanged进行验证?

C# 在WPF中,如何对LostFocus进行绑定,而对PropertyChanged进行验证?,c#,wpf,data-binding,C#,Wpf,Data Binding,我希望能够将UpdateSourceTrigger设置为LostFocus(默认设置)的文本框绑定,但在用户在文本中键入时执行验证。我能想到的最好办法是处理TextChanged事件,并在视图模型上调用验证方法。我想知道是否有更好的解决办法 我的视图模型侦听模型中的属性更改以更新自身(包括格式)。我不想将UpdateSourceRigger设置为PropertyChanged进行绑定,因为这会导致文本在用户键入时立即格式化(例如,用户可能希望键入“1.2”,但在他/她键入“1”时,由于视图模型的

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


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

详细阐述了我留下的评论,下面是一个如何做到这一点的例子

仅供参考,我使用nuget软件包MvvmLight安装管道

main window.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>
MainViewModel.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;
    }
}
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;
        }
    }
}
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;
    }
}
public class MyModel
{
    public double SomeNumber { get; set; }
}
SomeNumberViewModel.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;
    }
}
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;
        }
    }
}
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;
    }
}
public class MyModel
{
    public double SomeNumber { get; set; }
}

保持UpdateSourceTrigger=PropertyChanged,并添加
Delay=300
(它是毫秒)@ASh从可用性的角度来看这不是很尴尬吗?用户可能会键入“1”,如果他们输入“.2”的速度不够快,他们会看到文本变为“1.0”,我认为这会很烦人。此外,验证不会立即进行,但也会延迟(因为它发生在绑定时)。在我看来,延迟验证是一个很好的副作用(我个人不喜欢在仍然键入时使用红色边框/感叹号)。可能使用StringFormat进行格式化?我可以问一下您正在应用哪种验证吗?总的来说,我觉得有这样的要求有点奇怪。让视图模型控制文本的格式是不好的。此逻辑应在控件中实现。从数据的角度来看,数字1被视为1或1.0并不重要。总是1。在你的情况下,这是关于展示。您希望将1显示为1.0。这完全独立于将1推送到模型的视图模型。很明显,它甚至独立于验证,验证证明十进制要求对于数据的有效性不是强制性的。因此,要强制十进制数字,您应该覆盖
UIElement。OnLostFocus
或处理元素的路由事件
LostFocus
,以便在输入完成后应用格式规则。让视图模型规范化用于表示的数据是可以的:当从模型中读取1时,视图模型会在将数据公开给视图之前将其规范化为1.0。但是视图模型不应该直接调整用户输入。您应该将此类化妆品委托给收集输入并了解相关输入事件的控件。这也可以解决您的问题。我认为这是一个很好的解决方案,不过我会尝试使逻辑更可重用,这样它就可以应用于其他文本框,而无需重复代码。@redcurry明白了。这是第一步的解决方案,不是一个稳健的解决方案。