# Retrieve Project Browser State in SDK

**URL:** <https://forum.inductiveautomation.com/t/retrieve-project-browser-state-in-sdk/98091>\
**Category:** Module Development\
**Created:** [November 3, 2024, 1:06pm UTC](https://forum.inductiveautomation.com/t/retrieve-project-browser-state-in-sdk/98091 "2024-11-03T13:06:03Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![kgamble](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kgamble/32/8019_2.png) [@kgamble](https://forum.inductiveautomation.com/u/kgamble)\
**Post date:** [November 3, 2024, 1:06pm UTC](https://forum.inductiveautomation.com/t/retrieve-project-browser-state-in-sdk/98091/1 "2024-11-03T13:06:03Z")

</div>

I have been dealing with this annoying bug:

> [@\[IGN-3422\] Project Browser Tree Expansion Bug](https://forum.inductiveautomation.com/t/ign-3422-project-browser-tree-expansion-bug/52146/27):
>
> Alright, well, we had hoped we'd isolated a root cause being related to that search/filter box, but apparently not. Either way, we'll push those changes (probably in 8.1.21) and keep trying to figure out the core issue.

That gets triggered during saves, sometimes. I have noticed that my [Project scan endpoint module](https://github.com/design-group/ignition-project-scan-endpoint) triggers it quite a bit.

Because I have an opportunity to hook something in temporarily, I was curious if its possible for me to get the expansion state of items in the project browser, and then re-set it after they all get collapsed? Hacky workaround for an annoying bug, but would at lease solve the problem in some older versions of Ignition.

Is it possible to get and set the state of that tree?

---

<div class="post-metadata">

**Author:** ![kgamble](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kgamble/32/8019_2.png) [@kgamble](https://forum.inductiveautomation.com/u/kgamble)\
**Post date:** [November 3, 2024, 2:01pm UTC](https://forum.inductiveautomation.com/t/retrieve-project-browser-state-in-sdk/98091/2 "2024-11-03T14:01:13Z")

</div>

Actually, I think I figured it out.

I just wrote a state manager for it

```python
package com.bwdesigngroup.ignition.project_scan.designer.browser;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.inductiveautomation.ignition.designer.model.DesignerContext;
import com.inductiveautomation.ignition.designer.navtree.model.AbstractNavTreeNode;
import com.inductiveautomation.ignition.designer.navtree.model.ProjectBrowserRoot;
import com.bwdesigngroup.ignition.project_scan.common.ProjectScanConstants;

import java.util.*;

public class ProjectBrowserStateManager {
    private final Logger logger = LoggerFactory.getLogger(ProjectScanConstants.MODULE_ID + ".browserState");
    private final DesignerContext context;
    private Map<String, TreeNodeState> lastTreeState;

    public ProjectBrowserStateManager(DesignerContext context) {
        this.context = context;
    }

    public void captureState() {
        ProjectBrowserRoot root = context.getProjectBrowserRoot();
        if (root != null) {
            lastTreeState = new HashMap<>();
            for (int i = 0; i < root.getChildCount(); i++) {
                captureNodeState(root.getChildAt(i), lastTreeState);
            }
            logger.debug("Captured project browser state");
        }
    }

    public void restoreState() {
        if (lastTreeState != null) {
            ProjectBrowserRoot root = context.getProjectBrowserRoot();
            if (root != null) {
                for (int i = 0; i < root.getChildCount(); i++) {
                    restoreNodeState(root.getChildAt(i));
                }
                logger.debug("Restored project browser state");
            }
        }
    }

    private void captureNodeState(AbstractNavTreeNode node, Map<String, TreeNodeState> stateMap) {
        String pathString = getNodePath(node);
        boolean expanded = node.getModel().isExpanded(node);
        boolean selected = node.isSelected();
        
        TreeNodeState state = new TreeNodeState(
            pathString,
            node.getName(),
            expanded,
            selected
        );
        stateMap.put(pathString, state);

        if (!node.isLeaf()) {
            for (int i = 0; i < node.getChildCount(); i++) {
                captureNodeState(node.getChildAt(i), stateMap);
            }
        }
    }

    private String getNodePath(AbstractNavTreeNode node) {
        List<String> pathParts = new ArrayList<>();
        AbstractNavTreeNode current = node;
        while (current != null && current.getParent() != null) {
            pathParts.add(0, current.getName());
            current = current.getParent();
        }
        return String.join("/", pathParts);
    }

    private void restoreNodeState(AbstractNavTreeNode node) {
        String pathString = getNodePath(node);
        TreeNodeState savedState = lastTreeState.get(pathString);
        
        if (savedState != null) {
            if (savedState.expanded) {
                node.getModel().expandNode(node);
            }
            if (savedState.selected) {
                node.select(true);
            }
        }

        if (!node.isLeaf()) {
            for (int i = 0; i < node.getChildCount(); i++) {
                restoreNodeState(node.getChildAt(i));
            }
        }
    }

    private static class TreeNodeState {
        final String path;
        final String name; 
        final boolean expanded;
        final boolean selected;

        TreeNodeState(String path, String name, boolean expanded, boolean selected) {
            this.path = path;
            this.name = name;
            this.expanded = expanded;
            this.selected = selected;
        }
    }
}

```

And then on my push notification to the designer I am using

```python
ProjectScanEndpointDesignerHook.pushNotificationListener = new DesignerPushNotificationListener(designer) {
            @Override
            public void receive(PushNotification notification) {
                if (notification.getMessageType().equals(ProjectScanConstants.DESIGNER_SCAN_NOTIFICATION_ID)) {
                    browserStateManager.captureState();
                    super.receive(notification);
                    // Give the UI a moment to update
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        logger.error("Sleep interrupted", e);
                    }
                    browserStateManager.restoreState();
                }
            }
        };

```
