WPF:ContentTemplate.FindName返回"此操作仅对已应用此模板的元素有效且#34; - 即使模板已加载

时间:2018-05-29 11:05:09

标签: wpf

我试图创建一个MarkupExtension:

  1. 找到目标对象所在的DataTemplate / ContentTemplate。
  2. 在同一模板中查找另一个对象。
  3. 将目标对象上的属性绑定到该模板中找到的对象上的属性。
  4. 这样做的原因是我希望能够使用ElementName来绑定DataTemplates中的源代码,这通常是不可能的。

    我写了下面的MarkupExtension(注意:这是一个快速的第一个版本,我只是想让它起作用,并且在这一点上并不关心优雅或效率):

    using System;
    using System.Diagnostics;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Markup;
    using System.Windows.Threading;
    
    namespace Speedocs.WPF.MarkupExtensions
    {
        public sealed class DataTemplateElementBinding : MarkupExtension
        {
            #region fields
    
            private FrameworkElement _targetObject;
            private DependencyProperty _targetProperty;
            private ContentPresenter _templatedParent;
    
            #endregion
    
            #region properties
    
            public string ElementName { get; set; }
    
            public string Path { get; set; }
    
            #endregion
    
            #region Overrides of MarkupExtension
    
            public override object ProvideValue(IServiceProvider serviceProvider)
            {
                var target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
                if (target != null)
                {
                    if (target.TargetObject.GetType().Name == "SharedDp") return this;
    
                    _targetObject = target.TargetObject as FrameworkElement;
                    if (_targetObject == null)
                    {
                        return null;
                    }
    
                    _targetProperty = target.TargetProperty as DependencyProperty;
                    if (_targetObject == null)
                    {
                        return null;
                    }
    
                    // now that the target object has been loaded, find the requsted element
                    // in the DataTemplate that contains this object, and bind the requested property
                    // to that element
                    _targetObject.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, (Action) (() =>
                    {
                        _templatedParent = _targetObject.TemplatedParent as ContentPresenter;
                        if (_templatedParent == null) return;
    
                        var sourceObject =
                            _templatedParent.ContentTemplate.FindName(ElementName, _templatedParent);
                        var binding = new Binding(Path) {Source = sourceObject};
                        _targetObject.SetBinding(_targetProperty, binding);
                    }));
                }
    
                return null;
            }
    
            #endregion
        }
    }
    

    这个MarkupExtension的作用是:

    1. 如果从IProvideValueTarget返回的TargetObject是SharedDp,则返回MarkupExtension本身,以便在目标值加载时再次调用它。
    2. 加载模板后,将再次调用ProvideValue。
    3. 然后我们从目标对象获取TemplatedParent,使用FindName在模板中查找源对象,并绑定到它。
    4. 问题在于,当我致电_templatedParent.ContentTemplate.FindName(ElementName, _templatedParent);时,我收到了错误

      This operation is valid only on elements that have this template applied.
      

      现在,我知道这个错误,它出现在这里是没有意义的,因为此时模板必须已经加载了...如果它没有,那么ProvideValue就不会这样做。 t被称为第二次。

      正如您所看到的,我也尝试使用Dispatcher.BeginInvoke来调用DispatcherPriority.Loaded,但这并没有奏效。

      请帮忙: - \

1 个答案:

答案 0 :(得分:0)

您只需BeginInvoke Loaded优先级中的操作,但这与Framework.Loaded事件不同。 MarkupExtension会在分配给属性时立即评估其值,而不仅仅是通常的Control,还有Template

如果您的代码在this中运行,则需要返回Template,以便您的实际代码可以在实际控件中运行。只需添加以下条件:

if (!(serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget service))
{
    return null;
}
// Return this to provide lazy value when it is running in a template.
if (service.TargetObject.GetType().Name.EndsWith("SharedDp"))
{
    return this;
}

实际上,TargetObject类型的全名是System.Windows.SharedDp。但它是一个内部类型,因此可能会移动到另一个名称空间。我建议不要使用全名。

BeginInvoke之前添加此条件,它会对您有所帮助。此外,如果添加上述条件,您会发现可以安全地删除BeginInvoke

这是我的跑步结果:

FindName succeeded

这是我的XAML测试代码(我粘贴了您的MarkupExtension并且没有更改):

<ListView>
    <ListView.ItemTemplate>
        <DataTemplate DataType="system:String">
            <Grid Name="rootGrid">
                <TextBlock Text="{local:DataTemplateElementBinding ElementName=rootGrid, Path=Background}" />
            </Grid>
        </DataTemplate>
    </ListView.ItemTemplate>
    xyz
</ListView>