JTable won't scroll in JScrollPane - java

I've been having trouble trying to get the table to scroll; it just won't. I've looked at other stack answers and tried them, but they aren't working; and I'm not sure if I have something conflicting with those solutions.
tableModel = new TableModel(); //Custom Table Model
table = new JTable();
table.setBorder(null);
table.setFillsViewportHeight(true);
table.setModel(tableModel);
JScrollPane tblScrollPane = new JScrollPane(table);
tblScrollPane.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));
tblScrollPane.setBounds(245, 17, 560, 425);
frmServerAdministration.getContentPane().add(tblScrollPane);
EDIT: More info
So a window opens, which is the server program. Client programs connect to the server and when they do, a method is triggered in my table model class which adds a new row into the table. (I can see the row) Then at the end of that method it calls another but nothing changes in the ScrollPane. Do I need to do some kind of repainting? -
Server.updateTableScroll();
public static void updateTableScroll() {
System.out.println("Here"); //NOTE: I do see this printed out
int last = table.getModel().getRowCount() - 1;
Rectangle r = table.getCellRect(last, 0, true);
table.scrollRectToVisible(r);
}
EDIT 2: Thoughts
So in Eclipse I use Window Builder to make the GUI, and the following for loop will display the table with the scrollbar! But when I run the same addClient() method at another point, then the scroll bar won't appear.
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Server window = new Server();
window.frmServerAdministration.setVisible(true);
for(int i = 0; i < 20; i++){
tableModel.addClient(i, String.valueOf(i));
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}

Instead of setBounds(), override getPreferredScrollableViewportSize(), as suggested here, and pack() the enclosing top-level container. Also, the API authors "recommend that you put the component in a JPanel and set the border on the JPanel."
Addendum: As a JTable listens to its TableModel, verify that the correct event is fired from the model. If you extend AbstractTableModel, one of the fireTableXxx() methods will be appropriate.

All I needed to do was call this after I added data (I'm very new to table models :P )
this.fireTableStructureChanged();

Related

JTable and JButton click not working

I have a JPanel holding a JButton and JScrollPane (in turn holding a JTable) and am currently running into two issues which I believe are related:
The JButton listener's actionPerformed() method is not invoked upon click. The only way in which I can get it to be invoked is by calling doClick() on the JButton. The JButton color changes upon hover but no click animation is shown when the mouse is pressed.
Secondly, if a cell is clicked within the JTable, the cell located 2 rows down in the same column registers the click instead. This offset does not occur when clicking in the column headers (i.e. to adjust cell widths), only when within the cell area.
Left-hand panel. Click position circled
public class InventoryPanel extends JPanel {
// Parent Business object reference for communication and JFrame
private Business parent;
private AddItemPanel addItemPanel;
// Inventory table items
private DefaultTableModel inventoryModel;
private JTable inventoryTable;
private JScrollPane inventoryScrollPane;
private JLabel updateLbl;
private JButton addItemBtn;
// Columns for inventory table
private static final String[] INVENTORY_COLUMNS = {"Item","Stock","Restocking Level","Edit"};
public InventoryPanel(Business parent) {
this.parent = parent;
initGUI();
new Thread(new Runnable() {
#Override
public void run() {
while (true) {
//doStuff
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace(new PrintStream(System.out));
}
}
}
}).start();
}
// INITIALISES GUI
public void initGUI() {
this.setLayout(new BoxLayout(this,BoxLayout.PAGE_AXIS));
this.setBorder(BorderFactory.createLineBorder(Color.BLACK));
JLabel titleLabel = new JLabel("<html><B>Inventory</B></html>");
this.add(titleLabel);
// Create empty inventory table
inventoryModel = new DefaultTableModel(new Object[3][4],INVENTORY_COLUMNS);
inventoryTable = new JTable(inventoryModel);
inventoryScrollPane = new JScrollPane(inventoryTable);
// Create button to allow items to be added
addItemBtn = new JButton("Add item");
addItemBtn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("ADD ITEM PRESSED");
}
});
updateLbl = new JLabel("Loading inventory...");
this.add(addItemBtn);
this.add(inventoryScrollPane);
this.add(updateLbl);
}
I've tried removing the table from the panel to see if that solves the JButton issue and visa-versa, but no luck. I've also tried changing the project JDK but no luck there either.
There are other JPanels adjacent to the troublesome one in a JFrame which work perfectly fine. Any ideas?
Edit: I can create a working instance of the InventoryPanel alone in a frame in another project, as mentioned in the comments. However the exact same code (no calls being made to other objects/methods) in the current project now produces ClassCastExceptions. After some googling this seems to be due to non-EDT threads updating the GUI.
However there is no use of the Business class, and all GUI operations are performed using the SwingUtilities.invokeLater() method like so:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("test");
frame.add(new InventoryPanel());
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
});
}
Note: the no-argument constructor InventoryPanel() just calls initGUI().
Thanks for the help so far...still very confused by this.

