Search code examples
javauser-interfaceswingx

Is it possible to use a JXBusyLabel as a cell rendered in a JTable?


I would like to use the JXBusyLabel in a cell to notify the user that an event is currently taking place for the row where the JXBusyLabel is. For example, double clicking a row to open it would trigger the anymation of the JXBusyLabel.

Does this make sense?

In case you are wondering what a JXBusyLabel is, please look here.

Thanks!

[EDIT] Solution based on @kleopatra answer:

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.Timer;
import javax.swing.table.DefaultTableModel;

import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.decorator.AbstractHighlighter;
import org.jdesktop.swingx.decorator.HighlightPredicate;
import org.jdesktop.swingx.decorator.PainterHighlighter;
import org.jdesktop.swingx.painter.BusyPainter;

public class TableBusyLabelTest {

    private JFrame frame;
    private JXTable table;
    private JButton button;

    private BusyPainter busyPainter;
    private AbstractHighlighter highlighter;
    private Timer timer;

    private boolean on = false;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    TableBusyLabelTest window = new TableBusyLabelTest();
                    window.frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the application.
     */
    public TableBusyLabelTest() {
        initialize();
    }

    /**
     * Initialize the contents of the frame.
     */
    private void initialize() {
        frame = new JFrame();
        frame.setBounds(100, 100, 450, 300);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        busyPainter = new BusyPainter(15);

        timer = new Timer(100, getTimerActionListener());

        highlighter = new PainterHighlighter(HighlightPredicate.NEVER,
                busyPainter);

        table = new JXTable();
        table.setModel(getTableExampleModel());
        // Tell which column will use the highlighter
        table.getColumnExt(0).addHighlighter(highlighter);

        button = new JButton("Start / Stop busy thing");
        button.addActionListener(getButtonActionListener());

        frame.getContentPane().add(table, BorderLayout.CENTER);
        frame.getContentPane().add(button, BorderLayout.SOUTH);
    }

    private DefaultTableModel getTableExampleModel() {
        return new DefaultTableModel(new Object[][] { { null, "Test1" },
                { null, "Test2" }, }, new String[] { "busy", "Name" });
    }

    private ActionListener getTimerActionListener() {
        return new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int frame = busyPainter.getFrame();
                frame = (frame + 1) % busyPainter.getPoints();
                busyPainter.setFrame(frame);
            }

        };
    }

    private ActionListener getButtonActionListener() {
        return new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {

                if (on) {
                    on = false;
                } else {
                    on = true;
                }

                // on a change that should toggle the busy-ness on/off
                if (on) {
                    highlighter
                            .setHighlightPredicate(HighlightPredicate.ALWAYS);
                    timer.start();
                } else {
                    highlighter.setHighlightPredicate(HighlightPredicate.NEVER);
                    timer.stop();
                }
            }
        };
    }

}

Solution

  • Depending on your exact use case, you wouldn't want to use the JXBusyLabel as the rendering component (you wouldn't get the anitmation, as it's a renderer), all you need is a PainterHighlighter configured with a BusyPainter whose frame property is controlled by a timer. Whether or not the painter is visible should be bound to some property of your data which triggers the on/off of the highlighters HighlightPredicate.

    For an example, please see PainterVisualCheck interactiveAnimatedBusyPainterHighlight in the swingx test hierarchy, package renderer. Something like:

        BusyPainter painter = new BusyPainter();
        ActionListener l = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                int frame = busyPainter.getFrame();
                frame = (frame+1)%busyPainter.getPoints();
                busyPainter.setFrame(frame);
            }
    
        };
        Timer timer = new Timer(delay, l);
        AbstractHighlighter hl = new PainterHighlighter(HighlightPredicate.NEVER, painter);
        table.getColumnExt().addHighlighter(hl);
    
        // on a change that should toggle the busy-ness on/off
        if (on) {
             hl.setHighlightPredicate(HighlightPredicate.ALWAYS);
             timer.start();
        } else {
             hl.setHighlightPredicate(HighlightPredicate.NEVER);
             timer.stop(); 
        }
    

    Edit (answering the extended question):

    to activate the busy-highlighter only for a particular condition, implement a custom HighlightPredicate and set that instead of the ALWAYS. F.i. a specific row in the column:

        HighlightPredicate predicate = new HighlightPredicate() {
    
            @Override
            public boolean isHighlighted(Component renderer,
                    ComponentAdapter adapter) {
    
                return 
                   adapter.convertRowIndexToModel(adapter.row) == mySpecialRow;
            }
    
        };