Ошибка при использовании TransformToAncestor: «Указанный визуал не является предком этого визуала». - PullRequest
6 голосов
/ 15 мая 2010

Я пытаюсь получить смещение элемента управления относительно верхней части его окна, но у меня возникают проблемы при использовании метода TransformToAncestor элемента управления. Примечание. Этот код находится в преобразователе значений, который преобразует элемент управления в его относительную позицию Y относительно окна.

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    var ctrl = (Control) value;
    var win = Window.GetWindow(ctrl);
    var transform = ctrl.TransformToAncestor(win); // Exception thrown here.
    var pt = transform.Transform(new Point(0, 0));
    return pt.Y;
}

Вызов Window.GetWindow работает просто отлично и возвращает правильный объект окна, внутри которого находится элемент управления.

Не понимаю ли я, что WPF считает "предком"? Я думаю, что учитывая результат GetWindow, это окно будет предком элемента управления. Существуют ли определенные шаблоны гнездования, которые могут привести к отрезанию линии предков в определенной точке?

UPDATE:

Похоже, это может быть проблема синхронизации. Когда я попытался вызвать метод TransformToAncestor в обработчике событий, а не в конвертере значений, он работал просто отлично. Кажется, что преобразователь значения должен быть запущен, так как определенные элементы создаются до того, как будут установлены отношения предков.

Не уверен, как обойти это, так как я пытаюсь использовать шаблон MVVM (и, следовательно, на самом деле не хочу использовать обработчики событий, и предпочел бы не иметь System.Windows в моем ViewModel). *

1 Ответ

8 голосов
/ 15 мая 2010

Преобразователь вызывается, пока дерево визуалов все еще собирается, поэтому ваш Visual еще не является потомком окна.

Вы хотите выполнить преобразование, когда ваше визуальное дерево уже построено. Это делается путем регистрации обратного вызова Dispatcher с использованием Dispatcher.BeginInvoke(DispatcherPriority.Render, ...) и выполнения вашей работы внутри обратного вызова.

Однако это нельзя использовать с преобразователем, поскольку преобразователь должен немедленно вернуть преобразованное значение. Простой обходной путь - использовать присоединенное свойство вместо конвертера. Вот как:

Предположим, ваша привязка выглядит следующим образом:

<SomeObject Abc="{Binding Xyz, Converter={x:Static my:Converter.Instance}}" />

Создание подкласса DependencyObject «Независимо от», содержащего вложенное свойство «AbcControl», у которого PropertyChangedCallback выполняет преобразование и изменяет свойство «Abc»:

public class AttachedProperties : DependencyObject
{
  public Control GetAbcControl(...
  public void SetAbcControl(...
  ... AbcControlProperty = RegisterAttached("AbcControl", typeof(Control), typeof(AttachedProperties), new PropertyMetadata
  {
    PropertyChangedCallback = (obj, e) =>
    {
      var ctrl = (Control)e.NewValue;
      Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() =>
      {
        var win = Window.GetWindow(ctrl);   
        var transform = ctrl.TransformToAncestor(win); // Exception thrown here.   
        var pt = transform.Transform(new Point(0, 0));   
        obj.SetValue(AbcProperty, pt.Y);
      });
    }
  });
}

Теперь вы можете написать:

<SomeObject AbcControl="{Binding Xyz}" />

Который установит для свойства Abc преобразованное значение Y.

Существует множество возможных вариантов этой общей идеи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...