I'm trying to create a JTree that consists of JTables. So far, i succeeded in creating a Jtree with Jtables.But, I cant change the row count of a table of a specific tree node. Whenever i try to adjust the row count, all of the tree's node's row count changes.
I used the code at the following link:
Jtable as a Jtree Node
I wrote the following code by the recommendation of Trashgod; but it didnt work; could you please give some working code..
package helperPack;
import java.awt.BorderLayout;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.tree.DefaultMutableTreeNode;
public class JTreeTrial extends JFrame {
/**
* #param args
*/
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
DefaultMutableTreeNode parentNode = new DefaultMutableTreeNode("node");
JTree tree = new JTree(parentNode);
JTable table = new JTable();
table.setModel(new DefaultTableModel() {
private static final long serialVersionUID = 1L;
#Override
public int getRowCount() {
return 2;
}
#Override
public int getColumnCount() {
return 2;
}
#Override
public Object getValueAt(int row, int column) {
return ":" + "row" + ":" + column;
}
});
DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getModel().getRoot();
node.setUserObject(table);
JTreeTrial trial=new JTreeTrial();
trial.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JScrollPane jsp = new JScrollPane(tree);
trial.add(jsp, BorderLayout.CENTER);
trial.pack();
trial.setVisible(true);
trial.setLocationRelativeTo(null);
}
});
}
}
Example :
|node1|
|a|b|
|c|d|
|node2|
|e|f|
|node3|
|g|h|
|i|j|
|k|m|
Instead of rendering the tables in the tree, add a TreeSelectionListener and update a single JTable in an adjacent component. Let each TreeNode contain a Tablemodel, and use setModel() to update the JTable. Several related examples are cited here.
Related
I have a Swing UI element that uses nested JTables. That is, the TableCellRenderer of the outer table returns JTable objects as the Component to render for each cells. This code works great under Java 8. Using the exact same code under Java 9 or Java 10 causes the inner table to not be repainted properly when scrolling the table. The table IS repainted properly when the window is resized.
Edit:
I did some more digging and traced the problem to lines 1862:1877 in BasicTableUI.java - these lines were added in Java 9. If I make my own TableUI without these lines, the problem disappears. From debugging, it appears that the issue is that in my case rMin and rMax are equal and setting rMax = rMax - 1 makes rMax < rMin which is obviously is bad. The added code seems to be there to deal with issues printing a table. Not sure why nested tables makes this break.
See the SSCE below:
package testing.test_painting;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import java.awt.Component;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class InnerTable extends JTable {
public InnerTable(TableModel dm) {
super(dm);
}
}
class InnerTableModel extends AbstractTableModel {
#Override
public int getRowCount() {
return 500;
}
#Override
public int getColumnCount() {
return 10;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return rowIndex + "," + columnIndex;
}
}
class OuterTableModel extends AbstractTableModel {
private final List<JTable> termTables;
public OuterTableModel() {
this.termTables = Arrays.asList(new InnerTable(new InnerTableModel()));
}
#Override
public int getRowCount() {
return 1;
}
#Override
public int getColumnCount() {
return termTables.size();
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return termTables.get(columnIndex);
}
public JTable getTermTable(int modelColumn) {
return termTables.get(modelColumn);
}
}
class OuterTable extends JTable {
private final List<TableRenderer> renderers;
private class TableRenderer implements TableCellRenderer {
private final OuterTableModel tableModel;
public TableRenderer(OuterTableModel tableModel) {
this.tableModel = tableModel;
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
int modelColumn = convertColumnIndexToModel(column);
JTable termTable = tableModel.getTermTable(modelColumn);
termTable.setVisible(true);
return termTable;
}
}
private final OuterTableModel tableModel;
public OuterTable(OuterTableModel tableModel) {
super(tableModel);
renderers = new ArrayList<>(tableModel.getColumnCount());
for (int i = 0; i < tableModel.getColumnCount(); i++) {
renderers.add(new TableRenderer(tableModel));
}
this.tableModel = tableModel;
setCellDimensions();
}
#Override
public void setTableHeader(JTableHeader tableHeader) {
tableHeader.setUI(new BasicTableHeaderUI());
super.setTableHeader(tableHeader);
}
private void setCellDimensions() {
Dimension preferredSize = tableModel.getTermTable(0).getPreferredSize();
if (getRowHeight() != preferredSize.height) {
setRowHeight(preferredSize.height);
}
TableColumnModel columnModel = getColumnModel();
for (int i = 0; i < columnModel.getColumnCount(); i++) {
TableColumn column = columnModel.getColumn(i);
column.setMinWidth(preferredSize.width);
column.setMaxWidth(preferredSize.width);
column.setPreferredWidth(preferredSize.width);
column.setWidth(preferredSize.width);
}
setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
}
#Override
public TableCellRenderer getCellRenderer(int row, int column) {
return renderers.get(column);
}
}
public class TestNestedTable {
public static void main(String[] args) {
OuterTableModel mainTableModel = new OuterTableModel();
OuterTable mainTable = new OuterTable(mainTableModel);
JFrame jFrame = new JFrame();
JScrollPane scrollPane = new JScrollPane(mainTable);
scrollPane.getVerticalScrollBar().setUnitIncrement(10);
jFrame.getContentPane().add(scrollPane);
jFrame.pack();
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setVisible(true);
}
}
make my own TableUI without these lines, the problem disappears.
Or simpler, you might be able to use JViewport#SIMPLE_SCROLL_MODE:
JScrollPane scrollPane = new JScrollPane(mainTable);
scrollPane.getVerticalScrollBar().setUnitIncrement(10);
scrollPane.getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
I know its and old question, but I had the same issue and maybe someone else with the same problem finds this helpful.
This is an JDK bug reported several times (so a lot of duplicates) but most information can be found in the following ticket:
https://bugs.openjdk.java.net/browse/JDK-8202702
There has been added these lines to the BasicTableUI:
// Do not decrement rMax if rMax becomes
// less than or equal to rMin
// else cells will not be painted
if (rMax - rMin > 1) {
rMax = rMax - 1;
}
(s. http://hg.openjdk.java.net/jdk/client/rev/3ba3d39b91c7
or
https://github.com/openjdk/jdk/commit/bb9fed1008dee377725dc669401c389da685f618#diff-ae77528c5d554a9870371e7075801adc4ecd8f992ff484804f164b692b388858)
It is fixed for JDK 12 onward. Unfortunaltey, it is not backported to JDK 11, so if you are forced to use this LTS version one can either do it like #MattWallace and use own TableUI or you can overwrite your Table extending JTable with
#Override
public int getSelectedRow()
{
final int i = super.getSelectedRow();
return i == -1 ? -2 : i;
}
This might bring the problems back mentioned in BasicTableUI comments:
// We did rMax-1 to paint the same number of rows that are drawn on console
// otherwise 1 extra row is printed per page than that are displayed
// when there is no scrollPane and we do printing of table
// but not when rmax is already pointing to index of last row
// and if there is any selected rows
But if you can live with that, this can be also a workaround. Other workarounds like wrapping the Table in a JViewPort or a JScrollPane I cannot recommend as I got size issues with tables which can have changing row heights due to long texts.
It was remarkably easy to introduce sorting for my JTable:
//Existing code
dftTableModel = new DefaultTableModel(0 , 4);
tblOutput = new JTable(dftTableModel);
//Added code
RowSorter<TableModel> sorter = new TableRowSorter<TableModel>(dftTableModel);
tblOutput.setRowSorter(sorter);
But since I formatted the Size column as text with commas, it didn't sort:
I had never used a Comparator but found an example that I modified.
public class RowSorterWithComparator
{
static Comparator compareNumericStringsWithCommas;
static TableRowSorter sorter;
static JTable tblOutput;
static JScrollPane pane;
static JFrame frame;
static DefaultTableModel dftTableModel;
// static TableColumnAdjuster tca ;
static DefaultTableCellRenderer rightRenderer;
static JButton btnOpen;
static String[] columnNames = { "Date", "Size" };
static Object rows[][] =
{
{"7/27/2015","96","mavenVersion.xml","C:\\Users\\Dov\\.AndroidStudio1.2\\config\\options\\"},
{"7/27/2015","120","keymap.xml","C:\\Users\\Dov\\.AndroidStudio1.2\\config\\options\\"},
{"7/27/2015","108","Default.xml","C:\\Users\\Dov\\.AndroidStudio1.2\\config\\inspection\\"},
{"4/27/2015","392","key pay.txt","C:\\Users\\Dov\\A\\"},
{"6/13/2015","161","BuildConfig.java","C:\\Users\\Dov\\androidfp2_examples\\eclipse_projects\\FlagQuiz\\gen\\com\\deitel\\flagquiz\\"}
};
public static void main(String args[])
{
compareNumericStringsWithCommas = (Comparator) new Comparator()
{
#Override public int compare(Object oo1, Object oo2)
{
String o1 = oo1.toString().replace(",", "");
String o2 = oo2.toString().replace(",", "");
return Integer.valueOf(o1).compareTo(Integer.valueOf(o2));
}
};
dftTableModel = new DefaultTableModel(0 , columnNames.length);
tblOutput = new JTable(dftTableModel);
dftTableModel.setColumnIdentifiers(new Object[]{"Date", "Size", "File name", "Path to file"});
rightRenderer = new DefaultTableCellRenderer();
rightRenderer.setHorizontalAlignment(SwingConstants.RIGHT);
tblOutput.getColumnModel().getColumn(1).setCellRenderer(rightRenderer);
sorter = new TableRowSorter<>(dftTableModel);
sorter.setModel(tblOutput.getModel());
sorter.setComparator(1,compareNumericStringsWithCommas);
tblOutput.setRowSorter(sorter);
tblOutput.setAutoResizeMode(AUTO_RESIZE_OFF);
// tca = new tablecolumnadjuster.TableColumnAdjuster(tblOutput);
// tca.setDynamicAdjustment(true);
tblOutput.setFont(new Font("Courier New",Font.PLAIN,12));
pane = new JScrollPane(tblOutput);
for (int i = 0; i < 5; i++)
dftTableModel.addRow(rows[i]);
btnOpen = new JButton("Open selected file");
btnOpen.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e)
{
int row = tblOutput.getSelectedRow();
String entry = (String)tblOutput.getModel().getValueAt(row, 3)
+ "\\" + (String)tblOutput.getModel().getValueAt(row, 2);
try
{
Desktop.getDesktop().open(new File((entry.trim())));
} catch (IOException ex) {System.out.println("Can't open file"); }
}
});
frame = new JFrame("Sort Table Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(pane, BorderLayout.CENTER);
frame.add(btnOpen, BorderLayout.AFTER_LAST_LINE);
frame.setSize(800, 350);
frame.setVisible(true);
}
}
Works great.
It was easy to right-justify the size column:
rightRenderer = new DefaultTableCellRenderer();
rightRenderer.setHorizontalAlignment(SwingConstants.RIGHT);
I added a button to open the selected file. But after sorting, the wrong file opened.
So I removed getModel from the statement in the mouse listener:
String entry = (String)tblOutput.getValueAt(row, 3)
+ "\\" + (String)tblOutput.getValueAt(row, 2);
I don't know why it was there in the first place since I've been stealing all
kinds of code from various places.
Anyway, everything works now.
But I have questions:
(1) When would getModel be required in the context of getting a table row's values?
I thought since dftTableModel was used for tblOutput that surely it was proper.
(2) Is the row returned by getModel the row that the data originally was at?
If so, I guess that could be useful on occasion. Not sure when.... To "un-sort"?
(3) Is the fact that I used TableRowSorter the reason getModel didn't get the
right data? (I.e., are the two incompatible?)
(4) Is dftTableModel equivalent to tblOutput.getModel()?
...............................
imports for program:
import java.awt.BorderLayout;
import java.awt.Desktop;
import java.awt.Font;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.util.Comparator;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import static javax.swing.JTable.AUTO_RESIZE_OFF;
import javax.swing.SwingConstants;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableRowSorter;
//import tablecolumnadjuster.TableColumnAdjuster;
So I removed getModel from the statement in the mouse listener:
The data in the TableModel is NOT sorted. The View (JTable) displays the data in a sorted order
You get the data from the view using:
table.getValueAt(row, column);
if you want to get the data from the TableModel you use:
int modelRow = table.convertRpwIndexToModel(row);
table.getModel().getValueAt(modelRow, column);
There are also method for converting the column indexes back and forth.
So you always need to know whether you are trying to access the data the way it is displayed on the JTable or the way it was loaded into the TableModel and then use the appropriate index which may (or may) not involve an index conversion.
String entry = (String)tblOutput.getValueAt(row, 3) ...
When you hardcode a value you need to know what the hard coded value represents. In this case you are accessing a dynamic row value from the table and a column from the model.
So in the above statement you need to convert the "3" from the model to the view since you are using the getValueAt(...) method of the table.
int viewColumn3 = table.convertColumnIndexToView(3);
String entry = (String)tblOutput.getValueAt(row, viewColumn3)
Or, since you are referencing hardcoded values multiple times (ie. columns 2 and 3) it may be easier to keep the hardcoded column indexes and only convert the row index once and then access the data from the TableModel:
String entry = (String)tblOutput.getModel().getValueAt(modelRow, 3) ...
This IS NOW A SSCCE (see comments) but it is a revision of the original code based on suggestions in comments above and below by #camickr.
THIS PROBLEM IS FIXED At present, after dragging column, if filename or path column position has changed, clicking button results in error such as The file: xml\C:\Users\Dov\.AndroidStudio1.2\config\options doesn't exist. This occurred after moving Extension column (which was before Filename column) to after Path column.
import java.awt.BorderLayout;
import java.awt.Desktop;
import java.awt.Font;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.util.Comparator;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import static javax.swing.JTable.AUTO_RESIZE_OFF;
import javax.swing.SwingConstants;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableRowSorter;
//import tablecolumnadjuster.TableColumnAdjuster;
public class RowSorterWithComparator
{
static final int DATE_COLUMN = 0;
static final int SIZE_COLUMN = 1;
static final int EXTENSION_COLUMN= 2;
static final int FILENAME_COLUMN = 3;
static final int PATH_COLUMN = 4;
public static void main(String args[])
{
Comparator compareNumericStringsWithCommas;
TableRowSorter sorter;
JTable tblOutput;
JScrollPane pane;
JFrame frame;
DefaultTableModel dftTableModel;
// TableColumnAdjuster tca ;
DefaultTableCellRenderer rightRenderer;
JButton btnOpen;
Object[] columnNames = new Object[]{"Date", "Size", "Extension", "File name", "Path to file"};
Object rows[][] =
{
{"7/27/2015","9,600","xml","mavenVersion.xml","C:/Users/Dov/.AndroidStudio1.2/config/options/"},
{"7/27/2015","120,000","xml","keymap.xml","C:/Users/Dov/.AndroidStudio1.2/config/options/"},
{"7/27/2015","108","xml","Default.xml","C:/Users/Dov/.AndroidStudio1.2/config/inspection/"},
{"4/27/2015","39,200","txt","key pay.txt","C:/Users/Dov/A/"},
{"6/13/2015","91","java","BuildConfig.java","C:/Users/Dov/androidfp2_examples/eclipse_projects/FlagQuiz/gen/com/deitel/flagquiz/"}
};
compareNumericStringsWithCommas = (Comparator) new Comparator()
{
#Override public int compare(Object oo1, Object oo2)
{
String o1 = oo1.toString().replace(",", "");
String o2 = oo2.toString().replace(",", "");
return Integer.valueOf(o1).compareTo(Integer.valueOf(o2));
}
};
dftTableModel = new DefaultTableModel(0 , columnNames.length);
tblOutput = new JTable(dftTableModel);
dftTableModel.setColumnIdentifiers(columnNames);
rightRenderer = new DefaultTableCellRenderer();
rightRenderer.setHorizontalAlignment(SwingConstants.RIGHT);
tblOutput.getColumnModel().getColumn(1).setCellRenderer(rightRenderer);
sorter = new TableRowSorter<>(dftTableModel);
sorter.setModel(tblOutput.getModel());
sorter.setComparator(1,compareNumericStringsWithCommas);
tblOutput.setRowSorter(sorter);
tblOutput.setAutoResizeMode(AUTO_RESIZE_OFF);
// tca = new tablecolumnadjuster.TableColumnAdjuster(tblOutput);
// tca.setDynamicAdjustment(true);
tblOutput.setFont(new Font("Courier New",Font.PLAIN,12));
pane = new JScrollPane(tblOutput);
for (Object[] row : rows)
dftTableModel.addRow(row);
btnOpen = new JButton("Open selected file");
btnOpen.addMouseListener(new MouseAdapter()
{
#Override
public void mouseClicked(MouseEvent e)
{
int viewFilenameCol = tblOutput.convertColumnIndexToView(FILENAME_COLUMN);
int viewPathCol = tblOutput.convertColumnIndexToView(PATH_COLUMN);
int row = tblOutput.getSelectedRow();
String entry = (String)tblOutput.getValueAt(row, viewPathCol)
+ (String)tblOutput.getValueAt(row, viewFilenameCol);
try
{
Desktop.getDesktop().open(new File((entry.trim())));
}
catch
(
Exception ex) {System.out.println("Can't open file <" + entry + ">");
}
}
});
frame = new JFrame("Sort Table Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(pane, BorderLayout.CENTER);
frame.add(btnOpen, BorderLayout.SOUTH);
frame.setSize(800, 350);
frame.setVisible(true);
}
}
In my project i have a Jtree with custom node (which extends DefaultMutableTreeNode). Each node is associated with a boolean value. When the boolean is False i woul like to strike the texte of my node. So for example :
node 1
node1.1
node1.2
node 2
node2.1
...
I tried to create a new Font but i don't find any properties to strike the text and i only managed to add my custom font to the whole tree and not node by node.
I think that i should use the TreeRenderer but i can't find any method to help me strike the node.
Does someone have an idea on i can do it ?
Check out the example below. For keeping it simple, I am just striking through the selected node. You need to, of course, use your own check on the value.
package snippet;
import java.awt.Component;
import java.awt.Font;
import java.awt.font.TextAttribute;
import java.util.Map;
import javax.swing.JFrame;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultTreeCellRenderer;
public class JTreeTest extends JFrame {
private final class StrikeoutCellRenderer extends DefaultTreeCellRenderer {
private static final long serialVersionUID = 1L;
#Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded,
boolean leaf, int row, boolean hasFocus) {
Component c = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
Font font = c.getFont();
Map attributes = font.getAttributes();
if(sel)
attributes.put(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
else
attributes.remove(TextAttribute.STRIKETHROUGH);
Font newFont = new Font(attributes);
c.setFont(newFont);
return c;
}
}
private static final long serialVersionUID = 1L;
public JTreeTest() {
super(JTreeTest.class.getName());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initComponents();
}
private void initComponents() {
JTree tree = new JTree();
tree.setCellRenderer(new StrikeoutCellRenderer());
add(tree);
pack();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override public void run() {
JTreeTest t = new JTreeTest();
t.setVisible(true);
}
});
}
}
Note that even if the node doesn't need a strike through, you need to reset the attribute, since a single component is used for painting all the nodes.
The simplest way would be to define a renderer (extending DefaultTreeCellRenderer) and call setText() passing html string like this "<html><u>node1.2</u></html>" as value for the strikethrough.
I am using a custom TreeModel for my JTree. I have a root node and only 1 child node that is retrieved by a query from the database. I am able to populate the tree with the desired output.
However, when I click on the child node, it keeps recursively dispalying the same child node and it keeps adding child nodes with the same output. I tried to use static nodes i.e. I created a root node and then added 2 child nodes to it and I observe the same behavior.
My main program
import javax.swing.JFrame;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
public class RunApp {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
ShowFrame f = new ShowFrame();
f.setSize(600, 600);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
});
}
}
My show_frame class
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.HeadlessException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
public class ShowFrame extends JFrame {
private JSplitPane splitPane;
private FormPanel formPanel;
private TreePanel treePanel;
private JTabbedPane tabPane;
private List<Objects> instanceDetails= new ArrayList<Objects>();
public ShowFrame() {
super("new frame");
formPanel = new FormPanel();
instanceDetails.add(new Objects(" "," "," "," "));
treePanel = new TreePanel(instanceDetails);
tabPane = new JTabbedPane();
tabPane.add(treePanel);
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, formPanel,
tabPane);
splitPane.setOneTouchExpandable(true);
setMinimumSize(new Dimension(500, 500));
add(splitPane, BorderLayout.CENTER);
}
}
This is where I create my TreePanel
import java.util.List;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
public class TreePanel extends JPanel {
private int count = 0;
private JTree tree;
private List<Objects> instanceDetails;
private MyTreeModel gm;
private DefaultMutableTreeNode root = new DefaultMutableTreeNode();
private Controller c = new Controller();
public TreePanel(List<Objects> instanceDetails) {
this.instanceDetails = instanceDetails;
tree = new JTree();
if (instanceDetails.get(0).getObjectId() == " ") {
tree.setModel(new MyTreeModel(root));
} else {
tree.setModel(new MyTreeModel(treeNodes(instanceDetails)));
}
gm = new MyTreeModel(root);
gm.fireTreeStructureChanged(root);
tree.getSelectionModel().setSelectionMode(
TreeSelectionModel.SINGLE_TREE_SELECTION);
add(tree);
}
private DefaultMutableTreeNode treeNodes(List<Objects> instanceDetails) {
for (Objects id : instanceDetails) {
count++;
DefaultMutableTreeNode objs = new DefaultMutableTreeNode(count + " : " + id.getType()
+ " : " + id.getObjectId() + " : " + id.getStatus() + " : "
+ id.getCondition());
root.add(objs);
}
return root;
}
}
My tree model
import java.util.Vector;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
public class MyTreeModel implements TreeModel {
public static Vector<TreeModelListener> treeModelListeners =
new Vector<TreeModelListener>();
private static DefaultMutableTreeNode rootPerson;
public MyTreeModel(DefaultMutableTreeNode nodes) {
rootPerson = nodes;
}
//////////////// Fire events //////////////////////////////////////////////
/**
* The only event raised by this model is TreeStructureChanged with the
* root as path, i.e. the whole tree has changed.
*/
protected void fireTreeStructureChanged(DefaultMutableTreeNode rootPerson) {
TreeModelEvent e = new TreeModelEvent(this, new Object[] {rootPerson});
for (TreeModelListener tml : treeModelListeners) {
tml.treeStructureChanged(e);
}
}
//////////////// TreeModel interface implementation ///////////////////////
/**
* Adds a listener for the TreeModelEvent posted after the tree changes.
*/
public void addTreeModelListener(TreeModelListener l) {
treeModelListeners.addElement(l);
}
/**
* Returns the child of parent at index index in the parent's child array.
*/
public Object getChild(Object parent, int index) {
return rootPerson.getChildAt(index);
}
/**
* Returns the number of children of parent.
*/
public int getChildCount(Object parent) {
return 1;
//rootPerson.getLeafCount()
}
/**
* Returns the index of child in parent.
*/
public int getIndexOfChild(Object parent, Object child) {
return rootPerson.getIndex((DefaultMutableTreeNode) child);
}
/**
* Returns the root of the tree.
*/
public Object getRoot() {
return rootPerson;
}
/**
* Returns true if node is a leaf.
*/
public boolean isLeaf(Object node) {
return rootPerson.isLeaf();
}
/**
* Removes a listener previously added with addTreeModelListener().
*/
public void removeTreeModelListener(TreeModelListener l) {
//removeTreeModelListener(l);
}
/**
* Messaged when the user has altered the value for the item
* identified by path to newValue. Not used by this model.
*/
public void valueForPathChanged(TreePath path, Object newValue) {
}
}
Your implementation of TreeModel is clumsy and is the cause of your issues:
public static Vector<TreeModelListener> treeModelListeners =
new Vector<TreeModelListener>();
private static DefaultMutableTreeNode rootPerson;
--> Bad, Bad, Bad, ... real bad. There is absolutely no need to make these statements static and this will cause severe issues if you happen to create 2 different instances
/**
* Returns the child of parent at index index in the parent's child array.
*/
public Object getChild(Object parent, int index) {
return rootPerson.getChildAt(index);
}
Here, no matter which parent is provided, you return always the same child (hence this is why you see the same child over and over). The code should be return (parent==rootPerson?rootPerson.getChildAt(index):null);
/**
* Returns the number of children of parent.
*/
public int getChildCount(Object parent) {
return 1;
//rootPerson.getLeafCount()
}
Same as previous comment, you don't look what is the parent. Code should be return (parent==rootPerson?1:0);
/**
* Returns the index of child in parent.
*/
public int getIndexOfChild(Object parent, Object child) {
return rootPerson.getIndex((DefaultMutableTreeNode) child);
}
Same as previous comment, you don't look what is the parent. Code should be return (parent==rootPerson?rootPerson.getIndex((DefaultMutableTreeNode) child):-1);
/**
* Returns true if node is a leaf.
*/
public boolean isLeaf(Object node) {
return rootPerson.isLeaf();
}
Again, same mistake, you don't care about node
/**
* Removes a listener previously added with addTreeModelListener().
*/
public void removeTreeModelListener(TreeModelListener l) {
//removeTreeModelListener(l);
}
Why don't you implement properly removeTreeModelListener? (and as suggested by #trashgod, you can always use the default EventListenerList which does most of the work for you)
Conclusion: your implementation of TreeModel is full of bugs and this is why you get the problem you describe. Now, since you are using DefaultMutableTreeNode, I can only encourage you to also use DefaultTreeModel which will handle everything for you and avoid you to have to re-implement this, with all the "risks" it implies.
I got a Java Swing GUI and got a problem with a JTable in a JScrollPane. For some reason, when the rows of the table model are increased during the program execution, the JScrollPane isn't updated - that is, if the rows are increased so that the height of the table is over the height of the scroll view, the scroll panes aren't updated as supposed. (The new rows are shown at the screen as expected). If the window is resized, scrolling is updated as expected.
The vertical scrolling policy is VERTICAL_SCROLLBAR_AS_NEEDED, table models fireTableDataChanged is called..
Unfortunately the code's a bit complex so I can't provide an code sample causing the problem. But thought to ask if somebody got some ideas straight..
EDIT: Still a bit more confusing : horizontal scrolling policy is HORIZONTAL_SCROLLBAR_AS_NEEDED, and if the table width if over the view width (that is, the horizontal scrollbar is used), this problem doesn't occur...
EDIT2: The problem isn't that the table should be scrolled but that the scrollbar's aren't activated as they should.
You might need to post some of your code. I've just knocked up the following test and it works as advertised, i.e. vertical scrollbars are activated when the number of rows exceeds the viewport height:
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ScrollPaneConstants;
import javax.swing.table.AbstractTableModel;
public class JTableTest {
public static void main(String[] args) {
final MyTableModel tm = new MyTableModel();
tm.addData(new Data("R1C1", "R1C2"));
JTable table = new JTable(tm);
JScrollPane scrollPane = new JScrollPane(table);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
JFrame frame = new JFrame();
frame.setLayout(new GridLayout());
frame.add(scrollPane);
frame.pack();
frame.setSize(400, 150);
frame.setVisible(true);
Thread t = new Thread(new Runnable() {
private int count = 2;
public void run() {
for ( ; ; ) {
tm.addData(new Data("R" + count + "C1", "R" + count + "C2"));
count++;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
t.start();
}
private static class MyTableModel extends AbstractTableModel {
private List<Data> dataList = new ArrayList<Data>();
public int getColumnCount() {
return 2;
}
public void addData(Data data) {
dataList.add(data);
fireTableRowsInserted(dataList.size()-1, dataList.size()-1);
}
public int getRowCount() {
return dataList.size();
}
public Object getValueAt(int rowIndex, int columnIndex) {
Data data = dataList.get(rowIndex);
return columnIndex == 0 ? data.data1 : data.data2;
}
}
private static class Data {
public String data1;
public String data2;
public Data(String data1, String data2) {
this.data1 = data1;
this.data2 = data2;
}
}
}
Hmm.. after returning to the issue I found out that our row header customizing in JScrollPane was causing the problem. (Some preferredsizes were set with not-so-sensible values etc)..