在代码中创建控件模板的可视树

6
这是对之前问题的追问,但并没有让我得到答案: WPF中确定性和异步字段验证 由于WPF似乎不支持INotifyDataErrorInfo,所以我需要自己实现类似功能(如有错误请纠正)。我需要这个功能是因为我想让ViewModel触发特定字段的ErrorTemplates显示(例如在按钮点击后、长时间运行的异步验证操作结束后或者在内部状态发生变化导致特定字段突然无效时)。
我在考虑为此编写自定义标记扩展或行为。它监听由ViewModel实现的我的版本的INotifyDataErrorInfo,并在引发ErrorsChanged事件后从XAML中特定的预定义ErrorTemplate创建VisualTree 。
一旦我在XAML中定义了该模板,我该如何从我的行为/表达式中找到它,将其实例化为实际的可视树,然后在我的表单上正确的字段输入位置显示它(可能是在装饰层上)?

可能是 WPF 中异步字段验证的重复问题 - jonathanpeppers
你尝试过使用bindinggroups吗?因为它们可以用于每个表单的验证。从文档中可以看到,BindingGroups允许触发验证,但我自己从未使用过。另一个链接BindingGroups validation - dowhilefor
如果我理解正确的话,我需要在我的ViewModel中引用BindingGroup来显式触发验证。 - bitbonk
或者你可以创建一个ICommand,将其称为ValidateCommand,并将其作为DP放入你的控件/表单中,并将其绑定到你的ViewModel。这个命令的实现在你的控件/表单中。在ViewModel中,你现在只需要调用这个绑定命令上的Execute方法来触发验证。这样,你的ViewModel就不需要知道BindingGroup的存在。就像我说的,以前从未使用过它们,但我猜这应该是我会做的方式。我尽量让WPF的东西尽可能远离我的VM。 - dowhilefor
我该如何在命令中触发验证,以便WPF显示ErrorTemplate(或在验证时隐藏它)? - bitbonk
2个回答

6
您不需要标记扩展。最近我也希望有相同的行为,所以我创建了一个适合我的需求的解决方案。希望这也能帮到您。
实际上,IDataErrorInfo 接口包含了我们进行异步信号传递所需的一切。它缺少的是一个自动触发通知的事件系统。该接口与 INotifyPropertyChanged 接口之间存在关系。两者的组合实际上允许您间接地发出更改信号。
首先是控件:
<TextBox
    Grid.Column="1"
    Width="100"
    Text="{Binding UpdateSourceTrigger=LostFocus,
        Path=Id,
        ValidatesOnDataErrors=true}" />

很简单。 UpdateSourceTrigger 的值不重要,而且不需要 NotifyOnValidationError,但如果添加它,也不会有任何问题。
接下来是视图模型,这只是一个人为制造的示例。重要部分在于 IDataErrorInfo 索引器。
public class WindowViewModel : INotifyPropertyChanged, IDataErrorInfo
{
    public event PropertyChangedEventHandler PropertyChanged;

    private int _id;
    public int Id
    {
        get{ return _id; }
        set
        {
            _id = value;
            this.PropertyChanged( this, new PropertyChangedEventArgs( "Id" ) );
        }
    }

    public string this[ string columnName ]
    {
        get
        {
            object result = AsynchValidationCoordinator.GetError( columnName );
            if ( result != null )
            {
                return result.ToString();
            }
            return null;
        }
    }

AsynchValidationCoordinator 是一个跟踪属性及其相关错误信息的类。为了说明问题,使用的关键字只是属性名称,但在具有多个视图模型的情况下,您可以轻松创建复合关键字来区分潜在的属性冲突。

public static class AsynchValidationCoordinator
{
    private static readonly ConcurrentDictionary<string, object> ErrorList = 
        new ConcurrentDictionary<string, object>();

    public static void CancelError( string propertyName, object error )
    {
        object value;
        ErrorList.TryRemove( propertyName, out value );
    }

    public static object GetError( string propertyName )
    {
        object error = null;
        if ( ErrorList.ContainsKey( propertyName ) )
        {
            ErrorList.TryRemove( propertyName, out error );
        }
        return error;
    }

    public static void RegisterError( string propertyName, object error )
    {
        ErrorList[propertyName] = error;
    }
}

跟踪属性名称是必要的,但您可以创建完全不同的跟踪方式,包括跟踪视图模型中的名称。这只是我快速将结构化表单应用于现有项目的简单方法。
因此,将所有这些绑定在一起,我向测试视图模型添加了以下属性,并将其绑定到按钮上。(来自Josh Smith的MSDN MVVM文章。)
public ICommand ValidateCommand
{
    get
    {
        return new RelayCommand( Validate );
    }
}

private void Validate( object value )
{
    Thread thread = new Thread( RaiseChanged );
    thread.Start();
}

private void RaiseChanged()
{
    Thread.Sleep( 3000 );
    AsynchValidationCoordinator.RegisterError( "Id", "Error Message Goes Here" );
    this.PropertyChanged( this, new PropertyChangedEventArgs( "Id" ) );
}

调用的源始无关紧要。将所有内容联系在一起的重要一点是,一旦调用PropertyChangedIDataErrorInfo索引器就会跟随其轨迹。返回在AsynchValidationCoordinator中注册的错误信息将触发控件的Validation.ErrorTemplate,显示相关的错误消息。

对于将 IDataErrorInfoINotifyPropertyChanged 绑定的想法,我给予加一。我之前没有想到过这个。如果我理解正确的话,您只需要在想要显示或移除验证错误以强制 WPF 重新评估 public string this[string columnName] 中的验证时触发 额外的 PropertyChanged 事件。是这样吗? - bitbonk
这种方法可能有点不太正式,但它可能仍然比自己编写MarkupExtension和ErrorTemplate实现要好。我想WPF vNext将从Sliverlight带来INotifyDataErrorInfo:http://connect.microsoft.com/VisualStudio/feedback/details/568212/inotifydataerrorinfo-for-wpf - bitbonk
外部协调器类可能会让人感觉到这种方式。至少对我来说是这样的。我实际上修改了基本视图模型类,以跟踪视图模型内的错误,而不是依赖于外部集合。它让我更接近INotifyDataErrorInfo的实现,稍加努力,我可能可以滚动一个几乎完全匹配语法的模型,尽管在vNext之前,仍需要IDataErrorInfo - Paul Walls

0

是的,一旦WPF 4.5可用(2012年?),这将是一个选项。不幸的是我等不了那么久。 - bitbonk
@bitbonk:我只想指出,开发者预览版中已经可以使用,对于你和任何遇到这个问题的人都适用。而且当它正式发布时,它不仅仅是一个选择,而且应该使用,因为它将被完全实现。 - Fredrik Hedblad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接