2010-03-08 90 views
3

我有它运行作为一个excel插件WPF应用程序,它有它的可视树,像这样拦截的RelativeSource FindAncestor

  • Excel中
    • ElementHost的
      • WPF用户控件
        • WPF色带条控件

现在,当该插件在Excel加载任何坐在WPF带状条控制控件未启用。看到下面的错误

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调用窗口并将其连接到其他东西。请注意,我无法更改上述绑定,因为它不是我的控制。

+0

通过遍历树并使用'BindingOperations.GetBinding'获取绑定,然后更改绑定的RelativeSource属性,您是否可以在运行时获得Ribbon控件? – 2010-04-01 09:33:43

+0

嗨Gp,我应该检索绑定的属性应该是什么..我不知道控件的哪个属性尝试绑定。 “目标属性是'NoTarget'GetBinding期望指定依赖项属性 – Pradeep 2010-04-01 14:31:52

回答

0

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

+0

我已经找到了上面给出的错误信息中存在问题的控件,控件是Ribbon,但我不知道控件的哪个依赖项属性有这个失败的绑定表达式 – Pradeep 2010-04-06 08:59:08

+0

使用Snoop,也重新读取输出窗口中的绑定错误消息 - 您应该在那里找到依赖项属性名称 – 2010-04-06 18:44:40

+0

如错误消息中所述,依赖项属性名称不可用。无法监听托管在Excel中的托管内容 – Pradeep 2010-04-07 09:21:07

0

另一种选择是添加一个自定义控件作为祖先继承自Window,然后将其绑定到Excel控件。

+0

这不起作用,因为所有控件都将ElementHost作为父项,并且无法将一个窗口添加到ElementHost。 – Pradeep 2010-04-06 08:57:48

2

最直接的答案

FindAncestor由WPF内部处理和将才去其他任何地方,只要它可以搜索了可视化树。只有当它到达没有视觉父母的Visual时,它才会在其他地方搜索,这取决于它到达的内容。例如,如果它碰到一个FrameworkContentElement,它可以进入文档的容器。不幸的是,如果可视树的顶部是一个ElementHost,它将停止,因此无法重新路由该呼叫。

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

如何自动替换结合

这里有一个简单的方法,我写了一段时间后,通过视觉树搜索与指示由updateFunction取代绑定。如果updateFunction返回与传递不同的绑定,则会更新绑定。

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实例特定AncestorType的方法,具体如下:

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方法可以使用这样的事情:

elementHost.LayoutUpdated += (obj, e) => 
{ 
    ReplaceFindAncestorType(elementHost, typeof(Window), typeof(ElementHost); 
}; 

你的情况,这个通用的替代技术是行不通的:它会找你的ElementHost的,一个isActive属性,它不存在。所以你可能需要改变的不仅仅是RelativeSource。这意味着你的实际代码会更喜欢这样的:

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:窗口结合就是我们要找的人。条件中可以根据需要添加更多条件。

替代解决方案

还有另外一个完全不同的,解决方案可供选择:这是可能的实际举办的一个无国界的窗口的内容和添加自定义代码,因此它似乎把这个窗口定位在ElementHost的在另一个窗口内。这比听起来要复杂一些,因为你必须处理诸如ActiveWindow,ForegroundWindow,Z Order,Minimized状态,键盘焦点等等。但是如果你的需求非常简单,这可能是一个合理的解决方案。