3

I'm trying to create a JTree in which some nodes are compound objects containing a JLabel and a JButton. The Node is representing a server and port shown by the JLabel, the JButton will use the Desktop API to open the default browser and go to the URL.

I have read the following already and have followed them as closely as I can. The Node is displayed how I want it (mostly - I can deal with making it nicer later) but when I try to click on the button the JTree is responding to the events, not the button.

java swing: add custom graphical button to JTree item

http://www.java2s.com/Code/Java/Swing-JFC/TreeCellRenderer.htm

https://stackoverflow.com/a/3769158/1344282

I need to know how to allow the events to pass through the JTree so that they are handled by the object(s) underneath - the JButton or JLabel.

Here is my TreeCellEditor:

public class UrlValidationCellEditor extends DefaultTreeCellEditor
{
    public UrlValidationCellEditor(JTree tree, DefaultTreeCellRenderer renderer)
    {
        super(tree, renderer);
    }

    @Override
    public Component getTreeCellEditorComponent(JTree tree, Object value,
            boolean isSelected, boolean expanded, boolean leaf, int row)
    {
        return renderer.getTreeCellRendererComponent(tree, value, true, expanded, leaf, row, true);
    }

    @Override
    public boolean isCellEditable(EventObject anEvent)
    {
        return true; // Or make this conditional depending on the node
    }
}

Here is the TreeCellRenderer:

public class UrlValidationRenderer extends DefaultTreeCellRenderer implements TreeCellRenderer
{
    JLabel titleLabel;
    UrlGoButton goButton;

    JPanel renderer;

    DefaultTreeCellRenderer defaultRenderer = new DefaultTreeCellRenderer();

    public UrlValidationRenderer()
    {
        renderer = new JPanel(new GridLayout(1, 2));
        titleLabel = new JLabel(" ");
        titleLabel.setForeground(Color.blue);
        renderer.add(titleLabel);
        goButton = new UrlGoButton();
        renderer.add(goButton);
        renderer.setBorder(BorderFactory.createLineBorder(Color.black));
        backgroundSelectionColor = defaultRenderer
            .getBackgroundSelectionColor();
        backgroundNonSelectionColor = defaultRenderer
            .getBackgroundNonSelectionColor();
    }

    @Override
    public Component getTreeCellRendererComponent(JTree tree, Object value,
      boolean selected, boolean expanded, boolean leaf, int row,
      boolean hasFocus)
    {
        Component returnValue = null;
        if ((value != null) && (value instanceof DefaultMutableTreeNode))
        {
          Object userObject = ((DefaultMutableTreeNode) value)
              .getUserObject();
          if (userObject instanceof UrlValidation)
          {
                UrlValidation validationResult = (UrlValidation) userObject;
                titleLabel.setText(validationResult.getServer()+":"+validationResult.getPort());
                goButton.setUrl(validationResult.getUrl());

                if (selected) {
                  renderer.setBackground(backgroundSelectionColor);
                } else {
                  renderer.setBackground(backgroundNonSelectionColor);
                }
                renderer.setEnabled(tree.isEnabled());
                returnValue = renderer;
          }
        }
        if (returnValue == null)
        {
          returnValue = defaultRenderer.getTreeCellRendererComponent(tree,
              value, selected, expanded, leaf, row, hasFocus);
        }
        return returnValue;
    }

}

I would appreciate any insight or suggestions. Thanks!

Community
  • 1
  • 1
Kal
  • 223
  • 4
  • 11
  • What should happen when the user selects that tree node, but not the button? – Andrew Thompson Apr 22 '12 at 02:56
  • Well, the node should have 2 parts a label and a button. When the user clicks the label then some detailed information about the node should appear in a different part of the GUI. When the user clicks the button it should result in a browser window opening. I'm not sure how to prevent the JTree from responding to the user input so that when the user clicks on the label or button those events get handled by the label or button respectively. – Kal Apr 22 '12 at 15:05

2 Answers2

11

