WPF TreeList Control — Load Nodes Asynchronously (v22.2)

WPF Team Blog
11 January 2023

In this post, we’ll detail how you can asynchronously load tree nodes within the DevExpress WPF TreeList control and our WPF Data Grid’s TreeListView.

As you may already know, when fetching data from a service or loading a node with many child records, your app may temporarily “freeze” during node expansion. To help address this issue, you can now load child nodes in a background thread. With this option enabled, your app UI will remain responsive during load/expand operations.

When loading information, our WPF TreeList control will display a wait indicator on-screen. Once completed, the node will be expanded, and the wait indicator hidden.

To introduce this capability in your next WPF project, create a class that implements the IAsyncChildNodesSelector interface and override the SelectChildrenAsync method. The selector calls this method when a user expands a node and allows you to load child nodes within a background thread. The method returns a Task object (which contains the collection of loaded child records):


public class AsyncChildrenSelector : IAsyncChildNodesSelector {
    public Task<bool> HasChildNode(object item, CancellationToken token) {
        throw new NotImplementedException();
    }

    public IEnumerable SelectChildren(object item) {
        throw new NotImplementedException();
    }

    public Task<IEnumerable> SelectChildrenAsync(object item, CancellationToken token) {
        return Task.Run(async () => {
            await Task.Delay(1000);
            return SelectChildNodes(item);
        });
    }
    public IEnumerable SelectChildNodes(object item) {
        if (item is ProjectStage)
            return (item as ProjectStage).StageTasks;
        else if (item is ProjectObject)
            return (item as ProjectObject).ProjectStages;
        return null;
    }
}

Next, override the HasChildNode method to check whether the loaded node has child nodes. The selector calls this method when all child nodes are loaded. The method returns a Task object that contains a Boolean value (indicates whether the loaded node includes child nodes). Based on this value, our WPF TreeList control displays the node’s expand button:


public class AsyncChildrenSelector : IAsyncChildNodesSelector {
    public Task<bool> HasChildNode(object item, CancellationToken token) {
        return Task.Run(async () => {
            await Task.Delay(250);
            return !(item is StageTask);
        });
    }

    public IEnumerable SelectChildren(object item) {
        throw new NotImplementedException();
    }

    public Task<IEnumerable> SelectChildrenAsync(object item, CancellationToken token) {
        return Task.Run(async () => {
            await Task.Delay(1000);
            return SelectChildNodes(item);
        });
    }
    public IEnumerable SelectChildNodes(object item) {
        if (item is ProjectStage)
            return (item as ProjectStage).StageTasks;
        else if (item is ProjectObject)
            return (item as ProjectObject).ProjectStages;
        return null;
    }
}

You can allow end users to cancel any load operation. To cancel a load operation in response to a user’s cancellation request, call the CancellationToken.ThrowIfCancellationRequested() method in the SelectChildrenAsync or HasChildNode method. If implemented, our WPF TreeList control will display a retry button when a user cancels a load operation. As you would expect, this “refresh” button allows users to restart the operation:


public class CustomChildrenSelector : IAsyncChildNodesSelector {
    public Task<bool> HasChildNode(object item, CancellationToken token) {
        return Task.Run(async () => {
            for (int i = 0; i < 10; i++) {
                token.ThrowIfCancellationRequested();
                await Task.Delay(25);
            }
            return !(item is StageTask);
        });
    }

    public IEnumerable SelectChildren(object item) {
        throw new NotImplementedException();
    }

    public Task<IEnumerable> SelectChildrenAsync(object item, CancellationToken token) {
        return Task.Run(async () => {
            for (int i = 0; i < 10; i++) {
                token.ThrowIfCancellationRequested();
                await Task.Delay(100);
            }
            return SelectChildNodes(item);
        });
    }
    public IEnumerable SelectChildNodes(object item) {
        if (item is ProjectStage)
            return (item as ProjectStage).Tasks;
        else if (item is ProjectObject)
            return (item as ProjectObject).Stages;
        return null;
    }
}

Set the TreeListView.TreeDerivationMode property to ChildNodesSelector and assign an instance of the created class to the TreeListView.ChildNodesSelector property:


<dxg:TreeListControl ...>
    <dxg:TreeListControl.Resources>
        <local:AsyncChildrenSelector x:Key="childrenSelector"/>
    </dxg:TreeListControl.Resources>
    <dxg:TreeListControl.View>
        <dxg:TreeListView TreeDerivationMode="ChildNodesSelector"
                          ChildNodesSelector="{StaticResource childrenSelector}"/>
    </dxg:TreeListControl.View>
</dxg:TreeListControl>

Refer to the following help topic for more information on this feature: Fetch Nodes Asynchronously.

You can find sample updates and versions for different programming languages here: WPF Tree List - Load Nodes Asynchronously Without Locking the Application’s UI.

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.