Группируемый заголовок таблицы с фильтром под заголовком

Проблема

Я хотел бы создать группируемый заголовок таблицы со строкой фильтра под заголовком.

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

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

Я использовал форму кода заголовка группируемой таблицы этот поток и добавил компонент фильтра.

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

Вопрос

Кто-нибудь знает, как сделать текстовые поля в заголовке таблицы редактируемыми? Или у кого-нибудь есть лучший подход к размещению фильтра таблицы под заголовком таблицы?

Код

Код до сих пор:

FilterHeader.java

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;

public class FilterHeader extends JPanel {

    public FilterHeader( JTable table, Object value, int columnIndex) {

        setLayout( new BorderLayout());

        // header 
        JLabel header = new JLabel();
        header.setForeground(table.getTableHeader().getForeground());
        header.setBackground(table.getTableHeader().getBackground());
        header.setFont(table.getTableHeader().getFont());

        header.setHorizontalAlignment(JLabel.CENTER);
        header.setText(value.toString());
        header.setBorder(UIManager.getBorder("TableHeader.cellBorder"));

        add( header, BorderLayout.CENTER);

        // append filter components to header
        if( columnIndex == 3) {

            JComboBox cb = new JComboBox();
            cb.setBackground(Color.yellow);
            cb.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
            cb.setBorder(new EmptyBorder(0, 0, 0, 0));
            cb.setForeground(table.getTableHeader().getForeground());
            cb.setPreferredSize(new Dimension(0,table.getRowHeight() + 4));

            add( cb, BorderLayout.SOUTH);

        } else {

            JTextField tf = new JTextField( "enter filtertext");
            tf.setBackground(Color.yellow);
            tf.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
            tf.setForeground(table.getTableHeader().getForeground());
            tf.setHorizontalAlignment(JLabel.CENTER);

            add( tf, BorderLayout.SOUTH);

            tf.addMouseListener(new MouseAdapter() {

                 @Override
                 public void  mouseClicked(MouseEvent e) {
                     System.out.println("textfield clicked"); // doesn't work
                 }
             });
        }


    }

}

ColumnGroup.java

import java.awt.Component;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

public class ColumnGroup {

    protected TableCellRenderer renderer;

    protected List<TableColumn> columns;
    protected List<ColumnGroup> groups;

    protected String text;
    protected int margin = 0;

    public ColumnGroup(String text) {
        this(text, null);
    }

    public ColumnGroup(String text, TableCellRenderer renderer) {
        this.text = text;
        this.renderer = renderer;
        this.columns = new ArrayList<TableColumn>();
        this.groups = new ArrayList<ColumnGroup>();
    }

    public void add(TableColumn column) {
        columns.add(column);
    }

    public void add(ColumnGroup group) {
        groups.add(group);
    }

    /**
     * @param column
     *            TableColumn
     */
    public List<ColumnGroup> getColumnGroups(TableColumn column) {
        if (!contains(column)) {
            return Collections.emptyList();
        }
        List<ColumnGroup> result = new ArrayList<ColumnGroup>();
        result.add(this);
        if (columns.contains(column)) {
            return result;
        }
        for (ColumnGroup columnGroup : groups) {
            result.addAll(columnGroup.getColumnGroups(column));
        }
        return result;
    }

    private boolean contains(TableColumn column) {
        if (columns.contains(column)) {
            return true;
        }
        for (ColumnGroup group : groups) {
            if (group.contains(column)) {
                return true;
            }
        }
        return false;
    }

    public TableCellRenderer getHeaderRenderer() {
        return renderer;
    }

    public void setHeaderRenderer(TableCellRenderer renderer) {
        this.renderer = renderer;
    }

    public String getHeaderValue() {
        return text;
    }

    public Dimension getSize(JTable table) {
        TableCellRenderer renderer = this.renderer;
        if (renderer == null) {
            renderer = table.getTableHeader().getDefaultRenderer();
        }
        Component comp = renderer.getTableCellRendererComponent(table, getHeaderValue() == null || getHeaderValue().trim().isEmpty() ? " "
                : getHeaderValue(), false, false, -1, -1);
        int height = comp.getPreferredSize().height;
        int width = 0;
        for (ColumnGroup columnGroup : groups) {
            width += columnGroup.getSize(table).width;
        }
        for (TableColumn tableColumn : columns) {
            width += tableColumn.getWidth();
            width += margin;
        }
        return new Dimension(width, height);
    }

    public void setColumnMargin(int margin) {
        this.margin = margin;
        for (ColumnGroup columnGroup : groups) {
            columnGroup.setColumnMargin(margin);
        }
    }

}

GroupableTableHeader.java

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

@SuppressWarnings("serial")
public class GroupableTableHeader extends JTableHeader {