Table stops responding to clicks after refresh

I have the following code:
retrieveMessages.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent arg0) {
model.resetList();
top.remove(scrollPane);
folder = new getMessages().getMessagesInstance();
folder.initGetMessages();
model = new MessageTableModel().getModelInstance();
table = new JTable(model);
table.setTableHeader(null);
System.out.println("Got to here");
scrollPane = new JScrollPane(table);
scrollPane.setPreferredSize(new Dimension(150,150));
top.add(scrollPane, BorderLayout.CENTER);
table.setVisible(true);
dialog.setVisible(true);
}
});
folder = new getMessages().getMessagesInstance();
folder.initGetMessages();
model = new MessageTableModel().getModelInstance();
table = new JTable(model);
table.setTableHeader(null);
scrollPane = new JScrollPane(table);
scrollPane.setPreferredSize(new Dimension(150,150));
top.add(scrollPane, BorderLayout.CENTER);
table.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
public void valueChanged(ListSelectionEvent event) {
MessageTableModel model = new MessageTableModel().getModelInstance();
try {
String message = model.getMessage(table.getSelectedRow()).getMessage().toString();
messageArea.setText(Jsoup.parse(message).text());
System.out.println("Selected!");
} catch (Exception e) {
e.printStackTrace();
}
}
});
There's some code in between the first function and the other two functions, but it doesn't edit any of the data. Here's the problem. When I load the GUI, the table responds fine, just as it should (as in the last function). However, when I activate the retrieveMessages function, it refreshes the table, but clicking on the rows does nothing now. I've tried hard coding print-outs to see if it's even getting there,and it isn't, or it just isn't working.
And yes, I tried fireDataChanged. That did literally nothing besides add an entire block of emails, that were identical to the original emails, on to the bottom of the original table. So I had a double the size table with copies of all emails twice. fireDataRowsDeleted did nothing either.
Help?
Try creating a global public table and add that to your:
table.setModel(your_new_model_goes_here);
//update the global table with a new model`enter code here`
You are creating different table objects currently.

JTree async node creation is not consistent with JGraph library

