简体   繁体   English

可扩展 JTable 单元格编辑器

[英]Expandable JTable Cell Editor

I have a rows of data, where I can make actions on a remote server.我有一行数据,我可以在其中对远程服务器进行操作。

I'm displaying these data in a JTable as it avoids the cost of creating a component for each row, and there's a lot.我在JTable中显示这些数据,因为它避免了为每一行创建一个组件的成本,而且有很多。

在此处输入图像描述

I have managed to show the component and be able to interact with the cell, (however I still a first click).我已经设法显示组件并能够与单元格交互,(但我仍然是第一次点击)。

在此处输入图像描述

But what I'm struggling with is that I want my editor component to be expandable, and of course update the current row height.但我正在苦苦挣扎的是我希望我的编辑器组件是可扩展的,当然还要更新当前行高。 And of course to revert to regular row height when collapsed.当然,在折叠时恢复到正常的行高。 I think I need to register some listeners to the cell editor, but I'm currently not able to correctly do that.我想我需要向单元格编辑器注册一些听众,但我目前无法正确地做到这一点。

  • ComponentListner::componentResized make the whole table being constantly repainted. ComponentListner::componentResized使整个表不断重绘。
  • editorComponent.addPropertyChangeListener("preferredSize", propertyChangeListener) , doesn't always repaint the table editorComponent.addPropertyChangeListener("preferredSize", propertyChangeListener) ,并不总是重新绘制表格
  • editorComponent.addPropertyChangeListener("preferredSize", propertyChangeListener) , doesn't always repaint the table editorComponent.addPropertyChangeListener("preferredSize", propertyChangeListener) ,并不总是重新绘制表格

Hence this question.因此这个问题。

在此处输入图像描述 在此处输入图像描述

Somewhat out of scope, I'm currently debating if the component should stay expanded when going to another cell (but this can be an expanded property that is part of the model).有点超出 scope,我目前正在争论组件是否应该在转到另一个单元格时保持扩展(但这可以是模型的一部分的expanded属性)。

Here's a minimal example without my attempts to register listeners on the editor component (in getTableCellEditorComponent ).这是一个最小的示例,我没有尝试在编辑器组件(在getTableCellEditorComponent中)上注册侦听器。

Thanks in advance for ay pointers.在此先感谢您的指点。

package io.github.bric3.fireplace.ui;

import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import javax.swing.border.TitledBorder;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import java.awt.*;

