简体   繁体   English

JPAnel在JComboBox下拉列表中但不在编辑器中

[英]JPanel in JComboBox dropdown but not in the editor

What I want seems relatively simple, and I'm almost there. 我想要的东西看起来相对简单,我几乎就在那里。 This will eventually be used to extend TableCellEditor , so the size is important. 这最终将用于扩展TableCellEditor ,因此大小很重要。 What I want is something like this: 我想要的是这样的: 模拟理想的JPanelComboBox

With a combination of custom ComboBoxEditor s and ListCellRenderer s I've been able to get something like this: 通过自定义ComboBoxEditorListCellRenderer的组合,我已经能够得到这样的结果:

模拟JPanelComboBox的最佳尝试

Which has the inconveniences of: 其中有以下不便之处:

  • Cutting off any components beyond the original width of the JComboBox 切断超出JComboBox原始宽度的任何组件
  • Forcing the height of the JComboBox to be the height of the JPanel in the drop-down 强制JComboBox的高度为下拉列表中JPanel的高度
  • Allowing only one (1) click modification of the form before the drop-down disappears. 在下拉列表消失前,只允许对表单进行一(1)次单击修改。

I'd like to have the drop-down stay visible until the user clicks the editor or the JComboBox looses focus to another control, and then have the value in the editor update. 我想让下拉保持可见,直到用户单击编辑器或JComboBox将焦点丢失到另一个控件,然后在编辑器中更新值。 There will only ever be one (1) JPanel in the drop-down and I don't want the editor to be able to actually edit the string displayed. 下拉列表中只有一(1)个JPanel,我不希望编辑器能够实际编辑显示的字符串。

My question is similar to @ErkanHaspalut 's question here but neither response is satisfying. 我的问题类似于@ErkanHaspalut的问题,但两种反应都不令人满意。 I'd previously made a similar attempt by embedding a JPanel in a JPopupMenu and adding it to a JTextField but had similar issues about the popup disappearing prematurely. 我之前做过类似的尝试,在JPopupMenu嵌入JPanel并将其添加到JTextField但是有关弹出窗口过早消失的类似问题。

I've tried forcing the size of the JComboBox both by setting the setMaximumSize height value (which has no effect) and with 我已经尝试通过设置setMaximumSize height值(没有任何效果)来强制JComboBox的大小

    Rectangle tmp = cboTest.getBounds();
    tmp.height = 24;
    cboTest.setBounds(tmp);

which simply shows the top 24 lines of the JComboBox . 它只显示了JComboBox的前24行。 A minimum compilable example would be 最小的可编译示例是

