Почему ‘this.ContentTemplate.FindName’ выдает исключение InvalidOperationException в своем собственном шаблоне?

#wpf #invalidoperationexception #contenttemplateselector

#wpf #исключение invalidoperationexception #contenttemplateselector

Вопрос:

Хорошо … это ставит меня в тупик. Я переопределил его OnContentTemplateChanged в своем UserControl подклассе. Я проверяю, что значение, переданное для, newContentTemplate фактически равно this.ContentTemplate (так и есть), когда я вызываю это…

 var textBox = this.ContentTemplate.FindName("EditTextBox", this);
  

…он выдает следующее исключение…

«Эта операция действительна только для элементов, к которым применен этот шаблон».

Согласно комментарию в другом связанном вопросе, он сказал, что вы должны передавать content presenter для элемента управления, а не сам элемент управления, поэтому я тогда попробовал это…

 var cp = FindVisualChild<ContentPresenter>(this);

var textBox = this.ContentTemplate.FindName("EditTextBox", cp);
  

…где FindVisualChild — это просто вспомогательная функция, используемая в примере MSDN (см. Ниже) для поиска соответствующего презентатора контента. При cp обнаружении он также выдает ту же ошибку. Я в тупике!!

Вот вспомогательная функция для справки…

 private TChildItem FindVisualChild<TChildItem>(DependencyObject obj)
where TChildItem : DependencyObject {

    for(int i = 0 ; i < VisualTreeHelper.GetChildrenCount(obj) ; i  ) {

        var child = VisualTreeHelper.GetChild(obj, i);

        if(child is TChildItem typedChild) {
            return typedChild;
        }
        else {
            var childOfChild = FindVisualChild<TChildItem>(child);
            if(childOfChild != null)
                return childOfChild;
        }
    }

    return null;
}
  

Ответ №1:

Явное применение шаблона перед вызовом FindName метода предотвратит эту ошибку.

 this.ApplyTemplate(); 
  

Комментарии:

1. Срань господня! Если это действительно так просто (что и предлагают документы!), я собираюсь пнуть себя! Я предполагаю, что это разъясняет, что шаблон изменен, но на данный момент не применяется. Ваш вызов гарантирует, что это будет. Приятно!!

2. У меня здесь вопрос. Я делаю некоторые изменения внутри ApplyTemplate () только для переопределения некоторого управляющего значения. тогда каково решение?

3. В моем случае применение вызова метода к ContentPresenter (второй параметр вызова FindName() ) сделало свое дело, т.е. cp.ApplyTemplate()

Ответ №2:

Как указал Джон, OnContentTemplateChanged запускается до того, как он фактически применяется к базовому ContentPresenter . Поэтому вам нужно будет отложить вызов FindName до его применения. Что-то вроде:

 protected override void OnContentTemplateChanged(DataTemplate oldContentTemplate, DataTemplate newContentTemplate) {
    base.OnContentTemplateChanged(oldContentTemplate, newContentTemplate);

    this.Dispatcher.BeginInvoke((Action)(() => {
        var cp = FindVisualChild<ContentPresenter>(this);
        var textBox = this.ContentTemplate.FindName("EditTextBox", cp) as TextBox;
        textBox.Text = "Found in OnContentTemplateChanged";
    }), DispatcherPriority.DataBind);
}
  

В качестве альтернативы вы можете прикрепить обработчик к событию LayoutUpdated пользовательского элемента управления, но это может срабатывать чаще, чем вы хотите. Однако это также обработало бы случаи неявных DataTemplates.

Что-то вроде этого:

 public UserControl1() {
    InitializeComponent();
    this.LayoutUpdated  = new EventHandler(UserControl1_LayoutUpdated);
}

void UserControl1_LayoutUpdated(object sender, EventArgs e) {
    var cp = FindVisualChild<ContentPresenter>(this);
    var textBox = this.ContentTemplate.FindName("EditTextBox", cp) as TextBox;
    textBox.Text = "Found in UserControl1_LayoutUpdated";
}
  

Комментарии:

1. Все еще не согласен с тем, что MS называет эту функцию так, как будто она еще не была применена, тогда это действительно не изменило IMO (не говоря уже о том, что с почти идентичным именем OnApplyTemplate вам не нужно иметь с этим дело), но вы получаете ‘Accept’, поскольку вы дали мне пример кода, который дает мне то, что я хочу.

2. ты всегда хорош в такого рода вещах. Однако, даже ваш ответ не намного сложнее, чем приведенный ниже @Adabyron, который заключается в простом явном вызове this.ApplyTemplate() ? Я должен найти этот старый код для тестирования, но, согласно документам, это именно то, для чего он существует, даже сообщая вам, изменилось ли VisualTree в результате вызова.

3. @MarqueIV — Да, это действительно кажется более простым решением, а? Единственное мое преимущество сейчас в том, что его можно адаптировать для обработки неявно применяемых DataTemplates (т. Е. с помощью неявного стиля и т.д.).

Ответ №3:

ContentTemplate не применяется к ContentPresenter до окончания этого события. Хотя свойство ContentTemplate установлено в элементе управления в этот момент, оно не было перенесено на привязки, внутренние для ControlTemplate, такие как ContentTemplate ContentPresenter .

Что вы в конечном итоге пытаетесь сделать с ContentTemplate? Возможно, существует лучший общий подход для достижения вашей конечной цели.

Комментарии:

1. Тогда с какой стати это событие? И в нем действительно указано ‘Изменено’, а не ‘Предварительный просмотр’ или ‘Будет изменен’. Из того, что вы сказали, это на самом деле еще не изменилось. И ошибка определенно вводит в заблуждение. (Кроме того, опять же, я пробовал использовать ContentPresenter только для кого-то другого. Я использую UserControl, поэтому я не использую презентацию контента. Я просто заменяю существующие теги в usercontrol другими с помощью шаблона в ресурсах. Что касается того, что я пытаюсь сделать, это получить один из элементов, заменяемых этим шаблоном, который, как я думал, должен был показать приведенный выше код.

2. Способ добиться этого, который я придумал, заключался в определении переменной уровня класса для хранения управляющей ссылки. Затем я прикрепляю к нему загруженное событие через шаблон. Затем в коде я просто сохраняю ‘sender’ в переменной. Затем я использую вызов OnContentTemplateChanged, чтобы присвоить этой переменной значение ‘null’. Это сложная вещь, но она работает. И все же, почему это вышеупомянутое событие, когда вам буквально вручают шаблон, но вы ничего не можете с ним сделать?! Не имеет смысла, тем более, что OnApplyTemplate применяет его. По этой мысли, это тоже должно быть.

3. Вам следует более внимательно присмотреться к вашему визуальному дереву во время выполнения, используя такой инструмент, как Snoop, чтобы лучше понять, что на самом деле отображается. UserControl — это ContentControl, что означает, что он использует ContentPresenter в своем шаблоне по умолчанию для отображения содержимого. ContentTemplate применяется к этому ContentPresenter посредством привязки после того, как он был назначен свойству ContentTemplate в UserControl. Понятно, что вы пытаетесь получить EditTextBox, но зачем? Часто вы можете избавиться от кода события и вместо этого использовать привязку данных.

4. «ContentTemplate применяется к этому ContentPresenter посредством привязки после того, как оно было присвоено свойству ContentTemplate в UserControl.» тогда как я могу обнаружить это изменение?! По вашему собственному определению (и элементам управления) ContentTemplate установлен в качестве элемента управления, и, следовательно, эта привязка должна была уже выполняться, так почему же FindName не работает? Почему бы эта привязка не была обновлена к тому времени, когда я уже выполнил base. OnContentTemplateChanged? Позвольте задать это другим способом. Как я могу немедленно получить текстовое поле, когда шаблон содержимого изменяет визуальное дерево?