Placing JToggleButton with JPanel within into a JTable cell
Asked Answered
M

2

5

I need to have a JToggleButton (that has custom background) that contains a JPanel with several JLabels within itself. That part works.

This button is placed afterwards in a JTable cell and is meant to be pressed by users. The problem is that i can only press the button on the second click. Apperenty on the first click the focus first jumps to the panel with JLabels and only afterwards to the actual button.

I tried several things to try solving this issue, but the same issue persists. A) placing the JPanel with labels directly onto the JToggleButton#add(). B) using JLayeredPane to place Button and JPanel onto different Layers where JToggleButton takes constraint Integer(-) so that the JPanel with JLabels stays visible on top

Do you have any tips? Thanks

Below is a sample code that illustrates the problem. Clicking on the button only works second time.

public class ClickableCustomButtonInTable extends JToggleButton {

public ClickableCustomButtonInTable() {
    Dimension d = new Dimension(100, 100);
    JLabel lFirst = new JLabel("1st label");
    lFirst.setPreferredSize(d);

    JLabel lSecond = new JLabel("2nd label");
    lSecond.setPreferredSize(d);

    JPanel panel = new JPanel();
    panel.setOpaque(true);

    panel.setLayout(new BorderLayout());
    panel.add(lFirst, BorderLayout.NORTH);
    panel.add(lSecond, BorderLayout.SOUTH);
    add(panel);
    addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("Button clicked");
        }
    });
}

private static class CustomButtonRenderer implements TableCellRenderer {

    private final ClickableCustomButtonInTable button = new ClickableCustomButtonInTable();

    @Override
    public Component getTableCellRendererComponent(JTable table,
            Object value, boolean isSelected, boolean hasFocus, int row,
            int column) {
        return button;
    }
}

private static class CustomButtonEditor extends AbstractCellEditor
        implements TableCellEditor {

    private final ClickableCustomButtonInTable button = new ClickableCustomButtonInTable();

    @Override
    public Object getCellEditorValue() {
        return button.getText();
    }

    @Override
    public Component getTableCellEditorComponent(JTable table,
            Object value, boolean isSelected, int row, int column) {
        return button;
    }

}

public static void main(String[] args) {
    JFrame frame = new JFrame();
    frame.setSize(new Dimension(200, 200));
    Container content = frame.getContentPane();
    TableModel model = new AbstractTableModel() {

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            return null;
        }

        @Override
        public int getRowCount() {
            return 1;
        }

        @Override
        public int getColumnCount() {
            return 1;
        }

        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex) {
            return true;
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return ClickableCustomButtonInTable.class;
        }
    };

    JTable table = new JTable(model);
    // table.setBounds(new Rectangle(0, 0, content.getWidth(), content
    // .getHeight()));
    table.setRowHeight(frame.getHeight());
    table.setDefaultRenderer(ClickableCustomButtonInTable.class,
            new CustomButtonRenderer());
    table.setDefaultEditor(ClickableCustomButtonInTable.class,
            new CustomButtonEditor());

    content.add(table);
    content.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
}
}
Microphyte answered 6/3, 2012 at 17:5 Comment(6)
not directly related to your problem: the editor implementation is invalid: it must notify its listeners when edits are terminated for internal reasons. As to your problem: basically you have to a) make sure the editing is started on first click b) grab that click, calculate its position in editing component coordinates and dispatch to the "real" target component (f.i. the button) on the editing panelStarcrossed
BTW: you never-ever keep components in your tableModel ... as your TableModel implementation is fishy as well (it announces to be editable but missing any api to change the cell values), maybe you should read up on and understand some table basics before jumping right in the middle of the the not-so-trivial stuff :-)Starcrossed
@kleopatra, thanks for your tips. Could you maybe link/show how to a) make sure the editing is started on first click b) grab that click, calculate its position in editing component coordinates and dispatch to the "real" target component on the editing panel i am not sure what you mean by dispatching event to the real target, is it doClick() call what you mean. if so, i am afraid i will lose that click animation that jToggleButton provides.Microphyte
Would TablePopupEditor be an alternative?Correction
trashgod, not really. What i need is a clickable ToggleButton with some addition widgets placed onto it, no popups.Microphyte
not sure but you can get the bound of the ToggleButton and on the mouseListener even, check for the bounds and execute your operationPhonograph
M
0

