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);
}
}
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
d56
  • 825
  • 1
  • 9
  • 26
  • 1
    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 panel – kleopatra Mar 12 '12 at 09:15
  • 2
    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 :-) – kleopatra Mar 12 '12 at 09:18
  • @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. – d56 Mar 12 '12 at 09:25
  • Would [`TablePopupEditor`](http://stackoverflow.com/a/3591230/230513) be an alternative? – trashgod Mar 12 '12 at 09:37
  • trashgod, not really. What i need is a clickable ToggleButton with some addition widgets placed onto it, no popups. – d56 Mar 12 '12 at 13:12
  • not sure but you can get the bound of the ToggleButton and on the mouseListener even, check for the bounds and execute your operation – Abhishek Choudhary Mar 12 '12 at 17:44

2 Answers2

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.

Simon G.
  • 6,587
  • 25
  • 30
  • I was given another solution at http://www.coderanch.com/t/570021/GUI/java/click-event-custom-JToggleButton-JTable 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. – d56 Mar 13 '12 at 20:24
  • Having looked, "brittle" is indeed the word for it :) Thanks for sharing. – Simon G. Mar 13 '12 at 23:44
0

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

d56
  • 825
  • 1
  • 9
  • 26
  • 1
    that most probably won't save your career ;-) Better learn how to _correctly_ use tables/renderers/editors ... – kleopatra Mar 15 '12 at 16:15
  • everything is wrong, starting with some bullets in my comments to your question ... – kleopatra Mar 19 '12 at 09:29
  • 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) – d56 Mar 19 '12 at 09:47