JComboBox 和 JTable 作为 ListCellRenderer 组件

JComboBox with JTable as ListCellRenderer component

我目前正在开发 JComboBox 组件,我希望在 ComboBox 中有一个 JTable 用于下拉选择。我已经扩展了 ListCellRenderer,并且在弹出窗口中有 table。

我想用两种不同的方式来呈现。当弹出窗口不可见时,第一个作为所选行的绑定列的标签。第二个是在弹出窗口可见时用 JScrollPane 显示 table。

不幸的是,当我这样做时,弹出窗口被缩小到列表的行高,只为 table 的列留出空间。

如果我只使用滚动窗格,我可以看到完整的 table,但是当弹出窗口不可见时,我会在组合框中看到 table,这不是我想要的。

如何设置高度,使 table 可以适应,同时仍然能够仅在弹出窗口不可见时显示标签?

下面是编译和运行的最小示例:

import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListCellRenderer;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;

public class TableComboBoxMain {
    public static void main(String[] args) {
        JTableComboBox<Employee> combo = new JTableComboBox<>();
        combo.addItem(new Employee("April",3));
        //combo.setMaximumRowCount(10);
        combo.setRenderer(new TableListCellRenderer(combo));
        JFrame frame = new JFrame("Test Table Combo Box");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(combo);
        frame.pack();
        frame.setVisible(true);
    }

    public static class Employee{
        String name;
        int nr;
        public Employee(String name, int number ){
            this.name =name;
            this.nr = number;
        }
    }

    public static class JTableComboBox<E> extends JComboBox<E> implements ListSelectionListener{
        @Override
        public void valueChanged(ListSelectionEvent e) {System.out.println("Row selected"+e.getFirstIndex());
            this.setSelectedIndex(e.getFirstIndex());
        }
    }

    public static class TableListCellRenderer extends JScrollPane implements ListCellRenderer{
    JTable table;
    JTableComboBox combo;
    boolean mouseListenerAdded;
    public TableListCellRenderer(JTableComboBox combo){
        this.combo=combo;
        DefaultTableModel model = new DefaultTableModel();
        String[] cols1 = new String[]{"1","2","3","4"};
        String[] cols2 = new String[]{"Mark","John","April","Mary"};
        model.addColumn("Nr", cols1);model.addColumn("Name",cols2);
        table = new JTable(model);table.setShowGrid(true);table.setGridColor(Color.gray);
        this.getViewport().add(table);
    }
    @Override
    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
        if(!mouseListenerAdded){
            list.addMouseListener(this.getListener());
            mouseListenerAdded = true;
        }//If this is uncommented then the BasicComboPopup size is always no more than 1 row?!!
        if(!combo.isPopupVisible()){
            Employee emp = (Employee) value;
            return new JLabel(emp.name);

        }else
           return this;
    }

    public MouseAdapter getListener(){
        MouseAdapter adapter = new MouseAdapter(){
        @Override
        public void mousePressed(MouseEvent e) {
                if(combo.isPopupVisible()){
                    System.out.println("MouseClicked over list");
                    int row = table.rowAtPoint(e.getPoint());
                    if(row>0){
                        table.setRowSelectionInterval(row-1, row-1);
                        ListDataEvent event = new ListDataEvent(combo,ListDataEvent.CONTENTS_CHANGED,row-1,row-1);
                        combo.contentsChanged(event);
                    }
                }
            }
        };
        return adapter;
    }
}
}

所以我找到了解决问题的方法。我还没有完成,因为我还想有一些互动:

  1. 我希望能够移动列
  2. 我希望能够为列提供弹出菜单
  3. 我希望能够用鼠标垂直滚动

我想要 post 解决方案,以防其他人想要一个起点示例。我将在解决这些附加问题时更新我的​​答案。

下面是测试class:

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ScrollPaneConstants;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.plaf.basic.BasicComboBoxUI;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.table.DefaultTableModel;

