在代码中根据控件模板创建可视化树

发布于 2024-12-02 19:07:15 字数 750 浏览 2 评论 0原文

这是上一个问题的后续问题,但并没有真正让我得到任何帮助: WPF 中的确定性和异步字段验证

由于 WPF 不支持 < a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifydataerrorinfo%28v=vs.95%29.aspx" rel="nofollow noreferrer">INotifyDataErrorInfo 看来,我需要自己实现类似的东西(如果我在这里错了,请纠正我)。我需要这个,因为我希望 ViewModel 何时触发某些字段显示特殊的 ErrorTemplates(例如,单击按钮后或长时间运行的异步验证操作结束后,或者当内部状态以某些字段突然发生变化的方式发生变化时)失效)。

我正在考虑为此编写一个自定义标记扩展或行为。它侦听由 ViewModel 实现的我的 INotifyDataErrorInfo 版本,并在引发 ErrorsChanged 事件后从 XAML 中定义的特殊众所周知的 ErrorTemplate 创建 VisualTree。

一旦我在 XAML 中定义了该模板,我如何从我的行为/表达式中找到它,从中具体化一个实际的可视化树,然后在我的表单上的正确字段条目中显示它(可能以某种方式在装饰层上)?

This is a follow up question to a previous question, wich didn't really get me anywhere:
deterministic and asynchronous field validation in WPF

Since WPF doesn't support INotifyDataErrorInfo it seems, that I need to implement something like that myself (please correct me if I am wrong here). I need this because I want the ViewModel to trigger when to display special ErrorTemplates for certain fields (e.g. after the click of a button or after the end of a long running async validation operation or when the internal state changes in a way that certain fields suddenly become invalid).

I am considering to write a custom markup extension or behavior for this. It listens to my version of INotifyDataErrorInfo implemented by the ViewModel and creates a VisualTree from a special wellknown ErrorTemplate defined in XAML once the ErrorsChanged event was raised.

Once I have defined that template in XAML, how do I find it from my behavior/expression, materialize an actual visual tree out of it and then display it (probably somehow on an adorner layer) at the right field entry on my form?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

冷清清 2024-12-09 19:07:15

您不需要标记扩展。我最近发现自己希望有同样的行为,所以我创建了一个适合我的需求的解决方案。希望这对您也有帮助。

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;
    }
}

跟踪属性名称是必要的,但您可以创建一种完全不同的跟踪它们的方式,包括跟踪视图模型中的名称。这只是我将结构化表单快速应用到现有项目的一种简单方法。

因此,将所有这些结合在一起,我将以下 ICommand 属性添加到测试视图模型并将其绑定到按钮。 (RelayCommand 来自 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 并显示相关错误消息。

You don't need a markup extension. I recently found myself wishing for the same behavior, so I created a solution that works for my needs. Hopefully this helps you as well.

The IDataErrorInfo interface actually contains everything we need in order to do asynchronous signaling. What it lacks is an event system to trigger notifications automatically. There is a relationship between that interface and the INotifyPropertyChanged interface. The combination of the two actually allows you to signal a change, somewhat indirectly.

First the control:

<TextBox
    Grid.Column="1"
    Width="100"
    Text="{Binding UpdateSourceTrigger=LostFocus,
        Path=Id,
        ValidatesOnDataErrors=true}" />

Pretty straightforward. The value of UpdateSourceTrigger is not important, and NotifyOnValidationError is not required, but won't hurt anything if you add it.

Next, the view model, which is just a contrived example. The important part is in the IDataErrorInfo indexer.

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 is a class that tracks properties and any associated error information. For illustrative purposes, the key being used is just the property name, but you could as easily create a compound key to differentiate potential property collisions in scenarios with multiple view models.

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;
    }
}

Tracking property names is necessary, but you could create an entirely different way of tracking them, including tracking the names within the view model. This was just an easy way for me to apply a structured form quickly to an existing project.

So tying this all together, I added the following ICommand property to the test view model and bound it to a Button. (RelayCommand is from Josh Smith's MSDN MVVM article.)

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" ) );
}

The source of the call is irrelevant. The important point that ties all of this together is the fact that once PropertyChanged is called, the IDataErrorInfo indexer follows in its tracks. Returning the error information that was registered in AsynchValidationCoordinator triggers the Validation.ErrorTemplate of the control with the relevant error message.

抱猫软卧 2024-12-09 19:07:15

INotifyDataErrorInfo 现已与许多其他功能一起包含在 WPF 4.5 中。请参阅以下链接

这里是链接Visual Studio 11 开发者预览版:
http://msdn.microsoft.com/en-us/vstudio/hh127353

INotifyDataErrorInfo is now included in WPF 4.5 along with a lot of other features. See the following links

Here is the link to the Visual Studio 11 Developer Preview:
http://msdn.microsoft.com/en-us/vstudio/hh127353

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文