public class DynamicCellRow {
    @NotNull
    private static JTable makeTable() {
        var jTable = new JTable(
                new Object[][]{
                        {"a", "charly"},
                        {"b", "tango"}
                },
                new Object[]{"id", "control"}
        );
        {
            var richColumn = jTable.getColumnModel().getColumn(1);
            richColumn.setCellRenderer(new TableCellRenderer() {
                private final ExpandablePanel expandablePanelRenderComponent = new ExpandablePanel();
                @Override
                public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
                    expandablePanelRenderComponent.setValue(value);
                    return updatePreferredRowHeight(table, expandablePanelRenderComponent, row, column);
                }
            });

            richColumn.setCellEditor(new DynamicCellEditor());
        }
        return jTable;
    }

    public static <T extends JComponent> T updatePreferredRowHeight(JTable table, T cellComponent, int row, int column) {
        // Adjust cell height per component
        int originalPreferredHeight = cellComponent.getPreferredSize().height;
        cellComponent.setSize(
                table.getColumnModel().getColumn(column).getWidth(),
                originalPreferredHeight
        );
        int newPreferredHeight = cellComponent.getPreferredSize().height;
        if (table.getRowHeight(row) < newPreferredHeight) {
            table.setRowHeight(row, newPreferredHeight);
        }
        return cellComponent;
    }

    static class ExpandablePanel extends JPanel {

        private final JLabel comp;

        ExpandablePanel() {
            setBorder(BorderFactory.createLineBorder(Color.black));
            setLayout(new GridBagLayout());

            GridBagConstraints gbc = new GridBagConstraints();
            // horizontal left-to-right layout
            gbc.gridx = 0;
            gbc.gridy = 0;

            // resizing behavior
            gbc.weightx = 1;
            gbc.weighty = 1;

            gbc.insets = new Insets(2, 2, 2, 2);
            gbc.fill = GridBagConstraints.BOTH;

            JPanel advanced = new JPanel();
            {
                advanced.setLayout(new BoxLayout(advanced, BoxLayout.Y_AXIS));
                advanced.setBorder(new TitledBorder("Advance Settings"));
                advanced.add(new JCheckBox("Live"));
                advanced.add(new JCheckBox("Condition"));
                advanced.add(new JCheckBox("Disable"));
            }
            advanced.setVisible(false);

            var standard = new JPanel();
            {
                standard.setLayout(new BoxLayout(standard, BoxLayout.X_AXIS));
                comp = new JLabel("Label 1");
                standard.add(comp);
                standard.add(new JButton("Button 1"));
                var expandButton = new JButton("+");
                expandButton.addActionListener(e -> {
                    if (advanced.isVisible()) {
                        advanced.setVisible(false);
                        expandButton.setText("+");
                    } else {
                        advanced.setVisible(true);
                        expandButton.setText("-");
                    }
                });
                standard.add(expandButton);
            }
            add(standard, gbc);


            gbc.gridy++;
            gbc.weighty = 0;
            add(advanced, gbc);
        }

        public void setValue(Object value) {
            comp.setText(value.toString());
        }
    }

    private static class DynamicCellEditor extends AbstractCellEditor implements TableCellEditor {
        Object value;
        @Override
        public Object getCellEditorValue() {
            return value; // not changing
        }

        private final ExpandablePanel expandablePanel = new ExpandablePanel();

        @Override
        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
            this.value = value;
            expandablePanel.setValue(value);
            return updatePreferredRowHeight(table, expandablePanel, row, column);
        }
    }


    public static void main(String[] args) {

        var contentPane = new JPanel(new BorderLayout());
        contentPane.add(new JScrollPane(makeTable()));

        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("DynamicCellRow");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(contentPane);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

@kleopatra Thank you. @kleopatra 谢谢。 Your tip got me on the right track.你的提示让我走上了正确的轨道。 I made the following code to precompute the row height during the layout operation.我编写了以下代码来在布局操作期间预先计算行高。

And when a cell gets edited, the editor is added to the table, when then do a revalidation, and computes the height accordingly.当一个单元格被编辑时,编辑器被添加到表中,然后重新验证,并相应地计算高度。

I had a problem before because setting the height in the renderer actually prevented the correct display of the cell component.我之前有一个问题,因为在渲染器中设置高度实际上阻止了单元格组件的正确显示。

In the following code, notice how doLayout is adjusting the row height, according to cell component preferred size (wether the cell is rendered or edited).在下面的代码中,请注意doLayout如何根据单元格组件的首选大小(无论是渲染还是编辑单元格)调整行高。

Note I choose to collapse the component once editing is stopped, hence the listener in the DynamicExpndablePanelCellEditor .请注意,我选择在编辑停止后折叠组件,因此DynamicExpndablePanelCellEditor中的侦听器。

import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import javax.swing.border.TitledBorder;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import java.awt.*;

public class DynamicCellRow {
    public static void main(String[] args) {

        var contentPane = new JPanel(new BorderLayout());
        contentPane.add(new JScrollPane(makeTable()));

        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("DynamicCellRow");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(contentPane);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }

    @NotNull
    private static JTable makeTable() {
        var jTable = new JTable(
                new Object[][]{
                        {"a", "charly"},
                        {"b", "tango"}
                },
                new Object[]{"id", "control"}
        ) {
            @Override
            public void doLayout() {
                super.doLayout();
                adjustRowHeights();
            }

            private void adjustRowHeights() {
                for (int row = 0; row < getRowCount(); row++) {
                    int rowHeight = getRowHeight();

                    for (int column = 0; column < getColumnCount(); column++) {
                        var editorComponent = getEditorComponent();
                        if (getEditingRow() == row && getEditingColumn() == column && editorComponent != null) {
                            editorComponent.setSize(getColumnModel().getColumn(column).getWidth(), 0);
                            rowHeight = Math.max(rowHeight, editorComponent.getPreferredSize().height);
                        } else {
                            var comp = prepareRenderer(getCellRenderer(row, column), row, column);
                            rowHeight = Math.max(rowHeight, comp.getPreferredSize().height);
                        }
                    }

                    setRowHeight(row, rowHeight);
                }
            }
        };
        {
            var richColumn = jTable.getColumnModel().getColumn(1);
            richColumn.setCellRenderer(new ExpandablePanelCellRenderer());
            richColumn.setCellEditor(new DynamicExpndablePanelCellEditor());
        }
        return jTable;
    }


    static class ExpandablePanel extends JPanel {

        private final JLabel comp;
        private final JPanel advanced;

        ExpandablePanel() {
            setBorder(BorderFactory.createLineBorder(Color.black));
            setLayout(new GridBagLayout());

            GridBagConstraints gbc = new GridBagConstraints();
            // horizontal left-to-right layout
            gbc.gridx = 0;
            gbc.gridy = 0;

            // resizing behavior
            gbc.weightx = 1;
            gbc.weighty = 1;

            gbc.insets = new Insets(2, 2, 2, 2);
            gbc.fill = GridBagConstraints.BOTH;

            advanced = new JPanel();
            {
                advanced.setLayout(new BoxLayout(advanced, BoxLayout.Y_AXIS));
                advanced.setBorder(new TitledBorder("Advance Settings"));
                advanced.add(new JCheckBox("Live"));
                advanced.add(new JCheckBox("Condition"));
                advanced.add(new JCheckBox("Disable"));
            }
            advanced.setVisible(false);

            var standard = new JPanel();
            {
                standard.setLayout(new BoxLayout(standard, BoxLayout.X_AXIS));
                comp = new JLabel("Label 1");
                standard.add(comp);
                standard.add(new JButton("Button 1"));
                var expandButton = new JButton("+");
                expandButton.addActionListener(e -> {
                    if (advanced.isVisible()) {
                        advanced.setVisible(false);
                        expandButton.setText("+");
                    } else {
                        advanced.setVisible(true);
                        expandButton.setText("-");
                    }
                });
                standard.add(expandButton);
            }
            add(standard, gbc);


            gbc.gridy++;
            gbc.weighty = 0;
            add(advanced, gbc);
        }

        public void setValue(Object value) {
            comp.setText(value.toString());
        }

        public void setAdvancedVisibility(boolean visible) {
            advanced.setVisible(visible);
        }
    }

    private static class DynamicExpndablePanelCellEditor extends AbstractCellEditor implements TableCellEditor {
        Object value;
        @Override
        public Object getCellEditorValue() {
            return value; // not changing
        }

        private final ExpandablePanel expandablePanel = new ExpandablePanel();
        {
            addCellEditorListener(new CellEditorListener() {
                @Override
                public void editingStopped(ChangeEvent e) {
                    expandablePanel.setAdvancedVisibility(false);
                }

                @Override
                public void editingCanceled(ChangeEvent e) {
                    expandablePanel.setAdvancedVisibility(false);
                }
            });
        }

        @Override
        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
            this.value = value;
            expandablePanel.setValue(value);

            return expandablePanel;
        }
    }

    private static class ExpandablePanelCellRenderer implements TableCellRenderer {
        private final ExpandablePanel expandablePanelRenderComponent = new ExpandablePanel();

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            expandablePanelRenderComponent.setValue(value);
            return expandablePanelRenderComponent;
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM