ItemsControl:如何在ItemsPanelTemplate中使用FindName访问Panel

3
<Style TargetType="{x:Type local:CustomItemsControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate>
                <ScrollViewer>
                    <ItemsPresenter x:Name="PART_Presenter"/>
                </ScrollViewer>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <StackPanel x:Name="PART_StackPanel" IsItemsHost="True"/>
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
</Style>

尝试访问StackPanel以设置在子元素更改时的事件。

[TemplatePartAttribute(Name = "PART_StackPanel", Type = typeof(StackPanel))]
[TemplatePartAttribute(Name = "PART_Presenter", Type = typeof(ItemsPresenter))]
public class CustomItemsControl: ItemsControl
{
    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        var presenter = (ItemsPresenter)this.Template.FindName("PART_Presenter", this);
        var stackPanel = (StackPanel)this.ItemsPanel.FindName("PART_StackPanel",this);
    }
}

当我尝试定位StackPanel时,出现异常。

无效操作异常:

此操作仅适用于已应用此模板的元素。

请指导是否有一种方法可以在ItemsPanelTemplate中查找TemplatePart。我应该在何时预计知道ItemsPanelTemplate已被应用?


你需要订阅哪些事件? - Marat Khasanov
3个回答

6
另一种选择是在ItemControl的OnApplyTemplate方法中调用ItemsPresenter的.ApplyTemplate()方法。然后调用.FindName将成功。
    [TemplatePartAttribute(Name = "PART_StackPanel", Type = typeof(StackPanel))]
    [TemplatePartAttribute(Name = "PART_Presenter", Type = typeof(ItemsPresenter))]
    public class CustomItemsControl : ItemsControl
    {
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            var presenter = (ItemsPresenter)this.Template.FindName("PART_Presenter", this);
            presenter.ApplyTemplate();
            var stackPanel = (StackPanel)this.ItemsPanel.FindName("PART_StackPanel", presenter);
        }
    }

1
发现在ItemsPanelTemplate上等待的是Loaded事件。我能够使用TemplatePart名称找到StackPanel。感谢Rick建议在Presenter中找到StackPanel。
    protected override void OnInitialized(EventArgs e)
    {
        base.OnInitialized(e);
        this.Loaded += new Accordion_Loaded;
    }

    void Accordion_Loaded(object sender, RoutedEventArgs e)
    {
        var presenter = (ItemsPresenter)this.Template.FindName("PART_Presenter", this);
        var stackPanel = (StackPanel)this.ItemsPanel.FindName("PART_StackPanel", presenter);
    }

0

FindName方法只在已经扩展的模板中查找名称,而ItemsPanelItemsPresenter扩展,而不是ItemsControl。 在您的情况下,"PART_StackPanel"将始终是"PART_Presenter"的子级,因此可以像这样引用它:

    var stackPanel = (StackPanel)VisualTreeHelper.GetChild(presenter, 0);

仍然在那一行遇到了ArgumentOutOfRangeException。指定的索引超出范围或索引处的子项为空。 如果VisualChildrenCount返回零,表示Visual没有子项,请勿调用此方法。 参数名称:index 实际值为0。同时不确定在模板中提到“expanded”时确切的含义。感谢您的帮助。我还能在哪个时间点期望找到PART_StackPanel被添加到Presenter中? - Case
你可以将 StackPanel 移动到 ControlTemplate 中,而不使用 ItemsPresenter。或者你可以在 ItemsPresenterLoaded 事件中查找 StackPanel。Expanded 意味着谁正在应用模板。ItemsControl 不会应用 ItemsPanel 模板。 - Rick Sladkey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接