【问题标题】:Using WPF TextBox with a URI converter, invalid input wipes textbox使用带有 URI 转换器的 WPF 文本框,无效的输入擦除文本框
【发布时间】:2015-07-14 18:39:46
【问题描述】:

我在表单上有一个 WPF 文本框,允许输入 URI。

我尝试使用数据转换器来做到这一点。问题是当文本框绑定更新并且文本框不包含有效的 URI 时,

  • 数据转换器返回空值;
  • 将我的模型属性设置为 null ;
  • 这会导致属性更改事件触发;
  • 将文本框的值设置为空字符串,清除用户的无效输入

我是 WPF 新手,我找不到使用不会导致这种行为的数据转换器的简单模式。我想必须有一个标准模式可以使用,如果我是一位经验丰富的 WPF 程序员,我会知道的。

查看 Prism 4 中包含的示例,似乎使用了两种不同的方法。我都不喜欢他们俩。

第一种方法是在我的模型属性设置为 null 时引发异常,这会被捕获并显示为验证错误。问题是我希望该属性能够设置为 null - 每次打开表单时,这些字段都设置为它们以前的值。如果应用程序以前从未运行过,则 URI 将设置为 null - 这不应引发异常。此外,使用异常进行验证也很丑陋。

第二种方法是当属性设置为 null 时,将模型的验证状态设置为包含属性无效,但不实际更新属性。我认为这很糟糕。导致模型内部不一致,声称DCSUri无效,但包含DCSUri之前的有效值。

我用来避免这些问题的方法是在我的 ViewModel 中有一个字符串 DCSUri,如果它是一个有效的 URI,它只会更新我的模型的 Uri 类型 DCSUri 属性。但我更喜欢一种允许使用转换器并将我的文本框直接绑定到我的模型的方法。

我的转换器代码:

/// <summary>
/// Converter from Uri to a string and vice versa.
/// </summary>
public class StringToUriConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Uri uri = null;
        string stringValue = value as string;
        if (stringValue != null)
            Uri.TryCreate(stringValue, UriKind.Absolute, out uri);
        return uri;
    }
}

文本框的 XAML:

    <TextBox Grid.Row="1" Grid.Column="1" Name="DCSUriTextBox" 
             Text="{Binding Path=DCSLoadSettings.DCSUri, Mode=TwoWay, UpdateSourceTrigger=LostFocus, ValidatesOnExceptions=True, NotifyOnValidationError=True, ValidatesOnDataErrors=True, Converter={StaticResource StringToUriConverter} }" 
             HorizontalAlignment="Stretch" Height="Auto" VerticalAlignment="Center" Margin="5,0,20,0" IsReadOnly="{Binding Path=IsNotReady}" Grid.ColumnSpan="2" />

以及我模型中 DCSUri 属性的代码:

    /// <summary>
    /// The Uri of the DCS instance being provided configuration
    /// </summary>
    public Uri DCSUri
    {
        get
        {
            return mDCSUri;
        }
        set
        {
            if (!Equals(value, mDCSUri))
            {
                mDCSUri = value;
                this["DCSUri"] = value == null
                    ? "Must provide a Uri for the DCS instance being provided configuration"
                    : string.Empty;
                RaisePropertyChanged(() => DCSUri);
            }
        }
    }

【问题讨论】:

    标签: wpf binding converter


    【解决方案1】:

    您应该使用 ValidationRules 进行验证,并以正确的方式命名您的转换器;我会这样处理(假设您希望能够将 Uri 设置为null):

    public class UriToStringConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            Uri input = value as Uri;
            return input == null ?
                String.Empty : input.ToString();
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string input = value as string;
            return String.IsNullOrEmpty(input) ?
                null : new Uri(input, UriKind.Absolute);
        }
    }
    
    public class UriValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
        {
            string input = value as string;
            if (String.IsNullOrEmpty(input)) // Valid input, converts to null.
            {
                return new ValidationResult(true, null);
            }
            Uri outUri;
            if (Uri.TryCreate(input, UriKind.Absolute, out outUri))
            {
                return new ValidationResult(true, null);
            }
            else
            {
                return new ValidationResult(false, "String is not a valid URI");
            }
        }
    }
    

    然后像这样使用它(或者通过将转换器和规则定义为某处的资源):

    <TextBox MinWidth="100">
        <TextBox.Text>
            <Binding Path="Uri">
                <Binding.ValidationRules>
                    <vr:UriValidationRule />
                </Binding.ValidationRules>
                <Binding.Converter>
                    <vc:UriToStringConverter/>
                </Binding.Converter>
            </Binding>
        </TextBox.Text>
    </TextBox>
    

    如果输入文本未通过验证,则不会调用 Converter,这就是为什么我没有 TryCreate 或类似的东西。

    CodeProject 上有a decent article about input validation,您可能会发现它很有帮助。


    要测试 null 的值,您可以使用另一个转换器和一个助手 TextBlock:

    public class NullToStringConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return value == null ?
                "NULL" : value;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
    
    <TextBlock>
        <TextBlock.Text>
            <Binding Path="Uri">
                <Binding.Converter>
                    <vc:NullToStringConverter/>
                </Binding.Converter>
            </Binding>
        </TextBlock.Text>
    </TextBlock>
    

    【讨论】:

    • 谢谢 HB... 我必须承认我有时会发现 Microsoft 示例的状态令人讨厌,因为我一直在使用的实践直接来自 Prism 中包含的参考实现。他们对 ValidationRules 等只字未提,并鼓励您实现 IDataErrorInfo 或 INotifyDataErrorInfo。 IDataErrorInfo/INotifyDataErrorInfo 是否有任何位置,或者是否应该使用 ValidationRules 专门进行验证?
    • 我无法回答这个问题,因为我没有使用这些界面的经验,抱歉。从 MSDN 参考资料来看,IDataErrorInfo 似乎很古老,而 ValiationRule 只有参考版本回到 .NET 3.0,所以我怀疑 ValidationRules 是首选,特别是因为它已集成到 Binding 引擎中。
    • stackoverflow.com/questions/2980853/… 有一个很好的解释。基本上,对模型中的错误使用 IDataErrorInfo,对视图中的错误使用 ValidationRule。就我而言,无效的 Uri 是视图中的错误,因此您给我的 ValidationRule 实现是合适的。感谢您的帮助!
    猜你喜欢
    • 2020-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多