/*
    * Program to test having a JPanel in the JComboBox's drop-down but not the JComboBox's editor.
    */
    package testdropdownsubform;

    import java.awt.Component;
    import java.awt.Rectangle;
    import java.awt.event.ActionListener;
    import java.awt.event.FocusEvent;
    import java.awt.event.FocusListener;
    import javafx.application.Application;
    import javafx.stage.Stage;
    import javax.swing.ComboBoxEditor;
    import javax.swing.JComboBox;
    import javax.swing.JList;
    import javax.swing.ListCellRenderer;
    import javax.swing.event.PopupMenuEvent;
    import javax.swing.event.PopupMenuListener;
    import javax.swing.JTextField;
    import javax.swing.plaf.basic.BasicComboBoxEditor;

    /**
    * @author Masked Coder
    */

    public class Dim {
        public Long DimWidth;
        public Long DimHeight;

        public Dim () {
            DimWidth = 1L;
            DimHeight = 1L;
        }

        @Override
        public String toString() {
            return DimWidth.toString() + "\' x " + DimHeight.toString() + "\'";
        }
    }

    public class DimPanel extends javax.swing.JPanel {

        public DimPanel() {
            spnDimWidth = new javax.swing.JSpinner();
            spnDimHeight = new javax.swing.JSpinner();

            spnDimWidth.setModel(new javax.swing.SpinnerNumberModel(Long.valueOf(1L), Long.valueOf(0L), null, Long.valueOf(1L)));
            spnDimWidth.setPreferredSize(new java.awt.Dimension(50, 24));
            addComponent(spnDimWidth);

            lblTween.setText(" x ");
            addComponent(lblTween);

            spnDimHeight.setModel(new javax.swing.SpinnerNumberModel(Long.valueOf(1L), Long.valueOf(0L), null, Long.valueOf(1L)));
            spnDimHeight.setPreferredSize(new java.awt.Dimension(50, 24));
            addComponent(spnDimHeight);
        }

        private javax.swing.JSpinner spnDimWidth;
        private javax.swing.JLabel lblTween;
        private javax.swing.JSpinner spnDimHeight;
    }


    public class DimListCellRenderer implements ListCellRenderer {
        private DimPanel dpDim;

        public DimListCellRenderer(DimPanel newDimPanel) {
            dpDim = newDimPanel;

        }

        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            Dim dValue = (Dim) value;
            dpDim.setDim(dValue);
            return dpDim;
        }

    }

    public class DimComboBoxEditor extends BasicComboBoxEditor implements ComboBoxEditor {
        JTextField txtDim = new JTextField();
        Dim Item = new Dim();

        public DimComboBoxEditor() {
            txtDim.setEnabled(false);
            txtDim.setOpaque(true);
        }
        @Override
        public Component getEditorComponent() {
            txtDim.setText(Item.toString());
            return txtDim;
        }

        @Override
        public void setItem(Object anObject) {
            Item = (Dim) anObject;
        }

        @Override
        public Object getItem() {
            return Item;
        }

        @Override
        public void selectAll() {
            txtDim.selectAll();
        }

        @Override
        public void addActionListener(ActionListener l) {
            txtDim.addActionListener(l);
        }

        @Override
        public void removeActionListener(ActionListener l) {
            txtDim.removeActionListener(l);
        }

    }

    public class MainTestForm extends javax.swing.JFrame {
        public MainTestForm() {
            lblPrevComponent = new javax.swing.JLabel();
            chkPrevComponent = new javax.swing.JCheckBox();
            lblTest = new javax.swing.JLabel();
            cboTest = new JComboBox<testdropdownsubform.DicePanel>();
            lblNextComponent = new javax.swing.JLabel();
            scpNextComponent = new javax.swing.JScrollPane();
            txaNextComponent = new javax.swing.JTextArea();
            btnForceHeight = new javax.swing.JButton();

            setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

            lblPrevComponent.setText("Prev. Component");

            chkPrevComponent.setText("jCheckBox1");

            lblTest.setText("Dimension");

            cboTest.setEditable(true);
            cboTest.setEditor(new DimComboBoxEditor());
            cboTest.setRenderer(new DimListCellRenderer(new DimPanel()));
            cboTest.addItem(new Dim());

            lblNextComponent.setText("Next Component");

            txaNextComponent.setColumns(20);
            txaNextComponent.setRows(5);
            scpNextComponent.setViewportView(txaNextComponent);

            btnForceHeight.setText("Force");
            btnForceHeight.setToolTipText("Force test combobox height");
            btnForceHeight.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    btnForceHeightActionPerformed(evt);
                }
            });

            .addComponent(lblPrevComponent)
            .addComponent(chkPrevComponent))
            .addComponent(lblTest)
            .addComponent(cboTest)
            .addComponent(lblNextComponent)
            .addComponent(scpNextComponent)
            .addComponent(btnForceHeight))

        }

    private void btnForceHeightActionPerformed(java.awt.event.ActionEvent evt) {                                               
            Rectangle tmp = cboTest.getBounds();
            tmp.height = 24;
            cboTest.setBounds(tmp);
    }                                              

        /**
        * @param args the command line arguments
        */
        public static void main(String args[]) {
            /* Create and display the form */
            java.awt.EventQueue.invokeLater(new Runnable() {
                public void run() {
                    new MainTestForm().setVisible(true);
                }
            });
        }

        private javax.swing.JButton btnForceHeight;
        private javax.swing.JComboBox cboTest;
        private javax.swing.JCheckBox chkPrevComponent;
        private javax.swing.JLabel lblNextComponent;
        private javax.swing.JLabel lblPrevComponent;
        private javax.swing.JLabel lblTest;
        private javax.swing.JScrollPane scpNextComponent;
        private javax.swing.JTextArea txaNextComponent;
    }

    public class TestDropdownSubform extends Application {

        @Override
        public void start(Stage primaryStage) {
            MainTestForm mtfMain = new MainTestForm();

            mtfMain.setVisible(true);
        }

        /**
        * @param args the command line arguments
        */
        public static void main(String[] args) {
            launch(args);
        }

    }

