将 WPF TextBox 与 URI 转换器一起使用,无效输入会擦除文本框
我的表单上有一个 WPF 文本框,允许输入 URI。
我尝试使用数据转换器来做到这一点。问题是,当文本框绑定更新并且文本框不包含有效的 URI 时,
- 数据转换器返回 null ;
- 这将我的模型属性设置为 null ;
- 这会导致属性更改事件触发;
- 它将文本框值设置为空字符串,清除用户无效输入
我是 WPF 新手,我无法找到使用不会导致此行为的数据转换器的简单模式。我认为必须有一个标准模式可供使用,如果我是一名经验丰富的 WPF 程序员,我就会知道该模式。
查看 Prism 4 中包含的示例,似乎使用了两种不同的方法。我不喜欢他们两个。
第一种方法是当我的模型属性设置为 null 时抛出异常,该异常被捕获并显示为验证错误。问题是我希望该属性能够设置为 null - 每次打开表单时,字段都会设置为其之前的值。如果应用程序以前从未运行过,则 URI 将设置为 null - 这不应引发异常。此外,使用异常进行验证是丑陋的。
第二种方法是当属性设置为 null 时,设置模型的验证状态以包括属性无效,但实际上不更新属性。我认为这很糟糕。它导致模型内部不一致,声称 DCSUri 无效,但包含 DCSUri 之前的有效值。
我用来避免这些问题的方法是在我的 ViewModel 中包含一个字符串 DCSUri,它仅更新我的模型的 Uri 类型的 DCSUri 属性(如果它是有效的 URI)。但我更喜欢一种允许使用转换器并将文本框直接绑定到模型的方法。
我的转换器代码:
/// <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);
}
}
}
I have a WPF textbox on a form to allow input of a URI.
I tried to do this using a data converter. The problem is that when the textbox binding updates and the textbox doesn't contain a valid URI,
- The data converter returns a null ;
- which sets my model property to null ;
- which results in a property changed event firing ;
- which sets the text box value to the empty string, wiping out the users invalid input
I'm a WPF novice, and I'm at a loss to find a simple pattern using a data converter that doesn't result in this behaviour. I'm thinking there must be a standard pattern to use, that I'd know about if I was an experienced WPF programmer.
Looking at the examples included with Prism 4, there seems to be two different approaches used. I dislike them both.
The first approach is to throw an exception when my model property is set null, which is caught and shown as a validation error. The problem is that I want the property to be able to be set to null - each time you open the form, the fields are set to their previous values. If the application has never been ran before, the URI will be set to null - this shouldn't throw an exception. Also, the use of exceptions for validation is ugly.
The second approach is when the property is set to null, set the validation state of the model to include the property invalidity, but don't actually update the property. Which I think is awful. It results in the model being internally inconsistent, claiming that the DCSUri is invalid, but containing the previous valid value of DCSUri.
The approach I'm using to avoid these issues is to have a string DCSUri in my ViewModel, which only updates the Uri typed DCSUri property of my Model if it is a valid URI. But I'd prefer an approach which allows use of a converter and binding my textbox directly to my model.
My converter code:
/// <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;
}
}
The XAML for the textbox:
<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" />
And the code for the DCSUri property within my model:
/// <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);
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您应该使用 ValidationRules 进行验证,并以正确的方式命名您的转换器;我会这样处理它(假设您希望能够将 Uri 设置为
null
):然后像这样使用它(或者通过在某处将转换器和规则定义为资源):
如果输入文本未通过验证,转换器将不会被调用,这就是为什么我没有
TryCreate
或类似的内容。CodeProject 上有一篇关于输入验证的不错的文章,您可能会发现它有帮助。
要测试 null 值,您可以使用另一个转换器和助手 TextBlock:
You should use ValidationRules for Validation, and name your converters the right way around; i would approach it like this (assuming that you want to be able to set the Uri to
null
):Then use it like this (or by defining the converter and rule as a resource somewhere):
If the input text does not pass validation the Converter will not be called, that is why i have no
TryCreate
or anything like that in there.There is a decent article about input validation on CodeProject which you might find to be helpful.
To test the value for null you could use another converter and a helper TextBlock: