#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? Позвольте задать это другим способом. Как я могу немедленно получить текстовое поле, когда шаблон содержимого изменяет визуальное дерево?