My question is, am I missing a detail, or can anyone see a better way to accomplish this? 我的问题是,我错过了一个细节,还是有人能看到更好的方法来实现这个目标吗? Thanks in advance for your advice. 提前感谢您的建议。

Edit : Declaring the JComboBox as 编辑 :将JComboBox声明为

JComboBox cboTest = new JComboBox<DimPanel>() {
    private boolean layingOut = false; 

    @Override
    public void doLayout(){ 
        try{ 
        layingOut = true; 
        super.doLayout(); 
        }finally{ 
        layingOut = false; 
        } 
    } 

        @Override
        public Dimension getSize(){ 
            Dimension dim = super.getSize(); 
            if(!layingOut) {
                dim.width = Math.max(dim.width, dpThis.getPreferredSize().width); 
            }
            return dim; 
        } 
};

fixes the width of the drop-down. 修复下拉列表的宽度。 Declaring it as 将其声明为

JComboBox cboTest = new JComboBox<testdropdownsubform.DicePanel>() {
    private boolean layingOut = false; 

    @Override
    public void doLayout(){ 
        try{ 
        layingOut = true; 
        super.doLayout(); 
        }finally{ 
        layingOut = false; 
        } 
    } 

        @Override
        public Dimension getSize(){ 
            Dimension dim = super.getSize(); 
            if(!layingOut) {
                Dimension dim2 = dpThis.getPreferredSize(); 
                dim.width = Math.max(dim.width, dim2.width); 
    //          dim.height = dim2.height; 
            }
            return dim; 
        } 

    @Override
    public DimPanel getPrototypeDisplayValue() {
        DimPanel tmpPanel = new DimPanel();
        if(isCalledFromComboPopup()) {
        //
        }
        else {
        Dimension r = dcbeEditor.getPreferredSize();
        tmpPanel.setPreferredSize(r);
        }
        return tmpPanel;
    }

    /**
    * Hack method to determine if called from within the combo popup UI.
    */
    public boolean isCalledFromComboPopup() {
        try {
        final Throwable t = new Throwable();
        t.fillInStackTrace();
        StackTraceElement[] st = t.getStackTrace();
        // look only at top 5 elements of call stack
        int max = Math.min(st.length, 5);
        for (int i=0; i<max; ++i) {
            final String name = st[i].getClassName();
            System.out.println(i + ")  " + name);
            return ((name != null) && name.contains("ComboPopup"));
        }
        } catch (final Exception e) {
        // if there was a problem, assume not called from combo popup
        }
        return false;
    }
};

fixes the editor size but now the drop-down is the width and height of the editor. 修复了编辑器大小, 现在下拉列表是编辑器的宽度和高度。

So, I ended up going on a different track, creating my own pseudo-combobox with a JTextField and a JButton in a JPanel . 所以,我最终走上了另一条轨道,在JPanel创建了自己的伪组合框,其中包含JTextFieldJButton I believe I've quite handily genericized the result into four related classes. 我相信我已经非常方便地将结果归结为四个相关的类。 First, you need an implementation of AbstractPopupPanel (which quite handily loads into the NetBeans designer if you're using it): 首先,您需要AbstractPopupPanel的实现(如果您正在使用它,可以非常方便地加载到NetBeans设计器中):

package complexcombobox;

/**
*
* @author MaskedCoder
*
* @param E - the object to be edited in the panel
*/
public abstract class AbstractPopupPanel<E> extends javax.swing.JPanel {

    /**
    * Creates new form PopupPanel
    */
    public AbstractPopupPanel() {
        initComponents();
    }

    public abstract boolean isChanged();

    public abstract E getValue();

    public abstract void setValue(E newValue);