I am creating a Jtree with a rootNode and than create another thread that update the root node asynchronously.
It works fantastic if i run this Jtree independently in some JPanel, it was even working at some place in the project, but i was asked to have this Jtree in some new swing Component.
In new Swing Panel, it doesnot populate fully, it only populate the nodes that were inserted (at start for few milliseconds) before the Jtree was rendered on the Screen. Once the Jtree is rendered it doesnt get updated.
Now the interesting part i also made a mouse listener on the node so that i can create a new node by right click create node function, and with that new node is created and is added on the Jtree root node.
Important thing to add i was using newThread(){void run}).start() method to create a thread to add node on the Jtree, becuase i never felt the need of SwingUtilities.invokeLater method before. but now if i used SwingUtilities.invokeLater method than the main window also doesnot open it just halts during the startup, i just checked that SwingUtilities.invokeLater also works fine with the old component and ofcourse works fine independently.
And i do call model.nodeStructureChanged(changedNode); after adding the node thats why it was working fine before.
Kindly asist,
code is difficult to extract and Jtree code was working fine before, may be some component block the containing widgets to refresh itself async?
EDIT
Update to include some code, i am using Temp class as provided by Nick:-
public BasicGraphEditor(String appTitle, mxGraphComponent component)
{
// Stores and updates the frame title
this.appTitle = appTitle;
// Stores a reference to the graph and creates the command history
graphComponent = component;
final mxGraph graph = graphComponent.getGraph();
undoManager = createUndoManager();
// Do not change the scale and translation after files have been loaded
graph.setResetViewOnRootChange(false);
// Updates the modified flag if the graph model changes
graph.getModel().addListener(mxEvent.CHANGE, changeTracker);
// Adds the command history to the model and view
graph.getModel().addListener(mxEvent.UNDO, undoHandler);
graph.getView().addListener(mxEvent.UNDO, undoHandler);
// Keeps the selection in sync with the command history
mxIEventListener undoHandler = new mxIEventListener()
{
#Override
public void invoke(Object source, mxEventObject evt)
{
List<mxUndoableChange> changes = ((mxUndoableEdit) evt
.getProperty("edit")).getChanges();
graph.setSelectionCells(graph
.getSelectionCellsForChanges(changes));
}
};
undoManager.addListener(mxEvent.UNDO, undoHandler);
undoManager.addListener(mxEvent.REDO, undoHandler);
// Creates the graph outline component
graphOutline = new mxGraphOutline(graphComponent);
// Creates the library pane that contains the tabs with the palettes
libraryPane = new JTabbedPane();
/////////////////////////////////////////////////
// Only change i have done here: start
////////////////////////////////////////////////
Temp tempExplorer = new Temp();
libraryPane.add("new Explorere", tempExplorer);
/////////////////////////////////////////////////
// Only change i have done here: End
////////////////////////////////////////////////
// Creates the inner split pane that contains the library with the
// palettes and the graph outline on the left side of the window
JSplitPane inner = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
libraryPane, graphOutline);
inner.setDividerLocation(320);
inner.setResizeWeight(1);
inner.setDividerSize(6);
inner.setBorder(null);
// Creates the outer split pane that contains the inner split pane and
// the graph component on the right side of the window
JSplitPane outer = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, inner,
graphComponent);
outer.setOneTouchExpandable(true);
outer.setDividerLocation(200);
outer.setDividerSize(6);
outer.setBorder(null);
// Creates the status bar
statusBar = createStatusBar();
// Display some useful information about repaint events
installRepaintListener();
// Puts everything together
setLayout(new BorderLayout());
add(outer, BorderLayout.CENTER);
add(statusBar, BorderLayout.SOUTH);
installToolBar();
// Installs rubberband selection and handling for some special
// keystrokes such as F2, Control-C, -V, X, A etc.
installHandlers();
installListeners();
updateTitle();
}
The above class is from Jgraph library as https://github.com/jgraph/jgraphx
And i am just adding the jtree component like as above, no other changes.
please help.
Swing isn't thread safe unless explicitly stated. In the JavaDocs for JTree, it explicitly says this isn't thread safe. If you update it in a thread outside the EDT, there's no guarentee anything will work. So if you want to update a JTree from a different Thread, you need to use SwingUtilities.invokeLater(Runnable run); in order to put the request on the EDT.
I'd recommend having a data structure to store the info of the JTree, and only using the JTree for User Interaction (not data storage).
EDIT
Here's an example of using SwingUtilities.invokeLater() to update a JTree while in the component model. Without you posting any code, this is the best I have to work with. Please try to use this to recreate your problem (add pieces of your code to this example until you have narrowed down what the problem is).
import java.awt.*;
import javax.swing.*;
import javax.swing.tree.*;
public class Temp extends JPanel{
JTree tree = new JTree();
public Temp(){
JScrollPane jsp = new JScrollPane(tree);
// Creates the library pane that contains the tabs with the palettes
JTabbedPane libraryPane = new JTabbedPane();
libraryPane.add("new Explorere", jsp);
// Creates the inner split pane that contains the library with the
// palettes and the graph outline on the left side of the window
JSplitPane inner = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
libraryPane, new JPanel());
inner.setDividerLocation(320);
inner.setResizeWeight(1);
inner.setDividerSize(6);
inner.setBorder(null);
// Creates the outer split pane that contains the inner split pane and
// the graph component on the right side of the window
JSplitPane outer = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, inner,
new JPanel());
outer.setOneTouchExpandable(true);
outer.setDividerLocation(200);
outer.setDividerSize(6);
outer.setBorder(null);
// Puts everything together
setLayout(new BorderLayout());
add(outer, BorderLayout.CENTER);
}
public static void main(String[] args) {
final Temp temp = new Temp();
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(temp);
frame.pack();
frame.setVisible(true);
}});
Thread updater = new Thread(temp.new CustomThread());
updater.start();
}
public class CustomThread implements Runnable{
#Override
public void run() {
for(int i = 0; i < 1000; i++){
updateTree("New Item "+ i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void updateTree(final String nodeToAdd){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
DefaultMutableTreeNode child = new DefaultMutableTreeNode(nodeToAdd);
model.insertNodeInto(child, root,root.getChildCount());
tree.scrollPathToVisible(new TreePath(child.getPath()));
}});
}
}
}

Creating a custom TableModel with multiple column headers and row headers

I'm attempting to create a JTable that looks like the mockup below:
The green corner is basically buffer-space for the red column and row headers. The cells don't need to be rendered in the colours pictured; however they need to be distinguishable from the rest of the 'white' cells in the table.
This table also is not editable or selectable; it's merely viewed by a user whilst it is updated.
I know this can be achieved using a DefaultTableModel with custom renders for rows 1,2 && cols 1,2 and adding +2 when setting and getting table values (accounting for the rows and columns that are being used as headers).
My questions are as follows:
Is there a cleaner way of doing this without polluting my table model with these static values used in headers?
I've read about extending table models but I'm not sure which class should I extend (DefaultTableModel, AbstractTableModel) and what methods I should override.
Input is limited to 20x20 so including the headers that's 22x22.
Also consider a JScrollPane containing a JPanel having GridLayout and containing 22x22 instances JLabel, or a suitable subclass. This scales easily to several thousand cells.
Addendum: If the need arises, CellRendererPane makes a good flyweight renderer, as suggested here.
If you go with JTable for rendering scalability,
This is no abuse; it is exactly how TableModel is intended to be used. TableModel models a rectangular matrix of whatever you decide. JTable is just an (efficiently rendered) view of that model.
I prefer AbstractTableModel, shown here, because Vector is rarely the desired data structure. Use whatever container makes your indexing most convenient. DefaultTableModel is handy and serves as a guide to extending AbstractTableModel. In particular, you'll need a setValueAt().
#Override
public void setValueAt(Object aValue, int row, int col) {
... // update your data structure
this.fireTableCellUpdated(row, col); // notify the view
}
longer comment, everything depends
1) if is possible for Columns
resize
reordering
2) if is possible for Columns
filtering
sorting
a. then you have look at two JTables, first JTable only with TableHeader, simple with removed rows and second full sized JTable with TableHeader and Columns and rows,
b. for interactions betweens two JTableHeader is there
TableColumnModelListener#columnMarginChanged(ChangeEvent e) and columnMoved(TableColumnModelEvent e)
c. everyting put to one JPanel inside JScrollPane
d. if you'll change numbers of rows or colums (or filtering / sorting) then you have to notified JPanel for rezize JTable#getPreferredScrollableViewportSize() + Dimension for ontop JTable only with TableHeader
very similair way as there (is everything that you needed)
(endless kudos for Rob)
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
public class TableFilterRow extends JFrame implements TableColumnModelListener {
private static final long serialVersionUID = 1L;
private JTable table;
private JPanel filterRow;
public TableFilterRow() {
table = new JTable(3, 5);
table.setPreferredScrollableViewportSize(table.getPreferredSize());
JScrollPane scrollPane = new JScrollPane(table);
getContentPane().add(scrollPane);
table.getColumnModel().addColumnModelListener(this);
// Panel for text fields
filterRow = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
for (int i = 0; i < table.getColumnCount(); i++) {
filterRow.add(new JTextField(" Sum at - " + i));
}
columnMarginChanged(new ChangeEvent(table.getColumnModel()));
getContentPane().add(filterRow, BorderLayout.SOUTH);
}
// Implement TableColumnModelListener methods
// (Note: instead of implementing a listener you should be able to
// override the columnMarginChanged and columMoved methods of JTable)
#Override
public void columnMarginChanged(ChangeEvent e) {
TableColumnModel tcm = table.getColumnModel();
int columns = tcm.getColumnCount();
for (int i = 0; i < columns; i++) {
JTextField textField = (JTextField) filterRow.getComponent(i);
Dimension d = textField.getPreferredSize();
d.width = tcm.getColumn(i).getWidth();
textField.setPreferredSize(d);
}
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
filterRow.revalidate();
}
});
}
#Override
public void columnMoved(TableColumnModelEvent e) {
Component moved = filterRow.getComponent(e.getFromIndex());
filterRow.remove(e.getFromIndex());
filterRow.add(moved, e.getToIndex());
filterRow.validate();
}
#Override
public void columnAdded(TableColumnModelEvent e) {
}
#Override
public void columnRemoved(TableColumnModelEvent e) {
}
#Override
public void columnSelectionChanged(ListSelectionEvent e) {
}
public static void main(String[] args) {
JFrame frame = new TableFilterRow();
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
3) otherwise look How to Use Raised Borders in the prepareRederer
4) this question has nothing to do with type of TableModel

How do I add mouseClicked event to a swing table?

I am a new, terribly green user of Swing. I managed to create a table class using examples from java.sun tutorials, and I managed to load data dynamically into it. I want to be able to react to a click on a row by displaying a dialog box. How do I add the event Handler that will identify the selected row number?
The main function code:
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
MainWindow window = new MainWindow();
window.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Create and set up the content pane.
createAndShowGUI();
//...
}
}
}
}
and
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("Data Table");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Create and set up data of the content pane.
TableClass mainTable = new TableClass(fh.getColNames(), fh.getTableContent());
mainTable.setOpaque(true);
frame.setContentPane(mainTable);
//Display the window.
frame.pack();
frame.setVisible(true);
}
Thank you
table.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 1) {
int selectedRowIndex = table.getSelectedRow();
//show your dialog with the selected row's contents
}
}
});
A couple of points.
The answer from Bozhidar Batsov is correct. However, you may not necessarily have a reference to the table (if for example your mouse listener is in another class or something). Point is, the table can be found from the MouseEvent's getSource() method. You can safely cast it to a JTable.
Also, table.getSelectedRow() may return a -1 if no row has actually been selected on the table yet. That would happen if the user, for example, clicks in the "whitespace" of the table (the area outside of the grid, but still in the text area). Just be sure to test for a -1 in your code.

Categories