Мастер-деталь: Как извлечь элемент управления из шаблона внутри элемента управления содержимым «детали»?

#wpf #data-binding #master-detail

#wpf #привязка данных #мастер-деталь

Вопрос:

У меня есть ListView (на стороне «мастера»), выбор которого определяет ContentControl Content свойство (на стороне «детали»). Визуальное ContentControl дерево исходит из любого из двух DataTemplate ресурсов, которые используются DataType для выбора подробного представления для визуализации на основе того, что выбрано в ListView . Эта часть работает нормально.

Часть, с которой я борюсь, заключается в том, что внутри (одного из) шаблонов есть определенный элемент управления, на который мне нужно получать ссылку всякий раз, когда он изменяется (например, выбранный шаблон изменяется или ListView выбор изменяется таким образом, что экземпляр элемента управления воссоздается).

В моем ListView.SelectionChanged обработчике событий я обнаружил ContentControl , что его новое визуальное дерево еще не обновлено, поэтому изначально оно пусто при первом выборе, а для последующих вариантов его визуальное дерево соответствует старому выбору вместо нового. Я попытался отложить свой код , запланировав Dispatcher его с таким низким приоритетом, как DispatcherPriority.Loaded , что работает для первого выбора, но при последующих выборках мой код все еще запускается до обновления визуального дерева.

Есть ли лучшее событие, которое я должен запускать всякий ContentControl раз, когда визуальное дерево изменяется, чтобы отразить измененное значение, связанное с данными, в его Content свойстве?

Дополнительная информация: причина, по которой мне нужно обратиться к расширенному DataTemplate , заключается в том, что мне нужно эффективно установить свойство модели представления IList SelectedItems DataGrid на свойство элемента SelectedItems управления. Поскольку DataGrid.SelectedItems это не свойство зависимости, я должен сделать это вручную в коде.

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

1. Здесь были зарегистрированы голоса за закрытие : Требуется информация об отладке (2 голоса) Вопрос должен быть обновлен, чтобы включить желаемое поведение, конкретную проблему или ошибку и самый короткий код, необходимый для воспроизведения проблемы.

Ответ №1:

Исправление требовало сочетания методов. Для первого выбора, который заполняет визуальное дерево, мне нужно было обработать ContentControl.OnApplyTemplate() , который является только виртуальным методом, а не событием. Я извлек из этого и представил это как событие:

 public class ContentControlWithEvents : ContentControl {  public event EventHandler? TemplateApplied;   public override void OnApplyTemplate()  {  base.OnApplyTemplate();  this.TemplateApplied?.Invoke(this, EventArgs.Empty);  } }  

В XAML я использовал вышеуказанный класс, а не ContentControl :

 lt;local:ContentControlWithEvents  Content="{Binding SelectedAccount}"  x:Name="BankingSelectedAccountPresenter"   TemplateApplied="BankingSelectedAccountPresenter_TemplateApplied" /gt;  

Затем я обрабатываю событие следующим образом:

 void BankingSelectedAccountPresenter_TemplateApplied(object sender, EventArgs e) =gt; this.UpdateSelectedTransactions();  private void UpdateSelectedTransactions() {  if (this.MyListView.SelectedItem?.GetType() is Type type)  {  DataTemplateKey key = new(type);  var accountTemplate = (DataTemplate?)this.FindResource(key);  Assumes.NotNull(accountTemplate);  if (VisualTreeHelper.GetChildrenCount(this.BankingSelectedAccountPresenter) gt; 0)  {  ContentPresenter? presenter = VisualTreeHelper.GetChild(this.BankingSelectedAccountPresenter, 0) as ContentPresenter;  Assumes.NotNull(presenter);  presenter.ApplyTemplate();  var transactionDataGrid = (DataGrid?)accountTemplate.FindName("TransactionDataGrid", presenter);  this.ViewModel.Document.SelectedTransactions = transactionDataGrid?.SelectedItems;  }  } }  

Обратите внимание на GetChildrenCount проверку, которая позволяет избежать исключения, возникшего GetChild позже, если дочерних элементов еще нет. Это нам понадобится на потом.

TemplateApplied Событие вызывается только один раз-когда ContentControl сначала дается его ContentPresenter дочерний элемент. У нас все еще UpdateSelectedTransactions есть метод, который нужно запускать, когда представление списка в «основной» части представления изменяет выбор:

 void BankingPanelAccountList_SelectionChanged(object sender, SelectionChangedEventArgs e) =gt; this.UpdateSelectedTransactions();  

При первоначальном запуске SelectionChanged сначала вызывается, и мы пропускаем это с GetChildrenCount проверкой. Затем TemplateApplied вызывается, и мы используем текущий выбор, чтобы найти правильный шаблон и выполнить поиск нужного нам элемента управления. Позже, когда выбор меняется, первое событие вызывается снова и повторно запускает нашу логику.

Последний трюк заключается в том, что мы должны позвонить ContentPresenter.ApplyTemplate() , чтобы принудительно обновить выбор шаблона, прежде чем искать дочерний элемент управления. Без этого этот код все равно может выполняться до обновления шаблона в зависимости от типа элемента, выбранного в ListView .