#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
.