突出显示 JTable 的列标题

Posted

技术标签:

【中文标题】突出显示 JTable 的列标题【英文标题】:Highlighting a column header of a JTable 【发布时间】:2013-03-07 13:21:20 【问题描述】:

我目前正在构建一个小 JTable,并希望在选择单元格时突出显示列标题(和行标题 - 行标题部分实际上正在工作),以便更容易找到与该单元格相关联的名称.这是一张图片:

我已经尝试用这个来切换标题的渲染器:

table.getTableHeader().setDefaultRenderer(new ColumnHeaderRenderer());

但只有当我点击标题时才会调用它,并且总是说 isSelected 是假的。

这是我用于行名的代码,包括渲染器内的突出显示 - 代码不是我写的,我只是稍微修改了一下:

/*
 *  Use a JTable as a renderer for row numbers of a given main table.
 *  This table must be added to the row header of the scrollpane that
 *  contains the main table.
 */
public class RowNameTable extends JTable
        implements ChangeListener, PropertyChangeListener 

    private JTable main;

    public RowNameTable(JTable table) 
        main = table;
        main.addPropertyChangeListener(this);

        setFocusable(false);
        setAutoCreateColumnsFromModel(false);
        setModel(main.getModel());
        setSelectionModel(main.getSelectionModel());

        TableColumn column = new TableColumn();
        column.setHeaderValue(" ");
        addColumn(column);
        column.setCellRenderer(new RowNameRenderer(main));

        getColumnModel().getColumn(0).setPreferredWidth(table.getColumnModel().getColumn(0).getPreferredWidth());
        setPreferredScrollableViewportSize(getPreferredSize());
    

    @Override
    public void addNotify() 
        super.addNotify();

        Component c = getParent();

        //  Keep scrolling of the row table in sync with the main table.

        if (c instanceof JViewport) 
            JViewport viewport = (JViewport) c;
            viewport.addChangeListener(this);
        
    

    /*
     *  Delegate method to main table
     */
    @Override
    public int getRowCount() 
        return main.getRowCount();
    

    @Override
    public int getRowHeight(int row) 
        return main.getRowHeight(row);
    

    /*
     *  This table does not use any data from the main TableModel,
     *  so just return a value based on the row parameter.
     */
    @Override
    public Object getValueAt(int row, int column) 
        return Integer.toString(row + 1);
    

    /*
     *  Don't edit data in the main TableModel by mistake
     */
    @Override
    public boolean isCellEditable(int row, int column) 
        return false;
    
//
//  Implement the ChangeListener
//

    public void stateChanged(ChangeEvent e) 
        //  Keep the scrolling of the row table in sync with main table

        JViewport viewport = (JViewport) e.getSource();
        JScrollPane scrollPane = (JScrollPane) viewport.getParent();
        scrollPane.getVerticalScrollBar().setValue(viewport.getViewPosition().y);
    
//
//  Implement the PropertyChangeListener
//

    public void propertyChange(PropertyChangeEvent e) 
        //  Keep the row table in sync with the main table

        if ("selectionModel".equals(e.getPropertyName())) 
            setSelectionModel(main.getSelectionModel());
        

        if ("model".equals(e.getPropertyName())) 
            setModel(main.getModel());
        
    

    /*
     *  Borrow the renderer from JDK1.4.2 table header
     */
    private static class RowNameRenderer extends DefaultTableCellRenderer 

        private JTable main;

        public RowNameRenderer(JTable main) 
            this.main = main;
            setHorizontalAlignment(JLabel.CENTER);
        

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) 
            if (table != null) 
                JTableHeader header = table.getTableHeader();

                if (header != null) 
                    setForeground(header.getForeground());
                    setBackground(header.getBackground());
                    setFont(header.getFont());
                
            

            if (isSelected) 
                setFont(getFont().deriveFont(Font.BOLD));
            

            setText((value == null) ? "" : main.getColumnName(row));
            setBorder(UIManager.getBorder("TableHeader.cellBorder"));

            return this;
        
    

这里我们有创建表的相关部分:

    costTableModel = new CostTableModel(costCalc);
    table = new JTable(costTableModel);
    table.setPreferredScrollableViewportSize(table.getPreferredSize());
    table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
    table.setCellSelectionEnabled(true);

    scrollPane = new JScrollPane(table);

    RowNameTable nameTable = new RowNameTable(table);
    scrollPane.setRowHeaderView(nameTable);

