перехватить RelativeSource FindAncestor - PullRequest
3 голосов
/ 08 марта 2010

У меня есть приложение WPF, которое запускается как плагин Excel, оно имеет свое визуальное дерево, например,

  • Excel
    • ElementHost
      • WPF UserControl
        • WPF панель управления лентой

Теперь любые элементы управления, расположенные на ленточном элементе управления WPF, не включаются, когда плагин загружается в Excel. Смотрите ошибку ниже

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 для окна и подключить его к чему-то еще? Обратите внимание, что я не могу изменить вышеуказанную привязку, так как это не мой контроль.

Ответы [ 3 ]

2 голосов
/ 07 апреля 2010

Самый прямой ответ

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);
  }
}

Чтобы проиллюстрировать, как это работает, вот как вы можете написать метод, который заменяет определенный AncestorType во всех экземплярах RelativeSource FindAncestor, следующим образом:

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);
};

В вашем случае этот общий метод замены не будет работать: он будет искать свойство IsActive в вашем ElementHost, которого не существует. Поэтому вам, вероятно, нужно изменить больше, чем просто 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, минимизированное состояние, фокусировка клавиатуры и т. Д. Но если ваши потребности очень просты, это может быть разумным решением.

0 голосов
/ 05 апреля 2010

Другой вариант - добавить пользовательский элемент управления, наследуемый от Window, как предка, а затем привязать его к элементу управления Excel.

0 голосов
/ 05 апреля 2010

При использовании элемента управления в Excel нет окна в предке, однако, возможно, вы можете использовать Snoop , чтобы найти, где определена привязка, затем во время выполнения найти объект зависимости (по введите) и измените выражение привязки его свойства?

...