首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >拦截RelativeSource FindAncestor

拦截RelativeSource FindAncestor
EN

Stack Overflow用户
提问于 2010-03-08 15:15:20
回答 3查看 1.8K关注 0票数 3

我有一个作为excel插件运行的WPF应用程序,它有如下所示的可视树

  • Excel
    • ElementHost
      • WPF UserControl
        • WPF带状条控制

现在,当插件加载到excel中时,WPF带状条控件上的任何控件都不会启用。见下面的错误

代码语言:javascript
复制
System.Windows.Data Error: 4 : Cannot find source for binding with 
reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=IsActive; DataItem=null; target element 
is 'Ribbon' (Name=''); target property is 'NoTarget' (type 'Object')

如果我将丝带条控件嵌套在一个独立窗口(excel外),它可以工作得很好。

有没有办法拦截窗口的FindAncestor调用,并将其连接到其他东西上?请注意,我不能更改上述绑定,因为它不是我的控制。

EN

回答 3

Stack Overflow用户

发布于 2010-04-07 04:59:53

最直接的答案

FindAncestor是由WPF在内部处理的,它将尽可能地搜索可视树,然后再去其他任何地方。只有当它到达没有可视父级的Visual时,它才会在其他地方搜索,这取决于它所达到的目标。例如,如果它击中了一个FrameworkContentElement,它可以转到文档的容器。不幸的是,如果可视化树的顶部是一个ElementHost,它将停止,因此无法重新路由调用。

这意味着您最简单的选择是替换绑定。幸运的是,这并不是很困难。

如何自动替换绑定

下面是我不久前编写的一个简单方法,它通过可视化树进行搜索,并按照updateFunction的指示替换绑定。如果updateFunction返回的绑定与传递的绑定不同,则将更新绑定。

代码语言:javascript
复制
static void UpdateBindings(Visual visual, Func<Binding, Binding> updateFunction)
{
  if(visual==null) return;
  for(int i=0; i<VisualTreeHelper.GetChildrenCount(visual); i++)
    UpdateBindings(VisualTreeHelper.GetChild(visual, i) as Visual, updateFunction);
  for(var enumerator = visual.GetLocalValueEnumerator(); enumerator.MoveNext(); )
  {
    var property = enumerator.Current.Property;
    var binding = BindingOperations.GetBinding(visual, property);
    if(binding==null) continue;
    var newBinding = updateFunction(binding);
    if(newBinding!=binding)
      BindingOperations.SetBinding(visual, property, newBinding);
  }
}

为了说明这是如何工作的,下面是如何编写一个方法来替换所有RelativeSource FindAncestor实例中的特定FindAncestor,如下所示:

代码语言:javascript
复制
static void ReplaceFindAncestorType(Visual visual, Type fromType, Type toType)
{
  UpdateBindings(visual, binding =>
    binding.RelativeSource.Mode != RelativeSourceMode.FindAncestor ? binding :
    binding.RelativeSource.AncestorType != fromType ? binding :
    new Binding
    {
      RelativeSource = new RelativeSource(
        RelativeSourceMode.FindAncestor,
        toType,
        binding.RelativeSource.AncestorLevel),
      Path = binding.Path,
      Mode = binding.Mode,
      Converter = binding.Converter,
      StringFormat = binding.StringFormat,
      UpdateSourceTrigger = binding.UpdateSourceTrigger,
    });
}

注意,只有常用的属性被复制到新绑定中。

可以使用ReplaceFindAncestorVisualType方法如下所示:

代码语言:javascript
复制
elementHost.LayoutUpdated += (obj, e) =>
{
  ReplaceFindAncestorType(elementHost, typeof(Window), typeof(ElementHost);
};

在您的示例中,这种通用的替换技术不起作用:它将在您的IsActive上寻找一个不存在的ElementHost属性。因此,您可能需要改变的不仅仅是RelativeSource。这意味着您的实际代码将更像这样:

代码语言:javascript
复制
elementHost.LayoutUpdated += (obj, e) =>
{
  UpdateBindings(elementHost, binding =>
    binding.RelativeSource.AncestorType != typeof(Window) ? binding :
    new Binding
    {
      Source = ultimateContainingWindowOrOtherObjectHavingIsActiveProperty,
      Path = new PropertyPath("IsActive"), // Put property name here
    });
};

请注意,上面的代码假设任何FindAncestor:Window绑定都是我们要寻找的。可以根据需要在条件中添加更多条件。

替代解决方案

还有另一种完全不同的解决方案:可以将内容实际保存在无边界窗口中,并添加自定义代码以将此窗口保持在ElementHost之上,因此它似乎位于另一个窗口中。这比听起来更棘手,因为您必须处理诸如ActiveWindow、ForegroundWindow、Z Order、最小化状态、键盘焦点等问题。但是,如果您的需求非常简单,这可能是一个合理的解决方案。

票数 2
EN

Stack Overflow用户

发布于 2010-04-04 22:33:45

当使用Excel中的控件时,祖先中没有窗口,但是,也许您可以使用斯诺普查找绑定的定义位置,然后在运行时找到依赖对象(按类型)并更改其属性的绑定表达式?

票数 0
EN

Stack Overflow用户

发布于 2010-04-04 22:44:05

另一个选项是添加自定义控件,该控件作为祖先从窗口继承,然后将其绑定到Excel控件。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/2402280

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档