还有 costTableModel 类,只是为了完整起见:

public class CostTableModel extends AbstractTableModel 
    private CostCalculator costCalc;

    public CostTableModel(CostCalculator costCalc) 
        this.costCalc = costCalc;
    

    @Override
    public int getRowCount() 
        return costCalc.getPersonsList().size();
    

    @Override
    public int getColumnCount() 
        return costCalc.getPersonsList().size();
    

    @Override
    public String getColumnName(int col) 
        return costCalc.getPersonsList().get(col).getName();
    

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) 
        Person debtor = costCalc.getPersonsList().get(rowIndex);
        Person debtee = costCalc.getPersonsList().get(columnIndex);

        return costCalc.getAmountOwed(debtor, debtee);
    

    @Override
    public Class getColumnClass(int c) 
        return getValueAt(0, c).getClass();

    

提前感谢您的帮助!

【问题讨论】:

【参考方案1】:

我遇到的基本问题是表格标题和选择更改之间没有联系。事实上,标题真的很聪明,它的重绘......

我最终提供了我自己的标题,它将一个侦听器附加到表的选择模型并在选择更改时重新绘制标题。

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.RowSorter;
import javax.swing.RowSorter.SortKey;
import static javax.swing.SortOrder.ASCENDING;
import static javax.swing.SortOrder.DESCENDING;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;

public class TestColumnHighlight 

    public static void main(String[] args) 
        new TestColumnHighlight();
    

    public TestColumnHighlight() 
        EventQueue.invokeLater(new Runnable() 
            @Override
            public void run() 
                try 
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                 catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) 
                

                JTable table = new JTable();
                DefaultTableModel model = new DefaultTableModel(
                                new Object[]"abc", "def", "ghi", "jkl",
                                0);

                model.addRow(new Object[]0, 0, 0, 0);
                model.addRow(new Object[]0, 0, 0, 0);
                model.addRow(new Object[]0, 0, 0, 0);
                model.addRow(new Object[]0, 0, 0, 0);
                model.addRow(new Object[]0, 0, 0, 0);

                table.setModel(model);
                table.setTableHeader(new CustomTableHeader(table));
                table.getTableHeader().setDefaultRenderer(new ColumnHeaderRenderer());

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new JScrollPane(table));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            
        );
    

    public class CustomTableHeader extends JTableHeader 

        public CustomTableHeader(JTable table) 
            super();
            setColumnModel(table.getColumnModel());
            table.getColumnModel().getSelectionModel().addListSelectionListener(new ListSelectionListener() 
                @Override
                public void valueChanged(ListSelectionEvent e) 
                    repaint();
                
            );
        

        @Override
        public void columnSelectionChanged(ListSelectionEvent e) 
            repaint();
        

    

    public class ColumnHeaderRenderer extends DefaultTableHeaderCellRenderer 

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean focused, int row, int column) 
            super.getTableCellRendererComponent(table, value, selected, focused, row, column);

            int selectedColumn = table.getSelectedColumn();
            System.out.println("Selected " + selectedColumn + "-" + column);
            if (selectedColumn == column) 
                Color bg = table.getSelectionBackground();
                setBackground(bg);
                setOpaque(true);
             else 
                setOpaque(false);
            

            return this;
        

    

    public class DefaultTableHeaderCellRenderer extends DefaultTableCellRenderer 

        public DefaultTableHeaderCellRenderer() 
            setHorizontalAlignment(CENTER);
            setHorizontalTextPosition(LEFT);
            setVerticalAlignment(BOTTOM);
            setOpaque(false);
        

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
                        boolean isSelected, boolean hasFocus, int row, int column) 
            super.getTableCellRendererComponent(table, value,
                            isSelected, hasFocus, row, column);
            JTableHeader tableHeader = table.getTableHeader();
            if (tableHeader != null) 
                setForeground(tableHeader.getForeground());
            
            setIcon(getIcon(table, column));
            setBorder(UIManager.getBorder("TableHeader.cellBorder"));
            return this;
        

        protected Icon getIcon(JTable table, int column) 
            SortKey sortKey = getSortKey(table, column);
            if (sortKey != null && table.convertColumnIndexToView(sortKey.getColumn()) == column) 
                switch (sortKey.getSortOrder()) 
                    case ASCENDING:
                        return UIManager.getIcon("Table.ascendingSortIcon");
                    case DESCENDING:
                        return UIManager.getIcon("Table.descendingSortIcon");
                
            
            return null;
        

        protected SortKey getSortKey(JTable table, int column) 
            RowSorter rowSorter = table.getRowSorter();
            if (rowSorter == null) 
                return null;
            

            List sortedColumns = rowSorter.getSortKeys();
            if (sortedColumns.size() > 0) 
                return (SortKey) sortedColumns.get(0);
            
            return null;
        
    

