TheGreyNight
TheGreyNight

Reputation: 13

Java JTree node is a clickable URL link

I'm trying to make a node be a clickable URL, but I just can't seem to figure out how.

I've searched high and low, and I can't seem to find a solution.

This is my code:

public class NyttigeLinks {

    private static JFrame nyttigeLinks;

    public static void main(String[] args) {

                    initialize();
            }

    public NyttigeLinks() {

    }

    private static void initialize() {
        nyttigeLinks = new JFrame();
        nyttigeLinks.setBounds(new Rectangle(0, 0, 350, 650));
        nyttigeLinks.getContentPane().setBounds(new Rectangle(0, 0, 350, 650));
        nyttigeLinks.getContentPane().setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
        nyttigeLinks.getContentPane().setLayout(null);

        JLabel logoLabel = new JLabel("");
        logoLabel.setIcon(new ImageIcon(NyttigeLinks.class.getResource("/images/ssiLogo.jpg")));
        logoLabel.setBounds(0, 0, 350, 60);

        JTree tree = new JTree();

        nyttigeLinks.getContentPane().add(logoLabel);

        JScrollPane scrollPane = new JScrollPane(tree);
        scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPane.setBounds(10, 71, 324, 508);
        nyttigeLinks.getContentPane().add(scrollPane);

        tree.setModel(new DefaultTreeModel(
            new DefaultMutableTreeNode("Nyttige Links\t") {
                {
                    DefaultMutableTreeNode node_1;
                    node_1 = new DefaultMutableTreeNode("Projekt Wiki");
                        node_1.add(new DefaultMutableTreeNode("AO"));
                        node_1.add(new DefaultMutableTreeNode("Attends"));
                        node_1.add(new DefaultMutableTreeNode("Carlsberg"));
                        node_1.add(new DefaultMutableTreeNode("COOP"));
                        node_1.add(new DefaultMutableTreeNode("Dafgaard"));
                        node_1.add(new DefaultMutableTreeNode("Jysk DK"));
                        node_1.add(new DefaultMutableTreeNode("Jysk SE"));
                        node_1.add(new DefaultMutableTreeNode("Kvadrat"));
                        node_1.add(new DefaultMutableTreeNode("Solar"));
                        node_1.add(new DefaultMutableTreeNode("Stockmann"));
                        node_1.add(new DefaultMutableTreeNode("Tine"));
                        node_1.add(new DefaultMutableTreeNode("Unicef"));
                        node_1.add(new DefaultMutableTreeNode("Vectura"));
                    add(node_1);
                    node_1 = new DefaultMutableTreeNode("Helpdesk Norcic");
                        node_1.add(new DefaultMutableTreeNode("Test"));
                    add(node_1);
                    node_1 = new DefaultMutableTreeNode("Test");
                        node_1.add(new DefaultMutableTreeNode("AO"));
                        node_1.add(new DefaultMutableTreeNode("Attends"));
                        node_1.add(new DefaultMutableTreeNode("Carlsberg"));
                        node_1.add(new DefaultMutableTreeNode("COOP"));
                        node_1.add(new DefaultMutableTreeNode("Dafgaard"));
                        node_1.add(new DefaultMutableTreeNode("Jysk DK"));
                        node_1.add(new DefaultMutableTreeNode("Jysk SE"));
                        node_1.add(new DefaultMutableTreeNode("Kvadrat"));
                        node_1.add(new DefaultMutableTreeNode("Solar"));
                        node_1.add(new DefaultMutableTreeNode("Stockmann"));
                        node_1.add(new DefaultMutableTreeNode("Tine"));
                        node_1.add(new DefaultMutableTreeNode("Unicef"));
                        node_1.add(new DefaultMutableTreeNode("VecturaAO"));
                        node_1.add(new DefaultMutableTreeNode("Attends"));
                        node_1.add(new DefaultMutableTreeNode("Carlsberg"));
                        node_1.add(new DefaultMutableTreeNode("COOP"));
                        node_1.add(new DefaultMutableTreeNode("Dafgaard"));
                        node_1.add(new DefaultMutableTreeNode("Jysk DK"));
                        node_1.add(new DefaultMutableTreeNode("Jysk SE"));
                        node_1.add(new DefaultMutableTreeNode("Kvadrat"));
                        node_1.add(new DefaultMutableTreeNode("Solar"));
                        node_1.add(new DefaultMutableTreeNode("Stockmann"));
                        node_1.add(new DefaultMutableTreeNode("Tine"));
                        node_1.add(new DefaultMutableTreeNode("Unicef"));
                        node_1.add(new DefaultMutableTreeNode("VecturaAO"));
                        node_1.add(new DefaultMutableTreeNode("Attends"));
                        node_1.add(new DefaultMutableTreeNode("Carlsberg"));
                        node_1.add(new DefaultMutableTreeNode("COOP"));
                        node_1.add(new DefaultMutableTreeNode("Dafgaard"));
                        node_1.add(new DefaultMutableTreeNode("Jysk DK"));
                        node_1.add(new DefaultMutableTreeNode("Jysk SE"));
                        node_1.add(new DefaultMutableTreeNode("Kvadrat"));
                        node_1.add(new DefaultMutableTreeNode("Solar"));
                        node_1.add(new DefaultMutableTreeNode("Stockmann"));
                        node_1.add(new DefaultMutableTreeNode("Tine"));
                        node_1.add(new DefaultMutableTreeNode("Unicef"));
                        node_1.add(new DefaultMutableTreeNode("Vectura"));
                    add(node_1);
                }
            }
        ));
        tree.setBounds(10, 71, 324, 540);
        tree.setRootVisible(false);

        JLabel bottomLabelTop = new JLabel("                   Nyttige Links Version 1.0");
        bottomLabelTop.setBounds(0, 590, 230, 14);
        nyttigeLinks.getContentPane().add(bottomLabelTop);

        JLabel bottomLabelBot = new JLabel("                              Made by xxx");
        bottomLabelBot.setBounds(0, 605, 230, 15);
        nyttigeLinks.getContentPane().add(bottomLabelBot);

        JButton btnNewButton = new JButton("Admin");
        btnNewButton.setIcon(new ImageIcon(NyttigeLinks.class.getResource("/images/appIcon.ico")));
        btnNewButton.setBounds(240, 590, 80, 20);
        nyttigeLinks.getContentPane().add(btnNewButton);
        nyttigeLinks.setPreferredSize(new Dimension(350, 650));
        nyttigeLinks.setSize(new Dimension(350, 650));
        nyttigeLinks.setResizable(false);
        nyttigeLinks.setTitle("Nyttige Links");
        nyttigeLinks.setIconImage(Toolkit.getDefaultToolkit().getImage(NyttigeLinks.class.getResource("/images/appIcon.ico")));
        nyttigeLinks.setBackground(Color.YELLOW);
        nyttigeLinks.setBounds(100, 100, 350, 650);
        nyttigeLinks.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        nyttigeLinks.setVisible(true);
    }
}