    private void initComponents() {
        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 400, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 300, Short.MAX_VALUE)
        );
    }                        
}

With that created to your satisfaction, we need to place it in a pop-up menu: 如果您满意,我们需要将其放在弹出菜单中:

package complexcombobox;

import javax.swing.JPopupMenu;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;

/**
*
* @author MaskedCoder
* 
* @param E - the object to be edited
* @param P - the extension of AbstractPopupPanel that will display the object as you wish
*/
public class ComplexPopup<E, P extends AbstractPopupPanel<E>> extends JPopupMenu {

    /*
    * Interface to notify user of editing
    */
    public interface EditListener<E> {
        // return the object's value to be edited.
        // if the user returns null, the existing Dice is used.
        public E beforeEdit(); 

        // receives the new value
        // called *only* when the object has actually changed.
        public void afterEdit(E newValue);  
    }

    /*
    *internal variables
    */
    private P thisPanel;
    private EditListener<E> userListener;
//  private Class<E> eClass;
//  private Class<P> pClass;
    private PopupMenuListener myPopupListener = new PopupMenuListener() {

        @Override
        public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
            if(userListener != null) {
                E tmpSize = userListener.beforeEdit();
                if(tmpSize != null) thisPanel.setValue(tmpSize);
            }
        }

        @Override
        public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
            if(userListener != null) {
//              if(thisPanel.isChanged())
                    userListener.afterEdit((E) thisPanel.getValue());
            }
        }

        @Override
        public void popupMenuCanceled(PopupMenuEvent e) {
            popupMenuWillBecomeInvisible(e);
        }
    };

    /* 
    * Constructors
    */

    public ComplexPopup(E iniValue, P iniDropdownPanel, EditListener<E> iniListener) {
        super();

        init(iniValue, iniDropdownPanel, iniListener);
    }

    private void init(E iniValue, P iniDropdownPanel, EditListener<E> iniListener) {
        thisPanel = iniDropdownPanel;
        thisPanel.setValue(iniValue);
        add(thisPanel);
        userListener = iniListener;
        this.addPopupMenuListener(myPopupListener);
    }

    /* 
    * Public Properties 
    */
    public E getValue() {
        return (E) thisPanel.getValue();
    }
    public void setValue(E newObjectSize) {
        thisPanel.setValue(newObjectSize);
    }

    public EditListener getUserListener() {
        return userListener;
    }

    public void setEditListener(EditListener newListener) {
        userListener = newListener;
    }

    /*
    * functional extensions
    */
    public void afterEdit(E newValue) {
        if(userListener != null) {
            if(thisPanel.isChanged())
                userListener.afterEdit((E) thisPanel.getValue());
        }
    }
}

This places the form in a JPopupMenu and allows access to the object to be edited as well as notification before and after editing. 这将表单放在JPopupMenu并允许在编辑之前和之后访问要编辑的对象以及通知。 Now, I couldn't co-opt the JComboBox interface without getting deeper into the mechanics than I wanted to go, so I fudged the combo box, as I mentioned: 现在,我无法在没有深入了解机制的情况下选择JComboBox界面,所以我捏造了组合框,正如我所提到的:

package complexcombobox;

/**
*
* @author MaskedCoder
* 
* @param <I> the Item (Object) to be edited
* @param <Q> the AbstractPopupPanel to be used to do the editing. 
*/
public class ComplexComboBox<I, Q extends AbstractPopupPanel<I>> extends javax.swing.JPanel {
    private ComplexPopup<I, Q> thePopup;
    private ComplexPopup.EditListener<I> myListener = new ComplexPopup.EditListener<I>() {

        @Override
        public I beforeEdit() {
            return null; // no changes so, just let it ride.
        }

        @Override
        public void afterEdit(I newValue) {
            txtEditor.setText(thePopup.getValue().toString());
        }
    };

    /**
    * Creates new form ObjectSizeComboBox
    */
    public ComplexComboBox(I iniValue, Q iniPanel) {
        initComponents();
        thePopup = new ComplexPopup<I, Q>(iniValue, iniPanel, myListener);
    }

