JTable 中的 JComboBox 不显示选择
JComboBox in JTable is not displaying the selection
渲染器和编辑器听起来很简单,尽管我 return 关于类似的问题有十几个 SO 书签,但我遗漏了一些基本的东西。我想将任何旧文本文件拖到 2 列 JTable 中,让第一列显示文件名,第二列包含一个 JComboBox,其选项取决于拖动文件的内容。 (在下面的代码中,我只是伪造了一些条目。)
这一切正常,直到我从组合框中进行选择 - 选择不显示 - 只是一个组合框,正确填充但没有做出选择。我知道这一定与我对 renderers/editors 的滥用有关,但经过至少两周的失败后,我正在寻求专业帮助。如果您认为我完全不了解渲染器和编辑器的编写方式,那么,很高兴您没有看到我之前的尝试。
希望此代码符合 SSCCE 的要求 - 如果我包含了不该包含的内容,我深表歉意。我保留了 DnD 的东西以防它有一些意义。
为了它的价值,我使用了一个静态的 ComboBoxModel 列表(每行一个),因为每个 JComboBox 都包含不同的选项,同样还有 TableCellEditors(尽管我不知道这是否是正确的方法)。
至 运行 这只需将任何文件拖到出现的 table 中,然后从右列的 JComboBox 中进行选择,然后看着它忽略您。非常感谢,即使您有一些建议也无需麻烦 运行ning this。
Java 1.7/OS X 10.9.5/Eclipse Mars.2
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.AbstractCellEditor;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.MutableComboBoxModel;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.event.ListDataListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
public class Main extends JFrame {
static List<AComboBoxModel> priceComboModels = new ArrayList<AComboBoxModel>();
static List<DefaultCellEditor> editors = new ArrayList<DefaultCellEditor>();
public Main() {
setLayout(new BorderLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(500, 400));
JPanel panel = new JPanel(new BorderLayout());
JTable table = new JTable(0, 2) {
public TableCellEditor getCellEditor(int rinx, int cinx) {
if (cinx == 0) {
return super.getCellEditor(rinx, cinx);
}
return editors.get(rinx);
}
};
table.setPreferredScrollableViewportSize(new Dimension(360, 80));
table.setTransferHandler(new ATransferHandler());
table.setModel(new ATableModel());
TableColumnModel tcm = table.getColumnModel();
tcm.getColumn(0).setHeaderValue("File Name");
tcm.getColumn(1).setHeaderValue("Selection");
TableColumn column = tcm.getColumn(1);
column.setCellRenderer(new ACellRenderer());
column.setCellEditor(new ACellEditor());
table.setDragEnabled(true);
table.setFillsViewportHeight(true);
JScrollPane sp = new JScrollPane(
table,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED
);
panel.add(sp, BorderLayout.CENTER);
panel.setPreferredSize(new Dimension(200, 300));
add(panel, BorderLayout.CENTER);
pack();
}
public static int addComboModel(AComboBoxModel model) {
priceComboModels.add(model);
return priceComboModels.size() - 1;
}
public static AComboBoxModel getComboModelAt(int inx) {
return priceComboModels.get(inx);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Main().setVisible(true);
}
});
}
}
class ATableModel extends DefaultTableModel {
List<ARecord> data = new ArrayList<ARecord>();
public void addRow(ARecord row) {
data.add(row);
fireTableRowsInserted(data.size() - 1, data.size() - 1);
}
@Override
public int getRowCount() {
return data == null ? 0 : data.size();
}
@Override
public int getColumnCount() {
return 2;
}
public void setValueAt(Object value, int rinx, int cinx) {
ARecord row = data.get(rinx);
switch (cinx) {
case 0:
row.setFilename((String) value);
break;
case 1:
row.setCbox((JComboBox) value);
break;
}
}
@Override
public Object getValueAt(int rinx, int cinx) {
Object returnValue = null;
ARecord row = data.get(rinx);
switch (cinx) {
case 0:
returnValue = row.getFilename();
break;
case 1:
returnValue = row.getCbox();
break;
}
return returnValue;
}
// I assume this is unnecessary since column 1 defaults to text
// and column 2 is handled by ACellRenderer. I think.
// @Override
// public Class getColumnClass(int cinx) {
// return cinx == 0 ? String.class : JComboBox.class;
// }
}
//////////////////////////////////////////////////////////////////////////////////
// This class handles the drag and drop.
class ATransferHandler extends TransferHandler {
int getSourceActions(JList<String> lst) {
return TransferHandler.COPY;
}
Transferable createTransferable(JList<String> list) {
return null;
}
void exportDone(JList<String> lst, Transferable data, int action) {
}
public boolean canImport(TransferHandler.TransferSupport info) {
return true;
}
//////////////////////////////////////////////////////////////////////////
// This is the method of interest where the dropped text file is handled.
//////////////////////////////////////////////////////////////////////////
public boolean importData(TransferHandler.TransferSupport info) {
if (! info.isDrop()) return false;
JTable table = (JTable)info.getComponent();
Transferable tr = info.getTransferable();
List<File> files = null;
try {
files = (List<File>)tr.getTransferData(DataFlavor.javaFileListFlavor);
} catch(UnsupportedFlavorException | IOException e) {
}
ATableModel tm = (ATableModel)table.getModel();
String[] options;
// For each dropped text file...
for (File fl : files) {
String fname = fl.getName();
// Just fill the JComboBox with some unique options for now
// (in practice this comes from the dropped text file contents).
String dummyText = fname.substring(0, 5);
options = new String[] { dummyText + "_A", dummyText + "_B", dummyText + "_C" };
// Create a ComboBoxModel for this JComboBox containing the selection options.
AComboBoxModel cboModel = new AComboBoxModel(options);
// Create the combo box itself.
JComboBox<String> cbox = new JComboBox<String>();
// Assign the model to the box.
cbox.setModel(cboModel);
// Create and add to the editor list the table cell editor.
Main.editors.add(new DefaultCellEditor(cbox));
// Also add the ComboBoxModel to the model list.
Main.addComboModel(cboModel);
// Add the row to the model data.
tm.addRow(new ARecord(fname, cbox));
}
return true;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
class ARecord {
String filename;
JComboBox cbox;
// Just a bean to describe a table row (a filename and a JComboBox).
public ARecord(String filename, JComboBox cbox) {
super();
this.filename = filename;
this.cbox = cbox;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public JComboBox getCbox() {
return cbox;
}
public void setCbox(JComboBox cbox) {
this.cbox = cbox;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// This is the model for the JComboBoxes. A different model is instantiated
// for each row since each one has different contents.
class AComboBoxModel implements MutableComboBoxModel {
List<String> items = new ArrayList<String>();
public AComboBoxModel(String[] items) {
this.items = Arrays.asList(items);
}
@Override
public int getSize() {
return items.size();
}
@Override
public Object getElementAt(int index) {
return items.get(index);
}
@Override
public void addListDataListener(ListDataListener l) {
}
@Override
public void removeListDataListener(ListDataListener l) {
}
@Override
public void setSelectedItem(Object anItem) {
}
@Override
public Object getSelectedItem() {
return null;
}
@Override
public void addElement(Object item) {
}
@Override
public void removeElement(Object obj) {
}
@Override
public void insertElementAt(Object item, int index) {
}
@Override
public void removeElementAt(int index) {
}
}
//////////////////////////////////////////////////////////////////////////////////////
// I won't pretend that I'm confident as to how this should work. My guess is that
// I should just retrieve the appropriate ComboBoxModel, assign it and return.
class ACellRenderer extends JComboBox implements TableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int rinx, int cinx) {
setModel(Main.getComboModelAt(rinx));
return this;
}
}
/////////////////////////////////////////////////////////////////////////////////////////
class ACellEditor extends AbstractCellEditor implements TableCellEditor {
static JComboBox box = null;
// This is where I think I'm actually lost. I don't understand the significance of
// returning a JComboBox when one was already created when the text file was
// dropped. Is it correct to just assign the appropriate ComboBoxModel to a JComboBox
// and return it here?
public Component getTableCellEditorComponent(JTable table,
Object value,
boolean isSelected,
int rinx,
int cinx) {
box = (JComboBox)(table.getModel().getValueAt(rinx, cinx));
box.setModel(Main.getComboModelAt(rinx));
return box;
}
@Override
public Object getCellEditorValue() {
return box;
}
}
make a selection from the JComboBox in the right column and watch it ignore you
您的自定义编辑器出了点问题,我不确定是什么。你有一个大问题,因为你试图使用 JComboBox 作为编辑器的数据。这是完全错误的。
但好消息是您无需使用自定义渲染器或自定义编辑器。
您不应在 TableModel 中存储 JComboBox。您只需存储组合框中所选项目的字符串。 (这将由默认的组合框编辑器自动为您完成)。
您无需为每个拖到 table 的文件创建一个新的编辑器。
the second contain a JComboBox whose options depend on the contents of the dragged file
因此,您需要自定义的 table 的唯一部分是 getCellEditor(...)
方法。
我猜你会为给定的文件扩展名使用不同的编辑器。
所以基本代码可能是这样的:
int modelColumn = convertColumnIndexToModel( column );
if (modelColumn == 1)
{
String file = getModel.getValueAt(row, 0);
if (file.endsWith(".txt"))
return txtEditor;
else if (file.endsWith(".html"))
return htmlEditor;
}
return super.getCellEditor(row, column);
查看:
How to add unique JComboBoxes to a column in a JTable (Java) 一个工作示例。该帖子中的逻辑确实有一个单独的行编辑器,仅用于演示目的。该示例演示代码可与默认渲染器和编辑器一起使用。您需要做的就是为每个组合框编辑器提供项目。
在您的情况下,编辑器将基于文件类型,因此逻辑需要测试第一列中的数据。
注意:嵌套的if/else语句不是一个好的解决方案。您可能想要使用 filetype/editor 的 Hashmap。然后,一旦您提取文件的文件类型,getCellEditor(...) 方法将只是一个 Hashmap 查找。
所以你的拖动代码应该与table的编辑器无关。您需要事先知道要支持哪些文件类型,并为每种文件类型定义有效项目。
此外,您的 TableModel 不应扩展 DefaultTableModel。您正在提供自己的数据存储并实现所有方法,因此您应该只扩展 AbstractTableModel。
渲染器和编辑器听起来很简单,尽管我 return 关于类似的问题有十几个 SO 书签,但我遗漏了一些基本的东西。我想将任何旧文本文件拖到 2 列 JTable 中,让第一列显示文件名,第二列包含一个 JComboBox,其选项取决于拖动文件的内容。 (在下面的代码中,我只是伪造了一些条目。)
这一切正常,直到我从组合框中进行选择 - 选择不显示 - 只是一个组合框,正确填充但没有做出选择。我知道这一定与我对 renderers/editors 的滥用有关,但经过至少两周的失败后,我正在寻求专业帮助。如果您认为我完全不了解渲染器和编辑器的编写方式,那么,很高兴您没有看到我之前的尝试。
希望此代码符合 SSCCE 的要求 - 如果我包含了不该包含的内容,我深表歉意。我保留了 DnD 的东西以防它有一些意义。
为了它的价值,我使用了一个静态的 ComboBoxModel 列表(每行一个),因为每个 JComboBox 都包含不同的选项,同样还有 TableCellEditors(尽管我不知道这是否是正确的方法)。
至 运行 这只需将任何文件拖到出现的 table 中,然后从右列的 JComboBox 中进行选择,然后看着它忽略您。非常感谢,即使您有一些建议也无需麻烦 运行ning this。
Java 1.7/OS X 10.9.5/Eclipse Mars.2
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.AbstractCellEditor;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.MutableComboBoxModel;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.event.ListDataListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
public class Main extends JFrame {
static List<AComboBoxModel> priceComboModels = new ArrayList<AComboBoxModel>();
static List<DefaultCellEditor> editors = new ArrayList<DefaultCellEditor>();
public Main() {
setLayout(new BorderLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(500, 400));
JPanel panel = new JPanel(new BorderLayout());
JTable table = new JTable(0, 2) {
public TableCellEditor getCellEditor(int rinx, int cinx) {
if (cinx == 0) {
return super.getCellEditor(rinx, cinx);
}
return editors.get(rinx);
}
};
table.setPreferredScrollableViewportSize(new Dimension(360, 80));
table.setTransferHandler(new ATransferHandler());
table.setModel(new ATableModel());
TableColumnModel tcm = table.getColumnModel();
tcm.getColumn(0).setHeaderValue("File Name");
tcm.getColumn(1).setHeaderValue("Selection");
TableColumn column = tcm.getColumn(1);
column.setCellRenderer(new ACellRenderer());
column.setCellEditor(new ACellEditor());
table.setDragEnabled(true);
table.setFillsViewportHeight(true);
JScrollPane sp = new JScrollPane(
table,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED
);
panel.add(sp, BorderLayout.CENTER);
panel.setPreferredSize(new Dimension(200, 300));
add(panel, BorderLayout.CENTER);
pack();
}
public static int addComboModel(AComboBoxModel model) {
priceComboModels.add(model);
return priceComboModels.size() - 1;
}
public static AComboBoxModel getComboModelAt(int inx) {
return priceComboModels.get(inx);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Main().setVisible(true);
}
});
}
}
class ATableModel extends DefaultTableModel {
List<ARecord> data = new ArrayList<ARecord>();
public void addRow(ARecord row) {
data.add(row);
fireTableRowsInserted(data.size() - 1, data.size() - 1);
}
@Override
public int getRowCount() {
return data == null ? 0 : data.size();
}
@Override
public int getColumnCount() {
return 2;
}
public void setValueAt(Object value, int rinx, int cinx) {
ARecord row = data.get(rinx);
switch (cinx) {
case 0:
row.setFilename((String) value);
break;
case 1:
row.setCbox((JComboBox) value);
break;
}
}
@Override
public Object getValueAt(int rinx, int cinx) {
Object returnValue = null;
ARecord row = data.get(rinx);
switch (cinx) {
case 0:
returnValue = row.getFilename();
break;
case 1:
returnValue = row.getCbox();
break;
}
return returnValue;
}
// I assume this is unnecessary since column 1 defaults to text
// and column 2 is handled by ACellRenderer. I think.
// @Override
// public Class getColumnClass(int cinx) {
// return cinx == 0 ? String.class : JComboBox.class;
// }
}
//////////////////////////////////////////////////////////////////////////////////
// This class handles the drag and drop.
class ATransferHandler extends TransferHandler {
int getSourceActions(JList<String> lst) {
return TransferHandler.COPY;
}
Transferable createTransferable(JList<String> list) {
return null;
}
void exportDone(JList<String> lst, Transferable data, int action) {
}
public boolean canImport(TransferHandler.TransferSupport info) {
return true;
}
//////////////////////////////////////////////////////////////////////////
// This is the method of interest where the dropped text file is handled.
//////////////////////////////////////////////////////////////////////////
public boolean importData(TransferHandler.TransferSupport info) {
if (! info.isDrop()) return false;
JTable table = (JTable)info.getComponent();
Transferable tr = info.getTransferable();
List<File> files = null;
try {
files = (List<File>)tr.getTransferData(DataFlavor.javaFileListFlavor);
} catch(UnsupportedFlavorException | IOException e) {
}
ATableModel tm = (ATableModel)table.getModel();
String[] options;
// For each dropped text file...
for (File fl : files) {
String fname = fl.getName();
// Just fill the JComboBox with some unique options for now
// (in practice this comes from the dropped text file contents).
String dummyText = fname.substring(0, 5);
options = new String[] { dummyText + "_A", dummyText + "_B", dummyText + "_C" };
// Create a ComboBoxModel for this JComboBox containing the selection options.
AComboBoxModel cboModel = new AComboBoxModel(options);
// Create the combo box itself.
JComboBox<String> cbox = new JComboBox<String>();
// Assign the model to the box.
cbox.setModel(cboModel);
// Create and add to the editor list the table cell editor.
Main.editors.add(new DefaultCellEditor(cbox));
// Also add the ComboBoxModel to the model list.
Main.addComboModel(cboModel);
// Add the row to the model data.
tm.addRow(new ARecord(fname, cbox));
}
return true;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
class ARecord {
String filename;
JComboBox cbox;
// Just a bean to describe a table row (a filename and a JComboBox).
public ARecord(String filename, JComboBox cbox) {
super();
this.filename = filename;
this.cbox = cbox;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public JComboBox getCbox() {
return cbox;
}
public void setCbox(JComboBox cbox) {
this.cbox = cbox;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// This is the model for the JComboBoxes. A different model is instantiated
// for each row since each one has different contents.
class AComboBoxModel implements MutableComboBoxModel {
List<String> items = new ArrayList<String>();
public AComboBoxModel(String[] items) {
this.items = Arrays.asList(items);
}
@Override
public int getSize() {
return items.size();
}
@Override
public Object getElementAt(int index) {
return items.get(index);
}
@Override
public void addListDataListener(ListDataListener l) {
}
@Override
public void removeListDataListener(ListDataListener l) {
}
@Override
public void setSelectedItem(Object anItem) {
}
@Override
public Object getSelectedItem() {
return null;
}
@Override
public void addElement(Object item) {
}
@Override
public void removeElement(Object obj) {
}
@Override
public void insertElementAt(Object item, int index) {
}
@Override
public void removeElementAt(int index) {
}
}
//////////////////////////////////////////////////////////////////////////////////////
// I won't pretend that I'm confident as to how this should work. My guess is that
// I should just retrieve the appropriate ComboBoxModel, assign it and return.
class ACellRenderer extends JComboBox implements TableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int rinx, int cinx) {
setModel(Main.getComboModelAt(rinx));
return this;
}
}
/////////////////////////////////////////////////////////////////////////////////////////
class ACellEditor extends AbstractCellEditor implements TableCellEditor {
static JComboBox box = null;
// This is where I think I'm actually lost. I don't understand the significance of
// returning a JComboBox when one was already created when the text file was
// dropped. Is it correct to just assign the appropriate ComboBoxModel to a JComboBox
// and return it here?
public Component getTableCellEditorComponent(JTable table,
Object value,
boolean isSelected,
int rinx,
int cinx) {
box = (JComboBox)(table.getModel().getValueAt(rinx, cinx));
box.setModel(Main.getComboModelAt(rinx));
return box;
}
@Override
public Object getCellEditorValue() {
return box;
}
}
make a selection from the JComboBox in the right column and watch it ignore you
您的自定义编辑器出了点问题,我不确定是什么。你有一个大问题,因为你试图使用 JComboBox 作为编辑器的数据。这是完全错误的。
但好消息是您无需使用自定义渲染器或自定义编辑器。
您不应在 TableModel 中存储 JComboBox。您只需存储组合框中所选项目的字符串。 (这将由默认的组合框编辑器自动为您完成)。
您无需为每个拖到 table 的文件创建一个新的编辑器。
the second contain a JComboBox whose options depend on the contents of the dragged file
因此,您需要自定义的 table 的唯一部分是 getCellEditor(...)
方法。
我猜你会为给定的文件扩展名使用不同的编辑器。
所以基本代码可能是这样的:
int modelColumn = convertColumnIndexToModel( column );
if (modelColumn == 1)
{
String file = getModel.getValueAt(row, 0);
if (file.endsWith(".txt"))
return txtEditor;
else if (file.endsWith(".html"))
return htmlEditor;
}
return super.getCellEditor(row, column);
查看: How to add unique JComboBoxes to a column in a JTable (Java) 一个工作示例。该帖子中的逻辑确实有一个单独的行编辑器,仅用于演示目的。该示例演示代码可与默认渲染器和编辑器一起使用。您需要做的就是为每个组合框编辑器提供项目。
在您的情况下,编辑器将基于文件类型,因此逻辑需要测试第一列中的数据。
注意:嵌套的if/else语句不是一个好的解决方案。您可能想要使用 filetype/editor 的 Hashmap。然后,一旦您提取文件的文件类型,getCellEditor(...) 方法将只是一个 Hashmap 查找。
所以你的拖动代码应该与table的编辑器无关。您需要事先知道要支持哪些文件类型,并为每种文件类型定义有效项目。
此外,您的 TableModel 不应扩展 DefaultTableModel。您正在提供自己的数据存储并实现所有方法,因此您应该只扩展 AbstractTableModel。