Search code examples
javaswingjlabeljtreegridbaglayout

Controlling the revalidation of an HTML JLabel


I have a Swing GUI challenge that I have distilled to the following SSCCE. The issue is that the JTree/JScrollPane jumps around in a disturbing manner when updating the JLabel at the bottom of the window to reflect the selected node.

The best way to see this behavior is to run the SSCCE, click in the JTree, and hold your keyboard's down-arrow to rapidly to traverse the JTree. The GUI flickers as if the JLabel is being removed and then added again. This isn't really the case, though -- I'm just updating the JLabel's text.

I'd like to control the redrawing of the GUI so that it isn't so jarring. I need to retain the word wrapping and dynamic height of the "status bar" -- using a fixed height component would solve the jumping, but defeats the design idea.

I've tried several different approaches to updating the "status bar" area (one of the approaches you can see commented out in the SSCCE's TreeSelectionListener), but everything I've tried either results in improper recalculation of the height or flickering.

enter image description here

import java.awt.*;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;

public class JTreePlusStatus extends JFrame {
    JPanel _contentPane;
    JScrollPane _jScrollPane;
    JTree _jTree;
    JLabel _jLabelDescription;
    GridBagConstraints _gbcJLabelDescription;

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    String sysLaf = UIManager.getSystemLookAndFeelClassName();
                    UIManager.setLookAndFeel(sysLaf);
                    JFrame frame = new JTreePlusStatus();
                    frame.setSize(300, 300);
                    frame.setLocationRelativeTo(null);
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public JTreePlusStatus() {
        initComponents();
        initCustom();
    }

    void initComponents() {
        // Boilerplate layout busywork
        _contentPane = new JPanel();
        GridBagLayout gblContentPane = new GridBagLayout();
        _contentPane.setLayout(gblContentPane);
        setContentPane(_contentPane);

        _jScrollPane = new JScrollPane();
        GridBagConstraints gbcJScrollPane = new GridBagConstraints();
        gbcJScrollPane.fill = GridBagConstraints.BOTH;
        gbcJScrollPane.weighty = 1.0;
        gbcJScrollPane.weightx = 1.0;
        gbcJScrollPane.gridx = 0;
        gbcJScrollPane.gridy = 0;
        _contentPane.add(_jScrollPane, gbcJScrollPane);

        _jTree = new JTree();
        _jScrollPane.setViewportView(_jTree);
        _jTree.addTreeSelectionListener(new TreeSelectionListenerImpl());

        _jLabelDescription = new JLabel(" ");
        _gbcJLabelDescription = new GridBagConstraints();
        _gbcJLabelDescription.fill = GridBagConstraints.HORIZONTAL;
        _gbcJLabelDescription.gridx = 0;
        _gbcJLabelDescription.gridy = 1;
        _contentPane.add(_jLabelDescription, _gbcJLabelDescription);
    }

    void initCustom() {
        // Set sample data onto the JTree
        _jTree.setModel(createSampleModel());

        // Expand entire tree
        for (int i = 0; i < _jTree.getRowCount(); i++) {
            _jTree.expandRow(i);
        }
    }

    public DefaultTreeModel createSampleModel() {
        DefaultMutableTreeNode top = new DefaultMutableTreeNode("Music");
        for (int i = 0; i < 100; i++) {
            String s = "Classical - Concertos - Beethoven "
                    + "- S No. 5 - E-Flat Major " + i;
            top.add(new DefaultMutableTreeNode(s));
        }
        DefaultTreeModel defaultTreeModel = new DefaultTreeModel(top);
        return defaultTreeModel;
    }

    class TreeSelectionListenerImpl implements TreeSelectionListener {

        public void valueChanged(TreeSelectionEvent e) {
            TreePath selectionPath = _jTree.getSelectionPath();
            String s = selectionPath.getLastPathComponent().toString();
            String newText = "<html><b>Name:</b> " + s 
                    + " <br><b>Description: </b>" + s + "</html>";

            /** Tried this -- causes the JTree to jump around */
            _jLabelDescription.setText(newText);

            /**
             * OK, try making a new JLabel and "set" it into the parent,
             * hoping that there won't be an intermediate state where label's
             * space in the GUI collapses temporarily
             */
            //final JLabel newJLabel = new JLabel(newText);
            //final Container parent = _jLabelDescription.getParent();
            //parent.add(newJLabel, _gbcJLabelDescription, 1);
            //parent.remove(_jLabelDescription);
            //if (parent instanceof JComponent) {
            //    JComponent jc = (JComponent) parent;
            //    jc.validate(); // must revalidate after adding or removing
            //}
            //_jLabelDescription = newJLabel;

            // No luck, JTree/JScrollPane still jumps around
        }
    }
}

Solution

  • Seems to work ok when using a JTextPane instead of a JLabel:

        _jLabelDescription = new JTextPane();
        _jLabelDescription.setContentType( "text/html" );