http://www.coderanch.com/t/570021/GUI/java/click-event-custom-JToggleButton-JTable

Microphyte answered 13/3, 2012 at 20:25 Comment(3)
that most probably won't save your career ;-) Better learn how to correctly use tables/renderers/editors ...Starcrossed
everything is wrong, starting with some bullets in my comments to your question ...Starcrossed
I only mean this part ((ClickableCustomButtonInTable)(SwingUtilities.getAncestorOfClass(ClickableCustomButtonInTable.class,SwingUtilities.getDeepestComponentAt(table,me.getX(),me.getY())))).doClick(); The rest of the code was only an example, real code is different (but thanks for the tips on having component in a table model)Microphyte
P
9

When the table captures a mouse event to select a cell it passes the mouse event on to the deepest component regardless of whether that component can handle mouse events. In your example the first click ends up on one of the JLabels, bypassing the JToggleButton completely. Once the JToggleButton has become the active cell editor, mouse clicks work upon it normally. If it was to lose the focus, it would once again require two-clicks to activate.

You can also see this if you notice in your demo you click on the button border, not on the contained panel, the button works as desired.

One way to work around this is to ensure that any mouse event that is targeted at any component within the JToggleButton. You can do this using this static method:

static void addEventBubble(final Container target, Container container) {
    for(Component comp:container.getComponents()) {
        if (comp instanceof Container) {
            addEventBubble(target, (Container) comp);
        }
        comp.addMouseListener(new MouseAdapter() {
            private MouseEvent retarget(MouseEvent e) {
                return new MouseEvent(target, e.getID(), e.getWhen(),
                        e.getModifiers(), e.getX(), e.getY(),
                        e.getClickCount(), e.isPopupTrigger(),
                        e.getButton());
            }


            public void mousePressed(MouseEvent e) {
                MouseEvent r = retarget(e);
                for(MouseListener listen:target.getMouseListeners()) {
                    listen.mousePressed(r);
                }
            }


            public void mouseReleased(MouseEvent e) {
                MouseEvent r = retarget(e);
                for(MouseListener listen:target.getMouseListeners()) {
                    listen.mouseReleased(r);
                }
            }


            public void mouseClicked(MouseEvent e) {
                MouseEvent r = retarget(e);
                for(MouseListener listen:target.getMouseListeners()) {
                    listen.mouseClicked(r);
                }
            }
        });
    }
}

and then at the end of your constructor invoke:

addEventBubble(this,this);

After this any mouse event upon any component within the button will also reach the button and hence change its state. After doing this, I found the button reacted to every click as desired.

Pleonasm answered 13/3, 2012 at 2:30 Comment(2)
I was given another solution at coderanch.com/t/570021/GUI/java/… It's "brittle" but less code. I think I would stick with it then. Thank you for explanation and solution, I needed both so much.Microphyte
Having looked, "brittle" is indeed the word for it :) Thanks for sharing.Pleonasm
M
0

http://www.coderanch.com/t/570021/GUI/java/click-event-custom-JToggleButton-JTable

Microphyte answered 13/3, 2012 at 20:25 Comment(3)
that most probably won't save your career ;-) Better learn how to correctly use tables/renderers/editors ...Starcrossed
everything is wrong, starting with some bullets in my comments to your question ...Starcrossed
I only mean this part ((ClickableCustomButtonInTable)(SwingUtilities.getAncestorOfClass(ClickableCustomButtonInTable.class,SwingUtilities.getDeepestComponentAt(table,me.getX(),me.getY())))).doClick(); The rest of the code was only an example, real code is different (but thanks for the tips on having component in a table model)Microphyte

© 2022 - 2024 — McMap. All rights reserved.