8

我正在尝试创建一个带有自定义列标题的表。我希望列标题包含一个用户可以单击的按钮。该按钮的功能是从表中删除列。本质上,我正在尝试构建这样的东西

这是我的代码:

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 实现,并用几个组件构建我自己的列标题,即包含 JLabel 和 JButton 的 JPanel。我在 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(...) 中,就在 return 语句之前,我做了

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() 会使程序崩溃。

所以,如果可以的话,请帮忙。也许有人知道如何做到这一点。也许你可以建议一些其他的方法来尝试。或者,也许你知道这根本不可能……

谢谢您的帮助。

4

3 回答 3

2

是的,当我第一次尝试做类似的事情时,我很惊讶,比如将JButtons 放入 a JList- 由于您所说的原因,它不太有效。做我想做的事情的正确方法是使用类似列表的LayoutManager方式将JButtons 放入列表中。

在你的情况下,我会尝试使用JLabels 和的组合MouseListeners,我相信它会正常工作。使用 aJLabel作为标题将允许您提供图像和文本,并且您可以MouseListenerJLabel. 当然,您不会获得与单击 a 时相同的视觉效果JButton(即按下按钮),但它会允许相同的功能。

要注意的另一件事是使用 static JButtons(或 any JComponent)。这样做的问题是 的坐标JButton存储在JButton自身中,这意味着您不能JButton在不同的地方使用相同的坐标。换句话说,如果您尝试static JButton多次将相同添加到列表中,您将不会看到多个JButtons,您只会JButton在上次添加它的位置看到 。实现所需效果的正确方法是保持对内容的静态引用JButton- 即图像和文本 - 而不是JButton本身。然后,只需实例化一个新的JButton使用图像和文本并将其放置在您想要的任何位置。一般来说,最好不要保留对 Swing 组件的静态引用,因为这样就不可能拥有同一个组件的多个实例。

摇摆有时不是那么摇摆。希望我提供了一些有用的信息——继续打好仗!

于 2011-02-10T19:48:38.187 回答
1

您可以使用具有该选项的 JXTable(来自 swingx 调色板)来“禁用”表中的特定列。

安德烈·约努特·阿波佩

于 2012-08-02T13:14:20.783 回答
0

我得到了解决方案:

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;
    }
}
于 2012-08-22T10:25:37.163 回答