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

时间:2015-07-11 03:58:06

标签: java swing jpanel custom-controls jcombobox

我想要的东西看起来相对简单,我几乎就在那里。这将最终用于扩展TableCellEditor,因此大小很重要。我想要的是这样的: Mock-up of ideal JPanelComboBox

结合使用自定义ComboBoxEditorListCellRenderer s,我得到了类似的内容:

Mock-up of best attempt at JPanelComboBox

有哪些不便之处:

  • 切断超出JComboBox
  • 原始宽度的任何组件
  • 强制JComboBox的高度为下拉列表中JPanel的高度
  • 在下拉列表消失前,只允许对表单进行一(1)次单击修改。

我想让下拉保持可见,直到用户点击编辑器或JComboBox将焦点放在另一个控件上,然后让编辑器中的值更新。下拉列表中只有一(1)个JPanel,我不希望编辑器能够实际编辑显示的字符串。

我的问题类似于@ErkanHaspalut的question here,但两种反应都不令人满意。我之前通过在JPanel中嵌入JPopupMenu并将其添加到JTextField进行了类似的尝试,但是有关弹出窗口过早消失的类似问题。

我尝试通过设置JComboBox setMaximumSize值(无效)和

来强制height两者的大小
    Rectangle tmp = cboTest.getBounds();
    tmp.height = 24;
    cboTest.setBounds(tmp);

只显示JComboBox的前24行。最小的可编辑示例是

/*
    * 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);
        }

    }

我的问题是,我错过了一个细节,或者有人能看到更好的方法来实现这个目标吗?提前感谢您的建议。

修改:将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; 
        } 
};

修复下拉列表的宽度。将其声明为

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;
    }
};

修复了编辑器大小,但现在下拉列表是编辑器的宽度和高度。

1 个答案:

答案 0 :(得分:0)

所以,我最终走上了另一条轨道,在JTextField中创建了我自己的伪组合框,其中JButtonJPanel。我相信我已经非常轻松地将结果归类为四个相关的类。首先,您需要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)
        );
    }                        
}

创建令您满意的,我们需要将其放在弹出菜单中:

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());
        }
    }
}

这会将表单放在JPopupMenu中,并允许在编辑之前和之后访问要编辑的对象以及通知。现在,我无法选择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;
}

这似乎在表格的单元格编辑器中非常有效,前提是您可以使行高度足够高。但这与普通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;
    }
}

我不满意的三件事:   - 我想从系统中获取JComboBox向下箭头图标,而不是依赖项目资源   - 我必须通过AbstractPopupPanel实例,这意味着编码器必须自己创建面板,然后将其交给ComplexComboBoxComplexCellEditor的构造函数。我宁愿在ComplexPopup内部创建面板并自行管理。我遇到了实例化通用构造函数的困难。传递确实让编码员能够更好地控制,但我不认为这是必要的,我希望它至少是可选的。

我对泛型的了解并不高,但这似乎可以解决问题。