JTable с кнопкой «закрыть» в заголовке столбца - PullRequest
8 голосов
/ 10 февраля 2011

Я пытаюсь создать таблицу с пользовательскими заголовками столбцов.Я хочу, чтобы заголовки столбцов включали кнопку, на которую пользователи могут нажимать.Функцией кнопки будет удаление столбца из таблицы.По сути, я пытаюсь построить что-то вроде this .

Вот мой код:

public class CustomColumnHeadersTable {

    private static String[] columnNames = {
        "Column 1", "Column 2", "Column 3"
    };
    private static String[][] data = {
        {"A", "B", "C"},
        {"D", "E", "F"},
        {"G", "H", "I"}
    };

    public CustomColumnHeadersTable() {
        DefaultTableModel model = new DefaultTableModel((Object[][]) data, columnNames);
        JTable table = new JTable(model);
        JScrollPane scrollPane = new JScrollPane(table);

        //set Header Renderer of each column to use the Custom renderer
        Enumeration enumeration = table.getColumnModel().getColumns();
        while (enumeration.hasMoreElements()) {
            TableColumn aColumn = (TableColumn) enumeration.nextElement();
            aColumn.setHeaderRenderer(new CustomColumnCellRenderer());
        }

        JFrame frame = new JFrame();
        frame.getContentPane().add(scrollPane, BorderLayout.CENTER);

        frame.setPreferredSize(new Dimension(300, 150));
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        CustomColumnHeadersTable ccht = new CustomColumnHeadersTable();
    }
}

class CustomColumnCellRenderer implements TableCellRenderer {

    private static String iconURL = "http://www.accessdubuque.com/images/close_icon.gif";
        //using a URL for the icon, so I don't have to upload the icon with the question
    private static Dimension buttonSize = new Dimension(16, 16);
    private static Dimension buttonBoxSize = new Dimension(16, 16);
    private static Border panelBorder = BorderFactory.createRaisedBevelBorder();

    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
        JPanel panel = new JPanel();
        JLabel label = new JLabel();
        JButton button = new JButton();
        Box buttonBox = Box.createHorizontalBox();
        BorderLayout layout = new BorderLayout();

        label.setText(table.getColumnName(column));

        try { button.setIcon(new ImageIcon(new URL(iconURL))); }
        catch (MalformedURLException ex) {
            Logger.getLogger(CustomColumnCellRenderer.class.getName()).log(Level.SEVERE, null, ex);
        }

        //set size of the button and it's box
        button.setMaximumSize(buttonSize);
        button.setSize(buttonSize);
        button.setPreferredSize(buttonSize);
        buttonBox.setMaximumSize(buttonBoxSize);
        buttonBox.setSize(buttonBoxSize);
        buttonBox.setPreferredSize(buttonBoxSize);

        button.addMouseListener(new CustomMouseListener()); //doesn't work...

        buttonBox.add(button);
        panel.add(label, BorderLayout.CENTER);
        panel.add(buttonBox, BorderLayout.EAST);

        panel.setBorder(panelBorder);

        return panel;
    }
}

class CustomMouseListener implements MouseListener
{
    public void mouseClicked(MouseEvent e) { System.out.println("Mouse Clicked."); }
    public void mousePressed(MouseEvent e) { System.out.println("Mouse Pressed."); }
    public void mouseReleased(MouseEvent e) { System.out.println("Mouse Released."); }
    public void mouseEntered(MouseEvent e) { System.out.println("Mouse Entered."); }
    public void mouseExited(MouseEvent e) { System.out.println("Mouse Exited."); }
}

По умолчанию, если я правильно понимаю, JTable использует JLabel для рендерингазаголовки столбцов.Моя идея состоит в том, чтобы использовать пользовательскую реализацию TableCellRenderer и создать свой собственный заголовок столбца из нескольких компонентов, а именно JPanel, который содержит JLabel и JButton.Я строю и возвращаю это в функции getTableCellRendererComponent (...).

Визуально это работает.Проблема в том, что я не могу обнаружить щелчки мышью на кнопке (или, если на то пошло, на панели, которая ее удерживает).Простое добавление MouseListener к кнопке не работает.Событие никогда не достигает этого.

Я нашел несколько похожих вещей в Интернете, но они не достигают нужной мне функциональности.

Во-первых, есть пример того, как поместить JCheckBox взаголовок, здесь:

http://java-swing-tips.blogspot.com/2009/02/jtableheader-checkbox.html

