我试图创建一个MarkupExtension:
这样做的原因是我希望能够使用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的作用是:
问题在于,当我致电_templatedParent.ContentTemplate.FindName(ElementName, _templatedParent);
时,我收到了错误
This operation is valid only on elements that have this template applied.
现在,我知道这个错误,它出现在这里是没有意义的,因为此时模板必须已经加载了...如果它没有,那么ProvideValue就不会这样做。 t被称为第二次。
正如您所看到的,我也尝试使用Dispatcher.BeginInvoke
来调用DispatcherPriority.Loaded
,但这并没有奏效。
请帮忙: - \
答案 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
。
这是我的跑步结果:
这是我的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>