The renderers do not work this way. They are used as rubber stamps, which means that there is really only one instance of renderer that is painted all over the place as the JList is painted. So it cannot handle mouse inputs, as the objects are not really there - they are just painted.

In order to pass mouse events to objects underneath, you need to implement a cell editor. Sometimes, the editor looks different than the renderer (String renderers are labels, editors are textfields, for example). Following this logic, the editor must be implemented using another instance of a component.

Now you are going to render buttons and use them for manipulating (ie. editing). The editor then must be another instance of JButton, distinctive from the renderer. Implementing button as renderer is easy, but the tricky part is to implement is as an editor. You need to extend AbstractCellEditor and implement TreeCellEditor and ActionListener. The button is then a field of the editor class. In the constructor of the editor class, you initialize the button and add this as a new action listener for the button. In the getTreeCellEditorComponent method, you just return the button. In the actionPerformed, you call whatever code you need to do on button press and then call stopCellEditing().

This way it works for me.

I made a SSCCE that demonstrates the usage on a String Tree

public class Start
{
    public static class ButtonCellEditor extends AbstractCellEditor implements TreeCellEditor, ActionListener, MouseListener
    {
        private JButton button;
        private JLabel label;
        private JPanel panel;
        private Object value;

        public ButtonCellEditor(){
            panel = new JPanel(new BorderLayout());
            button = new JButton("Press me!");
            button.addActionListener(this);
            label = new JLabel();
            label.addMouseListener(this);
            panel.add(button, BorderLayout.EAST);
            panel.add(label);
        }

        @Override public Object getCellEditorValue(){
            return value.toString();
        }

        @Override public void actionPerformed(ActionEvent e){
            String val = value.toString();
            System.out.println("Pressed: " + val);
            stopCellEditing();
        }

        @Override public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row){
            this.value = value;
            label.setText(value.toString());
            return panel;
        }

        @Override public void mouseClicked(MouseEvent e){
        }

        @Override public void mousePressed(MouseEvent e){
            String val = value.toString();
            System.out.println("Clicked: " + val);
            stopCellEditing();
        }

        @Override public void mouseReleased(MouseEvent e){
        }

        @Override public void mouseEntered(MouseEvent e){
        }

        @Override public void mouseExited(MouseEvent e){
        }

    }

    public static class ButtonCellRenderer extends JPanel implements TreeCellRenderer
    {
        JButton button;
        JLabel label;

        ButtonCellRenderer(){
            super(new BorderLayout());
            button = new JButton("Press me!");
            label = new JLabel();
            add(button, BorderLayout.EAST);
            add(label);
        }

        @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus){
            label.setText(value.toString());
            return this;
        }

    }

    public static void main(String[] args){
        JTree tree = new JTree();
        tree.setEditable(true);
        tree.setCellRenderer(new ButtonCellRenderer());
        tree.setCellEditor(new ButtonCellEditor());

        JFrame test = new JFrame();
        test.add(new JScrollPane(tree));
        test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        test.setSize(500, 500);
        test.setLocationRelativeTo(null);
        test.setVisible(true);
    }
}
Jakub Zaverka
  • 8,816
  • 3
  • 32
  • 48
  • Thanks for the example, however, I'm not quite sure how to apply this to my situation. You're using a JButton as the node in the JTree but I want to use a JPanel that contains a JLabel and JButton. I then want to allow the user to click on either the JLabel or JButton. – Kal Apr 22 '12 at 02:53
  • @Kal see edit. I believe that now you can adapt the example to your needs. – Jakub Zaverka Apr 22 '12 at 13:35
1

the node should have 2 parts a label and a button. When the user clicks the label then some detailed information about the node should appear in a different part of the GUI. When the user clicks the button it should result in a browser window opening. ..

Don't do it that way. Instead, have just the label in the tree. Add the button to the same GUI that displays the 'detailed information about the node'.

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • This would require an extra click, if the button is going to be used often, it would be better to have it inside the tree. Otherwise it would be better to have it like you describe, to make the GUI consistent. – Nulano Aug 24 '15 at 10:59