Проблема в том, что весь заголовок является флажком.Нажатие на флажок или на соответствующую метку производит тот же эффект.Таким образом, невозможно отсортировать столбец.Я хотел бы сделать так, чтобы при нажатии на метку сортировался столбец, а при нажатии на кнопку закрытия столбец удалялся из таблицы.Другими словами, заголовок должен иметь две отдельные области с отдельными обработчиками событий мыши.

Я нашел другой пример здесь:

http://www.devx.com/getHelpOn/10MinuteSolution/20425/1954?pf=true

Это включает в себя размещение JButtons в ячейках таблицы,затем обнаружение щелчков мышью на самой таблице, вычисление столбца и строки, где произошел щелчок, и отправка события соответствующей кнопке.

У этого тоже есть несколько проблем.Во-первых, кнопки находятся в ячейках, а не в заголовках.И во-вторых, это опять-таки только один компонент, а не несколько компонентов внутри JPanel.Хотя в этом примере я получил представление о распределении событий, я не могу заставить его работать для составного компонента.

Я попробовал другой подход.Я рассуждал, что если я смогу получить координаты кнопок закрытия, то, зная координаты щелчка мышью, я могу рассчитать, какая кнопка была нажата, и соответствующим образом распределить событие.Я провел несколько тестов и обнаружил, что компоненты внутри заголовка таблицы на самом деле не расположены на экране.

Я добавил статическую переменную JButton в свой основной (публичный) класс и сделал класс, реализующий TableCellRenderer, внутренним.класс основного класса.В getTableCellRendererComponent (...) перед возвратом я назначаю только что созданный JButton этой статической переменной.Таким образом, я могу справиться с этим, так сказать.Затем в основном я попытался использовать getX (), getY (), getWidth (), getHeight () и getLocationOnScreen (), используя эту статическую переменную.X, Y, Ширина и Высота все возвращают 0.GetLocationOnScreen () завершает работу программы, говоря, что компонент должен присутствовать на экране, чтобы эта функция работала.