【讨论】:

首先,谢谢!这看起来很像我需要的,我目前没有时间在我的代码中实现它,但我今晚会做,然后在这里报告。 好的,我现在就构建它!它工作得很好,除了一件小事(标记几个列标题),但我会试着弄清楚我自己并了解更多关于 JTables 的知识:) hmm ... 不明白收听 row 选择如何有助于重新绘制 column 选择更改。实际上并没有:选择一个单元格,然后将所选内容移动到同一行中,并且该列的突出显示不会更新。 顺便说一句:仅仅为了注册一个监听器而进行子类化并不是一个足够好的子类化理由——可以在没有:-)的情况下完成 @kleopatra 同意,我想我一直在尝试其他带有标题的东西,只是没有清理代码。坏我【参考方案2】:

一个轻微的变化:当我阅读问题时,主要问题是标题没有在 column 选择更改时更新。让自定义标题监听 row 选择更改对于这种情况没有多大帮助。

事实上,JTableHeader 已经正在监听 Co​​lumnModel 并且模型的更改通知包括选择更改。只有 columnSelectionChange 方法是有意实现的:

// --Redrawing the header is slow in cell selection mode.
// --Since header selection is ugly and it is always clear from the
// --view which columns are selected, don't redraw the header.

自定义标题可以简单地实现重新绘制(这里懒惰我在表格的工厂方法中这样做只是为了省去我到表格的接线,你可以轻松地将它变成一个独立的类:-)。

final JTable table = new JTable(new AncientSwingTeam()) 

    @Override
    protected JTableHeader createDefaultTableHeader() 
        // subclassing to take advantage of super's auto-wiring
        // as ColumnModelListener
        JTableHeader header = new JTableHeader(getColumnModel()) 

            @Override
            public void columnSelectionChanged(ListSelectionEvent e) 
                repaint();
            

        ;
        return header;
    

;
table.setCellSelectionEnabled(true);
table.getTableHeader().setDefaultRenderer(new ColumnHeaderRenderer());

还使用 table api 稍微调整了 Mad 的渲染器:

/**
 * Slightly adjusted compared to @Mad
 * - use table's selectionBackground
 * - use table's isColumnSelected to decide on highlight
 */
public static class ColumnHeaderRenderer extends DefaultTableCellHeaderRenderer 

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean selected,  
        boolean focused, int row, int column) 
        super.getTableCellRendererComponent(table, value, selected, focused, row, column); 
        if (table.isColumnSelected(column)) 
            setBackground(table.getSelectionBackground());
        
        return this;
    

关于观察:

总是说 isSelected 是假的

原因是 BasicTableHeaderUI 中的一个小怪癖:

ui.selected != columnModel.selected

uiSelected 是键绑定可以访问的列 - 如果 laf 支持它并且标题是 focusOwner。对我来说真的没有意义,但是完全定义 ui 和 columnModel 选择的语义陷入了对新宝贝 fx 的兴奋中,这被遗忘了 ;-)

【讨论】:

不错,它有效。但是我不明白为什么 DefaultTableCellHeaderRenderer 只在 import sun.swing.table 而不是 javax.swing.table 您是如何访问DefaultTableCellHeaderRenderer的?

以上是关于突出显示 JTable 的列标题的主要内容,如果未能解决你的问题,请参考以下文章

比较两个或多个 JTable 和“突出显示”差异

使用 JQuery Tablesorter Pager 的列突出显示问题

找到 JTable 单元格并绘制它

如果该列中的任何单元格包含红色,则突出显示列标题

突出显示 WPF DataGrid 的已编辑单元格

选择 QTableWidget 中的行和列,同时保持突出显示