    public I getValue() {
        return thePopup.getValue();
    }

    public void setValue(I newValue) {
        thePopup.setValue(newValue);
    }

    private void initComponents() {

        txtEditor = new javax.swing.JTextField();
        btnShowPanel = new javax.swing.JButton();

        setBackground(new java.awt.Color(255, 255, 255));
        setMinimumSize(new java.awt.Dimension(40, 19));

        txtEditor.setEditable(false);
        txtEditor.setToolTipText("");
        txtEditor.setOpaque(false);

        btnShowPanel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/testpopupsubform/art/dropdownarrow.png"))); // NOI18N
        btnShowPanel.addActionListener(new java.awt.event.ActionListener() {
        public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnShowPanelActionPerformed(evt);
        }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(layout.createSequentialGroup()
            .addComponent(txtEditor, javax.swing.GroupLayout.DEFAULT_SIZE, 115, Short.MAX_VALUE)
            .addGap(1, 1, 1)
            .addComponent(btnShowPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 24, javax.swing.GroupLayout.PREFERRED_SIZE))
        );
        layout.setVerticalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addComponent(btnShowPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addComponent(txtEditor)
        );
    }                        

    private void btnShowPanelActionPerformed(java.awt.event.ActionEvent evt) {                                             
            thePopup.show(txtEditor, 0, txtEditor.getHeight());
    }                                            

    private javax.swing.JButton btnShowPanel;
    private javax.swing.JTextField txtEditor;
}

This seems to work very well in a cell editor for a table, provided you make the row heights tall enough. 这似乎在表格的单元格编辑器中非常有效,前提是您可以使行高度足够高。 That's no different than a regular JComboBox editor, though: 这与普通的JComboBox编辑器没什么不同,但是:

package complexcombobox;

import java.awt.Component;
import javax.swing.AbstractCellEditor;
import javax.swing.JTable;
import javax.swing.table.TableCellEditor;

/**
*
* @author MaskedCoder
*
* @param <E> the Element (Object) to be edited
* @param <P> the AbstractPopupPanel to be used to do the editing. 
*/
public class ComplexCellEditor<E, P extends AbstractPopupPanel<E>> extends AbstractCellEditor
            implements TableCellEditor {
    private ComplexComboBox<E, P> ComplexEditor;

    /*
    * Constructors
    */
    public ComplexCellEditor(E iniValue, P iniPanel) { 
        super();

        ComplexEditor = new ComplexComboBox<E, P>(iniValue, iniPanel);
    }

    /*
    * AbstractCellEditor Extensions
    */
    @Override
    public boolean stopCellEditing() {
            return super.stopCellEditing();
    }

    /*
    * TableCellEditor Implementation
    */
        @Override
    public E getCellEditorValue() {
            return ComplexEditor.getValue();
    }

    @Override
    public Component getTableCellEditorComponent(JTable iniTable, Object iniValue, boolean isSelected, int row, int column) {
            ComplexEditor.setValue((E) iniValue);

            return ComplexEditor;
    }
}

Three things I'm not entirely satisfied with: - I'd like to grab the JComboBox down-arrow icon from the system rather than relying on a project resource - I've had to pass through the AbstractPopupPanel instance which means that the coder has to create the panel themselves and then hand it to the constructor for ComplexComboBox or ComplexCellEditor . 我不满意的三件事: - 我想从系统中获取JComboBox向下箭头图标而不是依赖项目资源 - 我必须通过AbstractPopupPanel实例,这意味着编码器已经自己创建面板,然后将其交给ComplexComboBoxComplexCellEditor的构造ComplexCellEditor I'd rather have created the panel internally, in ComplexPopup and managed it myself. 我宁愿在内部创建面板,在ComplexPopup自己管理它。 I ran into difficulty instantiating Generic constructors. 我遇到了实例化通用构造函数的困难。 Pass-through does allow the coder more control but I don't think that's needed and I'd like it to be optional at least. 传递确实允许编码员更多控制,但我不认为这是必要的,我希望它至少是可选的。

My knowledge of Generics isn't advanced but this seems to do the trick. 我对Generics的了解并不高级,但这似乎可以解决问题。

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

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