基本上我需要做的是在我的ListSelectionListener
上为行和列模型设置一个JTable
,这样我就知道哪个单元格被点击了,我很好。
如果我点击的单元格恰好有一个JTree
作为渲染器组件,那么我将展开/折叠JTree,调整表格行的高度并重新绘制表。所以我的ListSelectionListener
看起来像这样
static class TableCellSelectionListener implements ListSelectionListener {
private JTable _t;
@Override
public void valueChanged(ListSelectionEvent lse) {
if(!lse.getValueIsAdjusting()) {
int rowSelected = _t.getSelectedRow();
int colSelected = _t.getSelectedColumn();
if(rowSelected > -1 && colSelected > -1) {
TableCellRenderer renderer = _t.getCellRenderer(rowSelected, colSelected);
Object obj = _t.getValueAt(rowSelected, colSelected);
Component c = renderer.getTableCellRendererComponent(_t, obj, true, false, rowSelected, colSelected);
if(c instanceof JTree) {
JTree tree = (JTree) c;
TreePath path = tree.getPathForRow(0);
if(tree.isExpanded(path)) {
tree.collapsePath(path);
tree.fireTreeCollapsed(path);
} else {
tree.expandPath(path);
tree.fireTreeExpanded(path);
}
Enumeration<TableColumn> columns = _t.getColumnModel().getColumns();
while(columns.hasMoreElements()) {
TableColumn col = columns.nextElement();
int colIndex = col.getModelIndex();
if(_t.getColumnClass(colIndex) == JTree.class && colIndex != colSelected) {
JTree t = (JTree) _t.getValueAt(rowSelected, colIndex);
TreePath p = t.getPathForRow(0);
if(tree.isExpanded(path) && !t.isExpanded(p)) {
t.expandPath(p);
t.fireTreeExpanded(p);
} else if(!tree.isExpanded(path) && t.isExpanded(p)) {
t.collapsePath(p);
t.fireTreeCollapsed(p);
}
}
}
_t.setRowHeight(rowSelected, c.getPreferredSize().height);
//Line below causes StackOverFlow
((SortableTableModel)_t.getModel()).fireTableDataChanged();
_t.clearSelection();
}
}
}
}
public void setTable(JTable t) {
_t = t;
}
}
&#34;正确的&#34;这样做的方法?
因为我真正感兴趣的是正确的方法,这里有完整的可编译代码,运行它你很快就会看到我的目标(如前所述,表格单元格中有JTree组件可以展开/折叠,高度为表行也需要反映JTree状态)
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.plaf.IconUIResource;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
public class ProgressBarPCSTable {
private ProgressBarValueTracker pbValueTracker = new ProgressBarValueTracker();
private String[] columnNames = {"JobID","Progress", "Status"};
private DefaultTableModel tableModel = new DefaultTableModel(null, columnNames) {
private static final long serialVersionUID = 1L;
@Override
public Class<?> getColumnClass(int column) {
return getValueAt(0, column).getClass();
}
@Override
public boolean isCellEditable(int row, int col) {
return false;
}
};
private JTable table = new JTable(tableModel);
public JComponent makeUI() {
TableColumn progressColumn = table.getColumnModel().getColumn(1);
progressColumn.setCellRenderer(new JTreeRenderer(true, pbValueTracker));
TableColumn statusColumn = table.getColumnModel().getColumn(2);
statusColumn.setCellRenderer(new JTreeRenderer(false, pbValueTracker));
//Setup row/column selection listeners
table.setRowSelectionAllowed(true);
TableCellSelectionListener cellSelectionListener = new TableCellSelectionListener(table);
table.getSelectionModel().addListSelectionListener(cellSelectionListener);
table.getColumnModel().getSelectionModel().addListSelectionListener(cellSelectionListener);
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
startTask(new Object[][]{{"000001"}, {0}, {"complete"}});
startTask(new Object[][]{{"000002"}, {0,0}, {"processing","rendering pdf"}});
startTask(new Object[][]{{"000003"}, {0,0,0}, {"processing","rendering pdf","rendering afp"}});
startTask(new Object[][]{{"000004"}, {0,0,0,0}, {"processing","rendering pdf","rendering afp","rendering postscript"}});
startTask(new Object[][]{{"000005"}, {0,0,0,0,0}, {"processing","normalsing","enhancing","sorting","rendering"}});
}
});
JPanel p = new JPanel(new BorderLayout());
p.add(new JScrollPane(table));
return p;
}
private void startTask(final Object[][] job) {
final int key = tableModel.getRowCount();
final String jobID = (String) job[0][0];
final DefaultMutableTreeNode progressRoot = new DefaultMutableTreeNode((Integer)job[1][0]);
final DefaultMutableTreeNode statusRoot = new DefaultMutableTreeNode((String)job[2][0]);
final DefaultTreeModel progressTreeModel = new DefaultTreeModel(progressRoot);
final DefaultTreeModel statusTreeModel = new DefaultTreeModel(statusRoot);
final JTree progressTree = new JTree(progressTreeModel);
final JTree statusTree = new JTree(statusTreeModel);
if(job[1].length > 1) {
initJTree(progressRoot, Arrays.copyOfRange(job[1], 1, job[1].length, Integer[].class));
((DefaultTreeModel)progressTree.getModel()).nodeChanged(progressRoot);
}
if(job[2].length > 1) {
initJTree(statusRoot, Arrays.copyOfRange(job[2], 1, job[2].length, String[].class));
}
pbValueTracker.initPVMap(progressTree);
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
startProgressBarTask(progressTree, progressRoot, key, false);
int childCount = progressRoot.getChildCount();
for(int i=0; i < childCount; i++) {
startProgressBarTask(progressTree, (DefaultMutableTreeNode)progressRoot.getChildAt(i), key, i == 1 ? true : false);
}
}
});
tableModel.addRow(new Object[]{jobID, progressTree, statusTree});
}
private <T> void initJTree(DefaultMutableTreeNode root, T[] list) {
for(T t: list) {
root.add(new DefaultMutableTreeNode(t));
}
}
private void startProgressBarTask(final JTree progressTree, final DefaultMutableTreeNode node,
final int rowCount, final boolean error) {
LoudCall<Void, JTree> progressShout = new LoudCall<Void, JTree>() {
@Override
public Void call() throws Exception {
SwingWorker<Integer, Integer> progressWorker = new SwingWorker<Integer, Integer>() {
private int sleepDummy = new Random().nextInt(100) + 1;
private int lengthOfTask = 120;
/**
* Overrides the SwingWorker doInBackground, this version, increments
* the value of the % complete and publishes it, the process method will
* pick up the published value so that the ProgressBarRenderer can
* deal with it. It also triggers a nodeChanged event on the DefaultTreeModel
* so that the JTree updates
*/
@Override
protected Integer doInBackground() throws Exception {
int current = 0;
DefaultMutableTreeNode root = (DefaultMutableTreeNode) progressTree.getModel().getRoot();
String suffix = root == node ? "_root" : "_" +root.getIndex(node);
String key = String.valueOf(System.identityHashCode(progressTree)) +suffix;
while(current < lengthOfTask && !isCancelled()) {
if(error && current >= 60) { //Error test
cancel(true);
publish(-1);
pbValueTracker.putErrMap(key, -1);
System.out.println(pbValueTracker.getErrMap());
return -1;
}
current++;
try {
Thread.sleep(sleepDummy);
} catch (InterruptedException ie) {
break;
}
int value = 100 * current / lengthOfTask;
publish(value);
if(!pbValueTracker.getErrMap().containsKey(key)) {
pbValueTracker.putPVMap(key, value);
}
}
return sleepDummy * lengthOfTask;
}
/**
* Attach a user object to the node, in this case
* it is an Integer with the latest value triggered by publish
* process will fire getTreeCellRendererComponent of the
* ProgressBarRenderer
*
* @param c - a list of Integer to process, only process the last value set
*/
@Override
protected void process(List<Integer> c) {
node.setUserObject(c.get(c.size() - 1));
shoutOut(progressTree);
((DefaultTreeModel)progressTree.getModel()).nodeChanged(node);
}
@Override
protected void done() {
int i = -1;
if(!isCancelled()) {
try {
i = get();
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("Value: " +i);
shoutOut(progressTree);
((DefaultTreeModel)progressTree.getModel()).nodeChanged(node);
System.out.println(pbValueTracker.getPVMap());
}
};
progressWorker.execute();
return null;
}
};
(new ListenerTask<Void, JTree>(progressShout) {
@Override
protected void process(List<JTree> chunks) {
tableModel.setValueAt(chunks.get(chunks.size() - 1), rowCount, 1);
tableModel.fireTableDataChanged();
}
}).execute();
}
public static void main(String... args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.getContentPane().add(new ProgressBarPCSTable().makeUI());
frame.setSize(669, 307);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
/**
* This class listens for selection of a table cell
* if selected and the cells component is a JTree it
* expands/collapses the JTree and adjusts the height
* of the table row accordingly
*
* @author pstatham
*
*/
class TableCellSelectionListener implements ListSelectionListener {
private JTable _t;
public TableCellSelectionListener(JTable t) {
_t = t;
}
@Override
public void valueChanged(ListSelectionEvent lse) {
if(!lse.getValueIsAdjusting()) {
int rowSelected = _t.getSelectedRow();
int colSelected = _t.getSelectedColumn();
if(rowSelected > -1 && colSelected > -1) {
TableCellRenderer renderer = _t.getCellRenderer(rowSelected, colSelected);
Object obj = _t.getValueAt(rowSelected, colSelected);
Component c = renderer.getTableCellRendererComponent(_t, obj, true, false, rowSelected, colSelected);
if(c instanceof JTree) {
JTree tree = (JTree) c;
TreePath path = tree.getPathForRow(0);
if(tree.isExpanded(path)) {
tree.collapsePath(path);
tree.fireTreeCollapsed(path);
} else {
tree.expandPath(path);
tree.fireTreeExpanded(path);
}
Enumeration<TableColumn> columns = _t.getColumnModel().getColumns();
while(columns.hasMoreElements()) {
TableColumn col = columns.nextElement();
int colIndex = col.getModelIndex();
if(_t.getColumnClass(colIndex) == JTree.class && colIndex != colSelected) {
JTree t = (JTree) _t.getValueAt(rowSelected, colIndex);
TreePath p = t.getPathForRow(0);
if(tree.isExpanded(path) && !t.isExpanded(p)) {
t.expandPath(p);
t.fireTreeExpanded(p);
} else if(!tree.isExpanded(path) && t.isExpanded(p)) {
t.collapsePath(p);
t.fireTreeCollapsed(p);
}
}
}
_t.setRowHeight(rowSelected, c.getPreferredSize().height);
((DefaultTableModel)_t.getModel()).fireTableDataChanged();
_t.clearSelection();
}
}
}
}
}
/**
* This class keeps track of a particular JProgressBar's previous
* value in case it has gone in to error
* @author pstatham
*
*/
class ProgressBarValueTracker {
private HashMap<String, Integer> pvMap = new HashMap<String, Integer>();
private HashMap<String, Integer> errMap = new HashMap<String, Integer>();
public void initPVMap(JTree tree) {
String key = String.valueOf(System.identityHashCode(tree)) +"_root";
DefaultMutableTreeNode root = (DefaultMutableTreeNode) ((DefaultTreeModel)tree.getModel()).getRoot();
pvMap.put(key, (Integer) root.getUserObject());
for(int i=0; i < root.getChildCount(); i++) {
key = String.valueOf(System.identityHashCode(tree)) +"_" + i;
DefaultMutableTreeNode child = (DefaultMutableTreeNode) root.getChildAt(i);
pvMap.put(key, (Integer) child.getUserObject());
}
}
public void putPVMap(String key, Integer value) {
pvMap.put(key, value);
}
public void putErrMap(String key, Integer value) {
errMap.put(key, value);
}
public HashMap<String, Integer> getPVMap() {
return pvMap;
}
public HashMap<String, Integer> getErrMap() {
return errMap;
}
}
/**
* This class extends the DefaultTreeCellRenderer and returns a JPanel with a
* JProgressBar attached as its renderer component
* @author pstatham
*
*/
@SuppressWarnings("serial")
class ProgressBarRenderer extends DefaultTreeCellRenderer {
private final JProgressBar progressBar = new JProgressBar(0, 100);
private ProgressBarValueTracker pbValueTracker;
public ProgressBarRenderer(ProgressBarValueTracker tracker) {
super();
pbValueTracker = tracker;
setOpaque(true);
configureProgressBar(progressBar);
progressBar.setBackground(Color.YELLOW);
}
@Override
public Component getTreeCellRendererComponent(JTree tree, final Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
if(((DefaultMutableTreeNode) value).getUserObject() instanceof String) {
super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
return this;
}
DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
String suffix = root == value ? "_root" : "_" +root.getIndex((DefaultMutableTreeNode)value);
String key = String.valueOf(System.identityHashCode(tree)) +suffix;
JPanel p = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 0));
p.setBackground(Color.WHITE);
JLabel l = (JLabel)super.getTreeCellRendererComponent(tree, null, selected, expanded, leaf, row, hasFocus);
if(((DefaultMutableTreeNode)value).isRoot() && ((DefaultMutableTreeNode)value).getChildCount() > 0) {
TreePath path = tree.getPathForRow(row);
if(tree.isExpanded(path)) {
l.setIcon(new IconUIResource(new NodeIcon('-')));
} else {
l.setIcon(new IconUIResource(new NodeIcon('+')));
}
} else {
l.setIcon(new IconUIResource(new NodeIcon(' ')));
}
p.add(l);
Integer i = (Integer) ((DefaultMutableTreeNode) value).getUserObject();
//
// If Job is in error return a different
// JProgressBar with a red background
//
if(i<0) {
JProgressBar errorProgressBar = new JProgressBar(0, 100);
configureProgressBar(errorProgressBar);
errorProgressBar.setBackground(Color.RED);
errorProgressBar.setValue(pbValueTracker.getPVMap().get(key));
p.add(errorProgressBar);
} else {
progressBar.setValue(i);
p.add(progressBar);
}
return p;
}
/**
* Configure a JProgressBar with common options
*
* @param pb JProgressBar to configure
*/
private void configureProgressBar(JProgressBar pb) {
pb.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
pb.setForeground(Color.GREEN.darker());
pb.setStringPainted(true);
pb.setPreferredSize(new Dimension(pb.getPreferredSize().width, 16));
}
}
/**
* This class extends the DefaultTableCellRenderer and returns a JTree
* as its renderer component. The JTrees renderer
* can either be an instance of a ProgressBarRenderer or a
* DefaultTreeCellRenderer that returns a label with an open/close icon
* as its renderer component.
* @author pstatham
*
*/
@SuppressWarnings("serial")
class JTreeRenderer extends DefaultTableCellRenderer {
private JTree tree;
private TreeCellRenderer renderer;
public JTreeRenderer(boolean progressTree, ProgressBarValueTracker tracker) {
if(progressTree) {
renderer = new ProgressBarRenderer(tracker);
} else {
renderer = new DefaultTreeCellRenderer() {
@Override
public Component getTreeCellRendererComponent(final JTree tree, Object value,
boolean sel,boolean expanded,boolean leaf,int row,boolean hasFocus){
JLabel label = (JLabel)super.getTreeCellRendererComponent(tree,value,
sel,expanded,leaf,row,hasFocus);
if(((DefaultMutableTreeNode)value).isRoot() && ((DefaultMutableTreeNode)value).getChildCount() > 0) {
TreePath path = tree.getPathForRow(row);
if(tree.isExpanded(path)) {
label.setIcon(new IconUIResource(new NodeIcon('-')));
} else {
label.setIcon(new IconUIResource(new NodeIcon('+')));
}
} else {
label.setIcon(new IconUIResource(new NodeIcon(' ')));
}
return label;
}
};
}
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
tree = (JTree) value;
tree.setCellRenderer(renderer);
table.setRowHeight(row, tree.getPreferredSize().height);
return tree;
}
}
/**
* Pinched from http://stackoverflow.com/a/7984734/564045
* @author pstatham
*
*/
class NodeIcon implements Icon {
private static final int SIZE = 9;
private char type;
public NodeIcon(char type) {
this.type = type;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
if(type != ' ') {
g.setColor(UIManager.getColor("Tree.background"));
g.fillRect(x, y, SIZE - 1, SIZE - 1);
g.setColor(UIManager.getColor("Tree.hash").darker());
g.drawRect(x, y, SIZE - 1, SIZE - 1);
g.setColor(UIManager.getColor("Tree.foreground"));
g.drawLine(x + 2, y + SIZE / 2, x + SIZE - 3, y + SIZE / 2);
if (type == '+') {
g.drawLine(x + SIZE / 2, y + 2, x + SIZE / 2, y + SIZE - 3);
}
}
}
public int getIconWidth() {
return SIZE;
}
public int getIconHeight() {
return SIZE;
}
}
/**
* Wrapper for the background logic
* http://stackoverflow.com/a/6834797/564045
* @author pstatham
*
* @param <T> return type
* @param <S> intermediary type (the "shout out")
*/
abstract class LoudCall<T, S> implements Callable<T> {
private PropertyChangeSupport pcs;
private S shout;
public LoudCall() {
pcs = new PropertyChangeSupport(this);
}
public void shoutOut(S s) {
pcs.firePropertyChange("shoutOut", this.shout, this.shout = s);
}
public void addListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void removeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
@Override
public abstract T call() throws Exception;
}
/**
* Wrapper for the GUI listener.
* http://stackoverflow.com/a/6834797/564045
* @author pstatham
*
* @param <T> return type
* @param <S> intermediary type (the "shout out" to listen for)
*/
abstract class ListenerTask<T, S> extends SwingWorker<T, S> implements PropertyChangeListener {
private LoudCall<T, S> aMethod;
public ListenerTask(LoudCall<T, S> aMethod) {
this.aMethod = aMethod;
}
@Override
protected T doInBackground() throws Exception {
aMethod.addListener(this);
return aMethod.call();
}
@SuppressWarnings("unchecked")
@Override
public void propertyChange(PropertyChangeEvent evt) {
if("shoutOut".equals(evt.getPropertyName())) {
publish((S)evt.getNewValue());
}
}
@Override
protected abstract void process(List<S> chunks);
}
答案 0 :(得分:0)
似乎EVERY DefaultTableModel.fire ...()方法也会导致ListSelectionEvent。想象一下,例如,删除之前选择的一些模型后,您的模型会更新。在这种情况下,必须重新计算列表选择,因此新的ListSelectionEvent。我猜JDK总是在TableModel.fire ...()。
之后重新计算列表选择另请参阅JDK的this错误报告。 我希望至少对于fireTableCellUpdated()它没有发生,但确实如此。
你可以做的是使用如上所述的标志,或者根据观察到第一个事件是选择&#39;清除&#39;来过滤掉这些事件。 event(选定的行为空),下一个事件与之前的选择相同。 在单选案例的代码下面:
int prevSel = -1;
...
public void valueChanged(ListSelectionEvent lse) {
if(lse.getValueIsAdjusting() || getSelectedRows().length==0 || getSelectedRows()[0]==prevSel )
return;
prevSel = getSelectedRows()[0];
... your code ...
fireTableDataChanged();
}
答案 1 :(得分:-1)
我真的不明白你的评论,但我设法通过设置布尔标志来实现它的工作
public void valueChanged(ListSelectionEvent lse) {
if(!lse.getValueIsAdjusting())
if(!methodCalled) {
//Logic for expanding/collapsing JTree and adjusting
//JTable row height
methodCalled = true;
fireTableDataChanged();
methodCalled = false;
}
}
}