    @SuppressWarnings("unused")
    private static final String uiClassID = "GroupableTableHeaderUI";

    protected List<ColumnGroup> columnGroups = new ArrayList<ColumnGroup>();

    public GroupableTableHeader(TableColumnModel model) {
        super(model);
        setUI(new GroupableTableHeaderUI());
        setReorderingAllowed(false);
        // setDefaultRenderer(new MultiLineHeaderRenderer());
    }

    @Override
    public void updateUI() {
        setUI(new GroupableTableHeaderUI());
    }

    @Override
    public void setReorderingAllowed(boolean b) {
        super.setReorderingAllowed(false);
    }

    public void addColumnGroup(ColumnGroup g) {
        columnGroups.add(g);
    }

    public List<ColumnGroup> getColumnGroups(TableColumn col) {
        for (ColumnGroup group : columnGroups) {
            List<ColumnGroup> groups = group.getColumnGroups(col);
            if (!groups.isEmpty()) {
                return groups;
            }
        }
        return Collections.emptyList();
    }

    public void setColumnMargin() {
        int columnMargin = getColumnModel().getColumnMargin();
        for (ColumnGroup group : columnGroups) {
            group.setColumnMargin(columnMargin);
        }
    }

}

GroupableTableHeaderUI.java

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

public class GroupableTableHeaderUI extends BasicTableHeaderUI {

    protected GroupableTableHeader getHeader() {
        return (GroupableTableHeader) header;
    }

    @Override
    public void paint(Graphics g, JComponent c) {
        Rectangle clipBounds = g.getClipBounds();
        if (header.getColumnModel().getColumnCount() == 0) {
            return;
        }
        int column = 0;
        Dimension size = header.getSize();
        Rectangle cellRect = new Rectangle(0, 0, size.width, size.height);
        Map<ColumnGroup, Rectangle> groupSizeMap = new HashMap<ColumnGroup, Rectangle>();

        for (Enumeration<TableColumn> enumeration = header.getColumnModel().getColumns(); enumeration.hasMoreElements();) {
            cellRect.height = size.height;
            cellRect.y = 0;
            TableColumn aColumn = enumeration.nextElement();
            List<ColumnGroup> groups = getHeader().getColumnGroups(aColumn);
            int groupHeight = 0;
            for (ColumnGroup group : groups) {
                Rectangle groupRect = groupSizeMap.get(group);
                if (groupRect == null) {
                    groupRect = new Rectangle(cellRect);
                    Dimension d = group.getSize(header.getTable());
                    groupRect.width = d.width;
                    groupRect.height = d.height;
                    groupSizeMap.put(group, groupRect);
                }
                paintCell(g, groupRect, group);
                groupHeight += groupRect.height;
                cellRect.height = size.height - groupHeight;
                cellRect.y = groupHeight;
            }
            cellRect.width = aColumn.getWidth();
            if (cellRect.intersects(clipBounds)) {
                paintCell(g, cellRect, column);
            }
            cellRect.x += cellRect.width;
            column++;
        }
    }

    private void paintCell(Graphics g, Rectangle cellRect, int columnIndex) {
        TableColumn aColumn = header.getColumnModel().getColumn(columnIndex);
        TableCellRenderer renderer = aColumn.getHeaderRenderer();
        if (renderer == null) {

            // original
            renderer = getHeader().getDefaultRenderer();

            // modified
            renderer = new DefaultTableCellRenderer() {
                public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {

                    FilterHeader header = new FilterHeader( table, value, column);
                    return header;
                }

            };
        }
        Component c = renderer.getTableCellRendererComponent(header.getTable(), aColumn.getHeaderValue(), false, false,
                -1, columnIndex);

        c.setBackground(UIManager.getColor("control"));

        rendererPane.paintComponent(g, c, header, cellRect.x, cellRect.y, cellRect.width, cellRect.height, true);
    }

    private void paintCell(Graphics g, Rectangle cellRect, ColumnGroup cGroup) {
        TableCellRenderer renderer = cGroup.getHeaderRenderer();
        if (renderer == null) {
            renderer = getHeader().getDefaultRenderer();
        }

        Component component = renderer.getTableCellRendererComponent(header.getTable(), cGroup.getHeaderValue(), false,
                false, -1, -1);
        rendererPane
                .paintComponent(g, component, header, cellRect.x, cellRect.y, cellRect.width, cellRect.height, true);
    }

