wpf - WPF:ContentTemplate.FindName 返回“此操作仅对应用了此模板的元素有效” - 即使在模板已加载之后
问题描述
我正在尝试创建一个 MarkupExtension,它将:
- 找到目标对象所在的 DataTemplate/ContentTemplate。
- 在同一模板中查找另一个对象。
- 将目标对象的属性绑定到在该模板中找到的对象的属性。
这样做的原因是我希望能够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 所做的是:
- 如果从 IProvideValueTarget 返回的 TargetObject 是 SharedDp,则返回 MarkupExtension 本身,以便在目标值加载时再次调用它。
- 加载模板时,将再次调用 ProvideValue。
- 然后我们从目标对象中获取 TemplatedParent,使用 FindName 在模板中找到源对象,并绑定到它。
问题是当我打电话时_templatedParent.ContentTemplate.FindName(ElementName, _templatedParent);
我得到了错误
This operation is valid only on elements that have this template applied.
现在,我知道这个错误,它出现在这里没有意义,因为此时模板必须已经加载......如果没有,ProvideValue 就不会被第二次调用。
如您所见,我也尝试Dispatcher.BeginInvoke
使用 using调用DispatcherPriority.Loaded
,但没有奏效。
请帮忙 :-\
解决方案
你只是BeginInvoke
把动作放在一个Loaded
优先级上,但这与Framework.Loaded
事件不一样。AMarkupExtension
将在分配给属性时立即评估其值,不仅是通常Control
的 s,还有Template
.
this
如果您的代码在 a 中运行,则需要返回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>
推荐阅读
- python - 从给定字典中获取数组
- flutter - 在 ListView Flutter 中获取当前可见的小部件
- java - Quartz 重试作业,触发器的延迟时间策略呈指数增加
- firebase - Firestore 中的最大批量删除量
- django - 模板的所见即所得编辑器
- javascript - 使用 javascript 验证多部分表单
- mongodb - Apache NiFi GetMongoProcessor - 不连续生成流文件
- php - 使用 Nginx 服务 PHP 网站不起作用。我的代码或我的设置是否错误?
- java - jOOQ:重用/复制查询
- python - 在本地使用 pyspark 处理大文件