public class TableComboBoxMain {
    public static void main(String[] args) {
        JTableComboBox<String> combo = new JTableComboBox<>();
        combo.addItem("");
        BasicComboBoxUI ui = new BasicComboBoxUI(){
            @Override
            protected ComboPopup createPopup() {
                return new BasicComboPopup( comboBox ){
                    @Override
                    protected int getPopupHeightForRowCount(int maxRowCount) {
                        return 100;
                    }
                    @Override
                    protected JScrollPane createScroller() {
                        JScrollPane sp = new JScrollPane( list,
                            ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                            ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED );
                        sp.getHorizontalScrollBar().setFocusable( false );
                        return sp;
                    }
                };
            }
        };
        combo.setUI(ui);
        combo.setRenderer(new TableListCellRenderer(combo));
        JFrame frame = new JFrame("Test Table Combo Box");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(combo);
        frame.pack();
        frame.setVisible(true);
    }

    public static class Employee{
        String name;
        int nr;
        public Employee(String name, int number ){
            this.name =name;
            this.nr = number;
        }
    }

    public static class JTableComboBox<E> extends JComboBox<E> implements ListSelectionListener{
        @Override
        public void valueChanged(ListSelectionEvent e) {
            System.out.println("Row selected"+e.getFirstIndex());
            this.setSelectedIndex(e.getFirstIndex());
        }
    }

    public static class TableListCellRenderer extends DefaultListCellRenderer{
        JTable table;
        JTableComboBox combo;
        JPanel renderer = new JPanel();
        JPanel colHeader = new JPanel();
        JScrollPane scroll = new JScrollPane();
        boolean mouseListenerAdded;
        public TableListCellRenderer(JTableComboBox combo){
            this.combo=combo;
            DefaultTableModel model = new DefaultTableModel();
            String[] cols1 = new String[]{"1","2","3","4","5","6"};
            String[] cols2 = new String[]{"Mark","John","April","Mary","Joe","Jack"};
            model.addColumn("Nr", cols1);model.addColumn("Name",cols2);
            table = new JTable(model);
            table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
            colHeader.add(table.getTableHeader());
            renderer.setLayout(new BorderLayout());
            renderer.add(colHeader, BorderLayout.NORTH);
            renderer.add(table,BorderLayout.CENTER);
            scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
            scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
            scroll.getViewport().add(table);

        }
        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            list.setFixedCellHeight(table.getRowHeight()*(table.getRowCount()+1)+10);
            list.setFixedCellWidth(table.getPreferredSize().width);
            JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
            if(!mouseListenerAdded){
                list.addMouseListener(this.getListener());
                mouseListenerAdded = true;
            }
            if(!combo.isPopupVisible()){
                label.setText("Select...");
                if(table.getSelectedRowCount()>0)
                    label.setText(""+table.getModel().getValueAt(table.getSelectedRow(),1));
                return label;
            }
               return scroll;
        }

        public MouseAdapter getListener(){
            MouseAdapter adapter = new MouseAdapter(){
            @Override
            public void mousePressed(MouseEvent e) {
                    if(combo.isPopupVisible()){
                        System.out.println("MouseClicked over list");
                        int row = table.rowAtPoint(e.getPoint());
                        if(row>0){
                            table.setRowSelectionInterval(row-1, row-1);
                            ListDataEvent event = new ListDataEvent(combo,ListDataEvent.CONTENTS_CHANGED,row-1,row-1);
                            combo.contentsChanged(event);
                        }
                    }
                }
            };
            return adapter;
        }
    }
}

解决方案的关键部分是,为了在弹出窗口中获得 table 和 table 的功能,您需要覆盖 UI.具体来说,您需要覆盖 BasicComboBoxUI 上的 createPopupgetPopupHeightForRowCountBasicComboPopup 上的 createScroller。最后,在实施 getListCellRenderingComponent 时,您设置了与 table 的首选高度和宽度相匹配的固定高度和宽度。这将允许弹出窗口的主滚动条充当 table 的滚动窗格。