修复需要结合多种技术。对于填充可视化树的第一个选择,我需要处理 ContentControl.OnApplyTemplate() 这只是一个虚拟方法而不是一个事件。我从中派生出来并将其作为一个事件公开:
public class ContentControlWithEvents : ContentControl
{
public event EventHandler? TemplateApplied;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.TemplateApplied?.Invoke(this, EventArgs.Empty);
}
}
在 XAML 中,我使用了上面的类而不是 ContentControl:
<local:ContentControlWithEvents
Content="{Binding SelectedAccount}"
x:Name="BankingSelectedAccountPresenter"
TemplateApplied="BankingSelectedAccountPresenter_TemplateApplied" />
然后我这样处理事件:
void BankingSelectedAccountPresenter_TemplateApplied(object sender, EventArgs e) => 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) > 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 子级时。当视图的'master'部分中的ListView更改选择时,我们仍然运行UpdateSelectedTransactions方法:
void BankingPanelAccountList_SelectionChanged(object sender, SelectionChangedEventArgs e) => this.UpdateSelectedTransactions();
在初始启动时,首先引发SelectionChanged,我们使用GetChildrenCount 检查跳过这个。然后TemplateApplied 被提升,我们使用当前选择来找到正确的模板并搜索我们需要的控件。后来在选择变化时,第一个事件再次提出并重新触发我们的逻辑。
最后一个技巧是我们必须调用ContentPresenter.ApplyTemplate() 来强制更新模板选择在我们搜索子控件之前。否则,此代码可能仍会在根据 ListView 中选择的项目类型更新模板之前运行。