根据嵌套类型使用自定义加载方法延迟加载 TreeViewItem

Lazy Loading TreeViewItem with custom loading method depending on their nested type

假设我有 collection 名员工。员工可以是高级主管、初级主管或实习生。 这 3 种类型的员工是 类 从 employee 派生的。每个高级主管指导一组初级主管。每个初级主管指导一组学员。

LoadChilds填写ChildsCollection.

现在我想通过使用延迟初始化树视图中显示他们从高级到实习生的层次结构。我想在 TreeViewItem.Exapand 事件上调用方法 LoadChilds。 (我对其他想法持开放态度。)

目前我正在通过 HierarchicalDataTemplate 呈现不同的数据类型。

<TreeView x:Name="Tree">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type model:Supervisor}" ItemsSource="{Binding Childs}">
            <StackPanel Orientation="Horizontal" >
                <TextBlock Text="{Binding Name}" />
            </StackPanel>
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate DataType="{x:Type model:Employee}" ItemsSource="{Binding Childs}">
            <StackPanel Orientation="Horizontal" >
                <TextBlock Text="{Binding Name}" />
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.Resources>
</TreeView>

问题:如何在展开 TreeViewItem 时调用方法、编辑数据并刷新 UI?

您可以定义一个接口,AB 都实现并将扩展的 TreeViewItemDataContext 转换为这种类型:

public interface ICommon
{
    string Name { get; set; }
    void LoadChilds(DBConnection connection);
}

public class A : ICommon
{
    public class A()
    {
        Childs = new ObservableCollection<B>();
    }
    public string Name { get; set; }
    public ObservableCollection<B> Childs { get; set; }
    public void LoadChilds(DBConnection connection) //Type is unimportant
    {
        // adding childs
    }
}

public class B : ICommon
{
    public class B()
    {
        Childs = new ObservableCollection<B>(); //Yes, rekursiv
    }
    public string Name { get; set; }
    public ObservableCollection<B> Childs { get; set; }
    public void LoadChilds(DBConnection connection)
    {
        // adding childs
    } 
}

private void TreeView_Expanded(object sender, RoutedEventArgs e)
{
    TreeViewItem tvi = e.OriginalSource as TreeViewItem;
    ICommon obj = tvi.DataContext as ICommon;
    if (obj != null)
        obj.LoadChilds(...);

}

绝对有一种 "prettier" 方法 - 无需在代码隐藏的事件处理程序中编写业务逻辑。

在视图模型 类 中引入布尔值 IsExpanded,将 TreeViewItem.IsExpanded 绑定到 属性 并触发 setter 中的 LoadChilds 方法:

<TreeView x:Name="Tree">
    <TreeView.ItemContainerStyle>
        <Style TargetType="TreeViewItem">
            <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded, Mode=TwoWay}"/>
        </Style>
    </TreeView.ItemContainerStyle>
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type model:A}" ItemsSource="{Binding Childs}">
            <StackPanel Orientation="Horizontal" >
                <TextBlock Text="{Binding Name}" />
            </StackPanel>
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate DataType="{x:Type model:B}" ItemsSource="{Binding Childs}">
            <StackPanel Orientation="Horizontal" >
                <TextBlock Text="{Binding Name}" />
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.Resources>
</TreeView>
public class A
{
    public A()
    {
        // null is a placeholder. 
        // without any items TreeViewItem will not even show expander
        // (Expand event won't work either)
        Childs = new ObservableCollection<B>() { null };
    }

    public string Name { get; set; }

    private bool _isExpanded;
    public bool IsExpanded
    {
        get
        {
            return _isExpanded;
        }
        set
        {
            _isExpanded = value;
            // when node is expanded, RELOAD!
            if (_isExpanded)
                LoadChilds();
        }
    }

    public ObservableCollection<B> Childs { get; set; }

    public void LoadChilds()
    {
        Childs.Clear();
        Childs.Add(new B() { Name = Guid.NewGuid().ToString() });
    }
}

B 在这个测试示例中几乎相同,但我想 LoadChilds 逻辑在真实应用程序中会有所不同

public class B
{
    public B()
    {
        Childs = new ObservableCollection<B>() { null };
    }

    public string Name { get; set; }

    private bool _isExpanded;
    public bool IsExpanded
    {
        get
        {
            return _isExpanded;
        }
        set
        {
            _isExpanded = value;
            if (_isExpanded)
                LoadChilds();
        }
    }

    public ObservableCollection<B> Childs { get; set; }

    public void LoadChilds()
    {
        Childs.Clear();
        Childs.Add(new B() { Name = Guid.NewGuid().ToString() });
    }
}