    private int getHeaderHeight() {
        int headerHeight = 0;
        TableColumnModel columnModel = header.getColumnModel();
        for (int column = 0; column < columnModel.getColumnCount(); column++) {
            TableColumn aColumn = columnModel.getColumn(column);
            TableCellRenderer renderer = aColumn.getHeaderRenderer();
            if (renderer == null) {
                renderer = getHeader().getDefaultRenderer();
            }

            Component comp = renderer.getTableCellRendererComponent(header.getTable(), aColumn.getHeaderValue(), false,
                    false, -1, column);
            int cHeight = comp.getPreferredSize().height;
            List<ColumnGroup> groups = getHeader().getColumnGroups(aColumn);
            for (ColumnGroup group : groups) {
                cHeight += group.getSize(header.getTable()).height;
            }
            headerHeight = Math.max(headerHeight, cHeight);
        }
        return headerHeight;
    }

    @Override
    public Dimension getPreferredSize(JComponent c) {
        int width = 0;
        for (Enumeration<TableColumn> enumeration = header.getColumnModel().getColumns(); enumeration.hasMoreElements();) {
            TableColumn aColumn = enumeration.nextElement();
            width += aColumn.getPreferredWidth();
        }
        return createHeaderSize(width);
    }

    private Dimension createHeaderSize(int width) {
        TableColumnModel columnModel = header.getColumnModel();
        width += columnModel.getColumnMargin() * columnModel.getColumnCount();
        if (width > Integer.MAX_VALUE) {
            width = Integer.MAX_VALUE;
        }
        return new Dimension(width, getHeaderHeight());
    }

}

GroupableHeaderExample.java

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.RowSorter;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;

// original from https://stackoverflow.com/questions/21347647/how-to-combine-two-column-headers-in-jtable-in-swings
public class GroupableHeaderExample extends JFrame {

      GroupableHeaderExample() {
        super( "Groupable Header Example" );

        DefaultTableModel dm = new DefaultTableModel();
        dm.setDataVector(new Object[][]{
          {"1","a","b","c","d","e"},
          {"2","f","g","h","i","j"},
          {"3","k","l","m","n","o"},
          {"4","p","q","r","s","t"}
          },
        new Object[]{"SNo.","1","2","Native","2","3"});

        JTable table = new JTable( dm ) {
          protected JTableHeader createDefaultTableHeader() {
              return new GroupableTableHeader(columnModel);
          }
        };


        TableColumnModel cm = table.getColumnModel();
        ColumnGroup g_name = new ColumnGroup("Name");
        g_name.add(cm.getColumn(1));
        g_name.add(cm.getColumn(2));
        ColumnGroup g_lang = new ColumnGroup("Language");
        g_lang.add(cm.getColumn(3));
        ColumnGroup g_other = new ColumnGroup("Others");
        g_other.add(cm.getColumn(4));
        g_other.add(cm.getColumn(5));
        g_lang.add(g_other);

        GroupableTableHeader header = (GroupableTableHeader)table.getTableHeader();
        header.addColumnGroup(g_name);
        header.addColumnGroup(g_lang);
        JScrollPane scroll = new JScrollPane( table );
        getContentPane().add( scroll );
        setSize( 400, 120 );   

        // allow sorting
        RowSorter<TableModel> sorter = new TableRowSorter<TableModel>(dm);
        table.setRowSorter(sorter);

      }

      public static void main(String[] args) {
        GroupableHeaderExample frame = new GroupableHeaderExample();
        frame.setSize(1024,768);
        frame.addWindowListener( new WindowAdapter() {
          public void windowClosing( WindowEvent e ) {
      System.exit(0);
          }
        });
        frame.setVisible(true);
      }
    }

И скриншот:

введите здесь описание изображения

Огромное спасибо за помощь!


person Roland    schedule 14.05.2015    source источник


Ответы (1)


Решил это путем объединения различных источников.

Одним из источников был это сообщение на StackOverflow, но это решение поставило фильтр только вне таблицы.

Другим источником была версия TableFilter с открытым исходным кодом на coderazzi. Это очень круто, но и очень тяжело для моих нужд. И не поддерживает сгруппированные столбцы. Итак, в общем, мне нужен был этот фрагмент кода:

JViewport headerViewport = new JViewport() {
    
    @Override
    public void setView(Component view) {
        if (view instanceof JTableHeader) {
            filterHeader.add(view, BorderLayout.NORTH);
            super.setView(filterHeader);
        }
    }
};

scroll.setColumnHeader(headerViewport);

и

private class TableFilterHeader extends JPanel {
    public TableFilterHeader(JTableHeader th) {
        setLayout(new BorderLayout());
        add(new TableFilterRow(th.getTable()), BorderLayout.SOUTH);
    }
}

Скриншот:

введите здесь описание изображения

Заинтересованные могут получить полный код из этого списка. Чего не хватает, так это самого фильтра, но это просто: добавьте прослушиватель документов и примените фильтр.

person Roland    schedule 20.05.2015