I apologize for being a bit of a noob, but you gotta start somewhere, right?

If someone can point me in the right direction, I would appreciate it a lot! Have a good night everyone!

Upvotes: 1

Views: 552

Answers (2)

TheGreyNight
TheGreyNight

Reputation: 13

While I haven't checked the above mentioned code, here is how I got around the issue while I was waiting for a reply to this thread:

class SelectionListener implements TreeSelectionListener {

    public void valueChanged(TreeSelectionEvent se) {
        JTree tree = (JTree) se.getSource();
        DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
        String selectedNodeName = selectedNode.toString();
        if (selectedNode.isLeaf()) {
            if (selectedNodeName == "Unicef") {
                try {
                    java.awt.Desktop.getDesktop().browse(java.net.URI.create("http://www.eb.dk"));
                } catch (IOException e1) {
                    // make a error pop up appear here
                    JOptionPane.showMessageDialog(null, "Something went wrong, please report this to the developer!", "Something went wrong", 0);
                    e1.printStackTrace();

                }
            }

            if (selectedNodeName == "Vectura") {
                try {
                    java.awt.Desktop.getDesktop().browse(java.net.URI.create("http://www.google.com"));
                } catch (IOException e1) {
                    // make a error pop up appear here
                    JOptionPane.showMessageDialog(null, "Something went wrong, please report this to the developer!", "Something went wrong", 0);
                    e1.printStackTrace();

                }
            }

        }

    }
}

It's definitely not a nice way of doing it, since it will lead to A LOT of if statements, but it was simple enough for me to understand by myself.

Upvotes: 0

avojak
avojak

Reputation: 2360

I don't believe there is a way to make the node itself a URL. However, you could accomplish the same thing by using listeners to open the browser when the node is clicked. To open the browser, you can use the Desktop#browse(URI) method.

