【问题标题】:Master-detail: How to fetch a control from a template inside the "detail" ContentControl?Master-detail:如何从“详细”ContentControl 中的模板中获取控件?
【发布时间】:2022-01-12 00:40:42
【问题描述】:

我有一个ListView(在“主”端),其选择驱动了ContentControlContent 属性(在“详细”端)。 ContentControl 的可视化树来自两个 DataTemplate 资源之一,它们使用 DataType 根据在​​ ListView 中选择的内容来选择要渲染的详细视图。 这部分工作正常。

我正在苦苦挣扎的部分是有一个特定的控件内部(其中一个)模板,我需要在它更改时获取对它的引用(例如,选择的模板更改或ListView 选择更改,从而重新创建控件实例。)

在我的ListView.SelectionChanged 事件处理程序中,我发现ContentControl 尚未使用其新的可视化树进行更新,因此最初它在第一次选择时是空的,而对于后续选择,它的可视化树与旧的匹配 选择而不是新的选择。 我尝试通过在Dispatcher 上调度优先级低至DispatcherPriority.Loaded 来延迟我的代码,这适用于第一个选择,但在随后的选择中,我的代码仍然在可视化树更新之前运行。

每当ContentControl 的可视化树发生更改以将更改的数据绑定值反映到其Content 属性时,是否有更好的事件我应该挂接运行?

额外信息:我需要进入扩展的DataTemplate 的原因是我需要有效地将我的视图模型的IList SelectedItems 属性设置为DataGrid 控件的SelectedItems 属性。由于DataGrid.SelectedItems 不是依赖属性,我必须在代码中手动执行此操作。

【问题讨论】:

  • 这是记录接近投票的地方:需要调试详细信息(2 票)应更新问题以包括所需的行为、特定问题或错误以及重现问题所需的最短代码。

标签: wpf data-binding master-detail


【解决方案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

<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 中选择的项目类型更新模板之前运行。

【讨论】:

    猜你喜欢
    • 2021-09-08
    • 1970-01-01
    • 2017-11-24
    • 1970-01-01
    • 2019-08-28
    • 1970-01-01
    • 1970-01-01
    • 2021-11-11
    • 1970-01-01
    相关资源
    最近更新 更多