Код для этого выглядит примерно так:

    public class CustomColumnHeadersTable {

        private static JButton static_button;
        ///the rest as before....

"Класс CustomColumnCellRendererреализует TableCellRenderer "становится внутренним классом CustomColumnHeadersTable.Для этого я должен отказаться от статических переменных в CustomColumnCellRenderer, поэтому я не стал беспокоиться о значках, URL-адресах или о чем-либо подобном.Вместо кнопки со значком, я просто использовал простую кнопку с надписью «КНОПКА» ...

Далее, внутри getTableCellRendererComponent (...), перед оператором возврата я сделал

static_button = button;

И, наконец, внутри main () я попытался сделать это:

    System.out.println("X: " + static_button.getX());
    System.out.println("Y: " + static_button.getY());
    System.out.println("W: " + static_button.getWidth());
    System.out.println("H: " + static_button.getHeight());
    System.out.println("LOC: " + static_button.getLocation());
    System.out.println("LOC on screen: " + static_button.getLocationOnScreen());

Вывод выглядит так:

X: 0
Y: 0
W: 0
H: 0
LOC: java.awt.Point[x=0,y=0]
Exception in thread "main" java.awt.IllegalComponentStateException: component must be showing on the screen to determine its location
at java.awt.Component.getLocationOnScreen_NoTreeLock(Component.java:1943)
at java.awt.Component.getLocationOnScreen(Component.java:1917)
...

Другими словами, кнопка имеетвсе размеры 0, и в соответствии с Java это на самом деле не находится на экране (хотя я вижу это ...).Вызов getLocationOnScreen () приводит к сбою программы.

Так что, пожалуйста, помогите, если можете.Может кто знает как это сделать.Может быть, вы могли бы просто предложить другой подход, чтобы попробовать.Или, может быть, вы знаете, что это вообще невозможно ...

Спасибо за вашу помощь.

Ответы [ 3 ]

2 голосов
/ 10 февраля 2011

Да, я был удивлен, когда впервые попытался сделать что-то похожее, например, положить JButton s в JList - это не совсем работает по причинам, которые вы указали. Правильный способ сделать то, что я хотел, использовать список LayoutManager, чтобы поместить JButton s в список.

В вашем случае я бы попытался использовать комбинацию JLabel s и MouseListeners, которая, я считаю, будет работать правильно. Использование JLabel в качестве заголовка позволит вам предоставить как изображение, так и текст, и вы можете зарегистрировать MouseListener на JLabel. Конечно, вы не получите такие же визуальные эффекты, как при нажатии JButton (то есть кнопка нажата), но это позволит использовать те же функции.

Другая вещь, на которую следует обратить внимание, это использование статических JButton s (или любых JComponent). Проблема заключается в том, что координаты JButton хранятся в самом JButton, что означает, что вы не можете использовать один и тот же JButton в разных местах. Другими словами, если вы попытаетесь добавить один и тот же static JButton в список несколько раз, вы не увидите несколько JButton s, вы просто увидите JButton в том месте, где вы его добавили в последний раз. Правильный способ достижения желаемого эффекта - вместо этого сохранять статическую ссылку на содержимое JButton, а именно на изображение и текст, вместо самого JButton. Затем просто создайте новый экземпляр JButton, используя изображение и текст, и поместите его в любое место. В общем, лучше держаться подальше от статических ссылок на компоненты Swing, так как невозможно иметь несколько экземпляров одного и того же компонента.

Качели не так часто качаются. Надеюсь, я предоставил некоторую полезную информацию - продолжайте бороться за хороший бой!

1 голос
/ 02 августа 2012

Вы можете использовать JXTable (из палитры swingx), у которого есть эта опция, чтобы «отключить» определенный столбец из вашей таблицы.

Андрей Ионут Апопей

0 голосов
/ 22 августа 2012

Я получил решение:

public class CustomColumnHeadersTable {

    public static Vector<JButton> buttons =  new Vector<JButton>();

    private static String[] columnNames = {
            "Column 1", "Column 2", "Column 3"
    };
    private static String[][] data = {
            {"A", "B", "C"},
            {"D", "E", "F"},
            {"G", "H", "I"}
    };

    class ColumnHeaderListener extends MouseAdapter {
        public void mouseClicked(MouseEvent evt) {
            JTable table = ((JTableHeader) evt.getSource()).getTable();
            TableColumnModel colModel = table.getColumnModel();

            int index = colModel.getColumnIndexAtX(evt.getX());
            if (index == -1) {
                return;
            }
            Rectangle headerRect = table.getTableHeader().getHeaderRect(index);

            if( 1== index){
                if(headerRect.contains(evt.getX() , evt.getY())){
                    int xx = evt.getX() - headerRect.x ;

                    for (int i = 0; i < buttons.size(); i++) {
                        JButton  button = buttons.get(i);
                        Rectangle re = button.getBounds();
                        if(re.contains( xx, evt.getY())){
                            System.out.println("Bingle");
                        }

                    }
                }
            }
        }
    }

    public CustomColumnHeadersTable() {
        DefaultTableModel model = new DefaultTableModel((Object[][]) data, columnNames);
        JTable table = new JTable(model);
        JScrollPane scrollPane = new JScrollPane(table);

        JTableHeader header = table.getTableHeader();
        header.addMouseListener(new ColumnHeaderListener());

        //set Header Renderer of each column to use the Custom renderer
        Enumeration enumeration = table.getColumnModel().getColumns();
        while (enumeration.hasMoreElements()) {
            TableColumn aColumn = (TableColumn) enumeration.nextElement();
            aColumn.setHeaderRenderer(new CustomColumnCellRenderer(buttons));
        }

        JFrame frame = new JFrame();
        frame.getContentPane().add(scrollPane, BorderLayout.CENTER);

        frame.setPreferredSize(new Dimension(300, 150));
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        CustomColumnHeadersTable ccht = new CustomColumnHeadersTable();
    }
}

class CustomColumnCellRenderer implements TableCellRenderer {

    private static String iconURL = "http://www.accessdubuque.com/images/close_icon.gif";
    //using a URL for the icon, so I don't have to upload the icon with the question
    private static Dimension buttonSize = new Dimension(16, 16);
    private static Dimension buttonBoxSize = new Dimension(16, 16);
    private static Border panelBorder = BorderFactory.createRaisedBevelBorder();

    Vector<JButton> buttons = null;

    JPanel panel = new JPanel();
    JLabel label = new JLabel();
    JButton button = new JButton();

    CustomColumnCellRenderer( Vector<JButton> buttons) {
        this.buttons  = buttons;

        try { button.setIcon(new ImageIcon(new URL(iconURL))); }
        catch (MalformedURLException ex) {
            Logger.getLogger(CustomColumnCellRenderer.class.getName()).log(Level.SEVERE, null, ex);
        }

        //set size of the button and it's box
        button.setMaximumSize(buttonSize);
        button.setSize(buttonSize);
        button.setPreferredSize(buttonSize);

        BorderLayout layout = new BorderLayout();
        panel.setLayout(layout);
        panel.add(label, BorderLayout.CENTER);
        panel.add(button, BorderLayout.EAST);
        panel.setBorder(panelBorder);

        buttons.add(button);
    }

    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {

        label.setText(table.getColumnName(column));
        return panel;
    }
}
...