On the topic of clicking, I would recommend not opening a URL when a node is single-clicked. For a user that would be very annoying (IMO). Instead, I'd recommend using a double-click. This answer provides a good way to distinguish between a single and double-click, and how to add it to the Tree.


[...] but you gotta start somewhere, right?

Exactly! Allow me to give a bit more detail here that will hopefully be helpful for learning a bit more.

One challenge you will face is how to know which URL to open for a given node. Currently you only know the String that is displayed, and I think it's a safe assumption that you don't want to be displaying a full URL for each node. Instead of creating the DefaultMutableTreeNode objects with a String parameter, I would recommend creating an object to pass in to the DefaultMutableTreeNode constructor instead. This object can also have a URI attribute so it knows which URL to open.

For example:

public class LeafNodeObject {
    private final URI uri;

    public LeafNodeObject(final String display, final URI uri) {
        this.display = display;
        this.uri = uri;
    }

    /**
     * Override so that we control what is display on the Node
     */
    @Override
    public String toString() {
        return display;
    }

    public void onDoubleClick() {
        try {
            Desktop.getDesktop().browse(uri);
        } catch (final IOException e) {
            e.printStackTrace();
        }
    }

}

Nodes can be added like so:

node_1.add(new DefaultMutableTreeNode(new LeafNodeObject("Test", new URI("www.eclipse.org"))));

And in our listener (slightly modified from the link above):

final MouseListener ml = new MouseAdapter() {
    public void mousePressed(final MouseEvent e) {
        final int selRow = tree.getRowForLocation(e.getX(), e.getY());
        final TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
        if (selRow != -1) {
            final DefaultMutableTreeNode node = (DefaultMutableTreeNode) selPath.getLastPathComponent();
            if (e.getClickCount() == 1) {
                // Single click
            } else if (e.getClickCount() == 2) {
                // Double click
                ((LeafNodeObject) node.getUserObject()).onDoubleClick();
            }
        }
    }
};

Now when there is a double-click event, we call the onDoubleClick() method on the LeafNodeObject that we've created.

This looks great, except that we will run into class casting issues with nodes that are not leaf nodes! To fix this, we can make a similar class for those nodes (eg. ParentNodeObject). In the interest of good OOP, we should recognize the common behavior of these two classes (onDoubleClick(), and also an onSingleClick()), and create an interface for the shared ability.

For example:

public interface NodeObject {
    public void onSingleClick();
    public void onDoubleClick();
}

Now we can have our two classes implement this interface:

public class ParentNodeObject implements NodeObject {
    private final String display;

    public ParentNodeObject(final String display) {
        this.display = display;
    }

    /**
     * Override so that we control what is display on the Node
     */
    @Override
    public String toString() {
        return display;
    }

    @Override
    public void onSingleClick() {
        // Do nothing
    }

    @Override
    public void onDoubleClick() {
        // Do nothing
    }
}

public class LeafNodeObject implements NodeObject {
    private final URI uri;

    public LeafNodeObject(final String display, final URI uri) {
        this.display = display;
        this.uri = uri;
    }

    /**
     * Override so that we control what is display on the Node
     */
    @Override
    public String toString() {
        return display;
    }

    @Override
    public void onSingleClick() {
        // Do nothing
    }

    @Override
    public void onDoubleClick() {
        try {
            Desktop.getDesktop().browse(uri);
        } catch (final IOException e) {
            e.printStackTrace();
        }
    }

}

As a result, in our listener we no longer need to know (and no longer care) whether the node is a parent or leaf node. We can simply call either onSingleClick() or onDoubleClick(), and the implementation handles the rest!

if (e.getClickCount() == 1) {
    // Single click
    ((NodeObject) node.getUserObject()).onSingleClick();
} else if (e.getClickCount() == 2) {
    // Double click
    ((NodeObject) node.getUserObject()).onDoubleClick();
}

Back in your code, the parent and child nodes can be added like so:

node_1 = new DefaultMutableTreeNode(new ParentNodeObject("Helpdesk Norcic"));
node_1.add(new DefaultMutableTreeNode(new LeafNodeObject("Test", new URI("www.eclipse.org"))));

Now, when you single or double-click any node, either the onSingleClick() or onDoubleClick() methods will also be called. In this case we only care about what happens when there is a double-click on the leaf nodes, so we only need to fill in that method.

Upvotes: 2

Related Questions