Newbie seeking help please :-)
I am working on a little project to get familiar with Java desktop development and Database connectivity.
Attached code gives me an empty TableModel after instantiating therefore no data displayed in the JFrame.
Test class is instantiated from the menue of the main window with Test.showFrame();.
package ...
import ...
public class Test extends JPanel {
public Test() {
initializePanel();
}
private void initializePanel() {
// Creates an instance of TableModel
CategoryTableModel tableModel = new CategoryTableModel();
System.out.println(tableModel.getRowCount());
// Creates an instance of JTable with a TableModel
// as the constructor parameters.
JTable table = new JTable(tableModel);
table.setFillsViewportHeight(true);
JScrollPane scrollPane = new JScrollPane(table);
this.setPreferredSize(new Dimension(500, 200));
this.setLayout(new BorderLayout());
this.add(scrollPane, BorderLayout.CENTER);
}
public static void showFrame() {
JPanel panel = new Test();
panel.setOpaque(true);
JFrame frame = new JFrame("test");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setContentPane(panel);
frame.pack();
frame.setVisible(true);
}
class CategoryTableModel extends AbstractTableModel {
private List<Category> all = null;
private Iterator<Category> iterator = null;
private int tableRowCount;
private TableModel tableModel;
public CategoryTableModel(){
Vector tableData = new Vector();
// TableModel's column names
Vector<String> tableHeaders = new Vector<String>();
tableHeaders.add("Category");
// Database call
all = new ReadCategory().allCategories();
// TableModel's data
for(Object o : all) {
Vector<Object> row = new Vector<Object>();
all.forEach((n) -> row.add(new Category().getName()));
tableData.add(row);
System.out.println("row added");
}
tableRowCount = tableData.size();
tableModel = new DefaultTableModel(tableData, tableHeaders);
System.out.println(tableModel.getRowCount());
}
#Override
public int getRowCount() {
return 0;
}
#Override
public int getColumnCount() {
return 0;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return null;
}
}
}
Database call is fetching data via Hibernate and stores data in a .
Thanks for help.
In its most basic form a table model for a JTable defines the columns, the mapping of object to column and holds the data for the JTable to call upon. If we take your current table model and cut it down to fit this basic requirement we get the following.
import java.util.ArrayList;
import java.util.List;
import javax.swing.table.AbstractTableModel;
public class CategoryTableModel extends AbstractTableModel {
private final List<Category> tableData = new ArrayList<>();
public void add(Category cat) {
tableData.add(cat);
fireTableDataChanged();
}
#Override
public String getColumnName(int column) {
String result = "";
if (column == 0) {
result = "Category Name";
}
return result;
}
#Override
public int getRowCount() {
return tableData.size();
}
#Override
public int getColumnCount() {
return 1;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (columnIndex == 0) {
return tableData.get(rowIndex).getName();
}
return null;
}
}
Notice that we do not define any data in the model itself. All we define is some storage for the data and the column name of the single column that we require.
I have added an add() method that allows you to add data to the model (you may wish to define remove etc. yourself). When you add or remove data from the model you must always let the JTable know that the data has changed by calling fireTableDataChanged() so that the table can redraw itself.
To use this table model you will need to do
CategoryTableModel model = new CategoryTableModel();
model.add(new Category());
JTable table = new JTable(model);
You can replace the model.add() with a loop that iterates over your data and adds it to the model.
Related
I am trying to refresh my table panel when I click the LOAD button. The method replaceText() will be called the LOAD button in GUI is clicked. A new columnNames list and display content will be sent to update my tableModel which extends AbstractTableModel. Then table.updateUI() is called to refresh the table.
public class Table {
private JPanel panel;
private TableModel model;
private JTable table;
private JScrollPane scrollPane;
public Table(){
this.panel = new JPanel();
this.model = new TableModel();
this.table = new JTable(model);
this.scrollPane = new JScrollPane(table);
}
public void replaceText(ArrayList<String> names, String[][] content){
model.update(names, content);
table.updateUI();
}
public JPanel build(){
panel.setSize(1000, 300);
panel.setLayout(new BorderLayout());
panel.add(scrollPane, BorderLayout.CENTER);
panel.setVisible(true);
return panel;
}
}
And here is the code in my tableModel:
public class TableModel extends AbstractTableModel {
String[] colNames = new String[] {};
String[][] content = new String[][] {};
#Override
public int getRowCount() {
return content.length;
}
#Override
public int getColumnCount() {
return colNames.length;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return content[rowIndex][columnIndex];
}
public String getColumnName(int columIndex){
return colNames[columIndex];
}
public void update(ArrayList<String> names, String[][] content){
this.colNames = names.toArray(new String[0]);
this.content = content;
}
}
public class TableModel extends AbstractTableModel {
There is an interface called TableModel. Don't name the class the same as the interface. It is confusing.
table.updateUI();
You should NOT invoke updateUI(). That method is only used internally by Swing when you change the LAF. Changing the data is not changing the LAF.
It is the responsibility of the "model" to update the "view" when the data of the model is changed.
public void update(ArrayList<String> names, String[][] content){
this.colNames = names.toArray(new String[0]);
this.content = content;
}
Your update(…) method does NOT notify the view that the data has changed. This would be done by invoking the:
fireTableStructureChanged(…);
method of the AbstractTableModel in your update(…) method.
However, I see no reason to create a custom TableModel. I would suggest you just use the DefaultTableModel. It has a method setDataVector(…) that allows you to reset the data in the model.
I have many different array lists. I want each index to be a new row in the JTable but I'm not sure how to do that. I made a for loop but it is not working.
Is there even a way to populate a JTable with an array list and not an array?
public TableCreator() {
super(new GridLayout(1,0));
String[] columnNames = {"Item Type",
"Description",
"Size",
"Price"};
// for(int i=0; i<ShoppingFunctions.cartType.size(); i++){
for(int i=0; i<GUI.size.size(); i++){//FIX!!!!
item = ShoppingFunctions.cartType.get(i)+"\n";
described = GUI.describe[GUI.imageNum];
sizes = GUI.size.get(i);
price = ShoppingFunctions.cartPrice.get(i)+"\n";
}//end of for
Object[][] data = {{item, described, sizes, price}};
final JTable table = new JTable(data, columnNames);
table.setPreferredScrollableViewportSize(new Dimension(500, 70));
table.setFillsViewportHeight(true);
if (DEBUG){
table.addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent e){
printDebugData(table);
}//end of method
});//end of listener
}//end of if
JScrollPane scrollPane = new JScrollPane(table);
add(scrollPane);
}//end of method
The simple answer is to create your own, based on something like AbstractTableModel
For example...
public abstract class AbstractGenericTableModel<R> extends AbstractTableModel {
protected ArrayList<R> rows;
protected List columnIdentifiers;
public AbstractGenericTableModel() {
this(0, 0);
}
public AbstractGenericTableModel(int rowCount, int columnCount) {
this(new ArrayList(columnCount), rowCount);
}
public AbstractGenericTableModel(List columnNames, int rowCount) {
setData(new ArrayList<>(rowCount), columnNames);
}
public AbstractGenericTableModel(List<R> data, List columnNames) {
setData(data, columnNames);
}
public List<R> getRowData() {
return rows;
}
private List nonNullVector(List v) {
return (v != null) ? v : new ArrayList();
}
public void setData(List<R> data, List columnIdentifiers) {
this.rows = new ArrayList<>(nonNullVector(data));
this.columnIdentifiers = nonNullVector(columnIdentifiers);
fireTableStructureChanged();
}
public void addRow(R rowData) {
insertRow(getRowCount(), rowData);
}
public void insertRow(int row, R rowData) {
rows.add(row, rowData);
fireTableRowsInserted(row, row);
}
/**
* Removes the row at <code>row</code> from the model. Notification of the row being removed will be sent to all the listeners.
*
* #param row the row index of the row to be removed
* #exception ArrayIndexOutOfBoundsException if the row was invalid
*/
public void removeRow(int row) {
rows.remove(row);
fireTableRowsDeleted(row, row);
}
public void setColumnIdentifiers(List columnIdentifiers) {
setData(rows, columnIdentifiers);
}
#Override
public int getRowCount() {
return rows.size();
}
#Override
public int getColumnCount() {
return columnIdentifiers.size();
}
#Override
public String getColumnName(int column) {
Object id = null;
// This test is to cover the case when
// getColumnCount has been subclassed by mistake ...
if (column < columnIdentifiers.size() && (column >= 0)) {
id = columnIdentifiers.get(column);
}
return (id == null) ? super.getColumnName(column)
: id.toString();
}
}
public class ArrayListTableModel extends AbstractGenericTableModel<ArrayList> {
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
List<ArrayList> rows = getRowData();
return rows.get(rowIndex).get(columnIndex);
}
}
This creates two classes, an "abstract generics" based model, which allows you to specify the physical data which backs a row and a ArrayListTableModel which allows you to use a ArrayList for the individual data.
What this assumes though, is each row has the same number of elements, but it makes no checks
I suggest you have a closer look at How to Use Tables for more details
You can use the List Table Model. It will support rows of data stored in an ArrayList, Vector or any other class that implements the List interface.
The ListTableModel is also based on a generic TableModel that allows you to add Objects to a row in the model. There are many row based methods that allow easy usage of the model
The ListTableModel provides methods that allow you to easily customize the model:
setColumnClass – specify the class of individual columns so the proper renderer/editor can be used by the table.
setModelEditable – specify editable property for the entire model.
setColumnEditable – specify editable property at a column level. This property will have preference over the model editable property.
The other option is to iterate through the ArrayList and copy each row of data to a Vector and then add the Vector to the DefaultTableModel using the addRow(...) method.
I have two JTables one in main viewport and one in footer viewport, using JideScrollPane.
the problem is when the main JTable's view is customized, the footer JTable remains the
same, is there any way to synchronize their view?
thanks.
EDIT:
Here's a demo that will synch up the resizing of two tables that have similar columns. The idea is:
Create a custom TableColumnModelListener for each table's column model.
Upon resize, sync up the column widths. You'll have to disable the other listener temporarily, while this is happening.
For moving of columns, implement that logic in columnMoved(...) [left as an exercise]
This shows two-way synching:
import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
public class JTableResizeColumnsDemo implements Runnable
{
JTable table1, table2;
TableColumnModelListener columnListener1, columnListener2;
Map<JTable, TableColumnModelListener> map;
public static void main(String[] args)
{
SwingUtilities.invokeLater(new JTableResizeColumnsDemo());
}
public void run()
{
Vector<String> names = new Vector<String>();
names.add("One");
names.add("Two");
names.add("Three");
table1 = new JTable(null, names);
table2 = new JTable(null, names);
columnListener1 = new ColumnChangeListener(table1, table2);
columnListener2 = new ColumnChangeListener(table2, table1);
table1.getColumnModel().addColumnModelListener(columnListener1);
table2.getColumnModel().addColumnModelListener(columnListener2);
map = new HashMap<JTable, TableColumnModelListener>();
map.put(table1, columnListener1);
map.put(table2, columnListener2);
JPanel p = new JPanel(new GridLayout(2,1));
p.add(new JScrollPane(table1));
p.add(new JScrollPane(table2));
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(p);
frame.setSize(300, 200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
class ColumnChangeListener implements TableColumnModelListener
{
JTable sourceTable;
JTable targetTable;
public ColumnChangeListener(JTable source, JTable target)
{
this.sourceTable = source;
this.targetTable = target;
}
public void columnAdded(TableColumnModelEvent e) {}
public void columnSelectionChanged(ListSelectionEvent e) {}
public void columnRemoved(TableColumnModelEvent e) {}
public void columnMoved(TableColumnModelEvent e) {}
public void columnMarginChanged(ChangeEvent e)
{
TableColumnModel sourceModel = sourceTable.getColumnModel();
TableColumnModel targetModel = targetTable.getColumnModel();
TableColumnModelListener listener = map.get(targetTable);
targetModel.removeColumnModelListener(listener);
for (int i = 0; i < sourceModel.getColumnCount(); i++)
{
targetModel.getColumn(i).setPreferredWidth(sourceModel.getColumn(i).getWidth());
}
targetModel.addColumnModelListener(listener);
}
}
}
You can apply an Observer pattern: the first JTable observes the second and vice versa. Then you add listners to both tables so that, when one is "customized", the other is notified. Basically, "being notified" consists in a method invocation that causes the update of the JTable.
In order to do that, you have two options:
You define a class Observer with a "register" method and a
"notify" method. When creating a JTable, you register it with the
Observer. Then, the listener you create and associate to each JTable
invoke the "notify" method of the observer, which informs all other
registered JTables of the change
You define a sort of "callback method" notify in the class that contains and declares the JTable. This "notify" method is invoked within the listner and updates the correct JTable. You can also create two methods: one for updating one JTable and one for the other JTable
Usually this is done by using the same model for different ui components. Sadly the JTable contains a bug that will cause problems when sharing the TableColumnModel.
But you can work around it using this JTable
class ShareableColumnModelTable extends JTable {
/**
* Fixes http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4816146 and
* more...
*
*/
#Override
public void columnMarginChanged(ChangeEvent e) {
if (isEditing()) {
removeEditor();
}
TableColumn resizingColumn = null;
if (tableHeader != null) {
resizingColumn = tableHeader.getResizingColumn();
}
if (resizingColumn != null) {
if (autoResizeMode == AUTO_RESIZE_OFF) {
resizingColumn.setPreferredWidth(resizingColumn.getWidth());
} else { // this else block is missing in jdk1.4 as compared to
// 1.3
TableColumnModel columnModel = getColumnModel();
/**
* Temporarily disconnects this column listener to prevent
* stackoverflows if the column model is shared between
* multiple JTables.
*/
columnModel.removeColumnModelListener(this);
try {
doLayout();
} finally {
columnModel.addColumnModelListener(this);
}
repaint();
return;
}
}
resizeAndRepaint();
}
}
With the ShareableColumnModelTableshowed above you can share one column model bettween multiple tables.
public static void main(String[] args) {
JFrame frame = new JFrame("Column Sync");
Container contentPane = frame.getContentPane();
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
splitPane.setResizeWeight(0.5d);
contentPane.add(splitPane);
JTable table1 = new ShareableColumnModelTable();
JTable table2 = new ShareableColumnModelTable();
TableColumnModel tableColumnModel = createTableColumnModel();
table1.setModel(createTableModel1());
table2.setModel(createTableModel2());
table1.setColumnModel(tableColumnModel);
table2.setColumnModel(tableColumnModel);
splitPane.setLeftComponent(new JScrollPane(table1));
splitPane.setRightComponent(new JScrollPane(table2));
showFrame(frame);
}
private static TableColumnModel createTableColumnModel() {
TableColumnModel tableColumnModel = new DefaultTableColumnModel();
TableColumn column1 = new TableColumn(0);
column1.setHeaderValue("1. column");
tableColumnModel.addColumn(column1);
TableColumn column2 = new TableColumn(1);
column2.setHeaderValue("2. column");
tableColumnModel.addColumn(column2);
return tableColumnModel;
}
private static TableModel createTableModel1() {
DefaultTableModel tableModel = new DefaultTableModel();
tableModel.setColumnCount(2);
tableModel.addRow(new Object[] { "a", "b" });
return tableModel;
}
private static TableModel createTableModel2() {
DefaultTableModel tableModel = new DefaultTableModel();
tableModel.setColumnCount(2);
tableModel.addRow(new Object[] { "c", "d" });
return tableModel;
}
private static void showFrame(JFrame frame) {
frame.setSize(240, 400);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
}
I am trying to make a Checkbox change value on click in a JTable. Here is the code I use for that in the MouseListener
public void mouseClicked(MouseEvent e) {
Point mouse = e.getPoint();
int row = table.rowAtPoint(mouse);
int col = table.columnAtPoint(mouse);
if (col == 0) tableModel.setValueAt(new Boolean(!(Boolean) tableModel.getValueAt(row, col)), row, col);
}
The problem is, that when I sort the table, this happens
Here is an SSCCE
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
#SuppressWarnings("serial")
public class SSCCE extends JFrame {
JTable table;
public SSCCE() {
setSize(300, 200);
Object[][] data = { {false, "This is false"}, {true, "This is true"}};
table = new JTable(new CustomTableModel(data));
add(table);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}
private class CustomTableModel extends AbstractTableModel {
Object[][] data;
public CustomTableModel(Object[][] data) {
this.data = data;
}
public Class<?> getColumnClass(int columnIndex) {
return data[0][columnIndex].getClass();
}
public int getColumnCount() {
return data[0].length;
}
public int getRowCount() {
return data.length;
}
public Object getValueAt(int rowIndex, int columnIndex) {
return data[rowIndex][columnIndex];
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new SSCCE();
}
});
}
}
Is there a way around this? Or a better method (not ListListener) to detect clicks on cells?
There is no need to use a MouseListener. You just need to use a proper editor for the column and the table will handle it for you.
Read the section from the Swing tutorial on How to Use Tables for more information and working examples.
Basically you need to do two things:
Add Boolean data to the TableModel
Override the getColumnClass(...) method of the TableModel to return Boolean.class for that column and the table will choose the appropriate editor.
Above is the answer for your question, but for future information the MouseEvent is relative to the table, so you want to use table methods to access the data. That is you would use table.getValueAt(...) and table.setValueAt(...). These reference the data as it is currently displayed in the view of the table. That is the view could be sorted or the column could have been moved.
This is my TableModel, I have extended AbstractTableModel
class CustomTableModel extends AbstractTableModel
{
String[] columnNames = {"Name","Contact","eMail","Address","City","Pin","State","Type","ID"};
Vector<String[]> data = new Vector<String[]>();
CustomTableModel()
{
try
{
//Using JDBC connection//
while(rs.next())
{
String[] s=new String[9];
s[0]=rs.getString(1);
//System.out.println(s[0]);
s[1]=rs.getString(2);
s[2]=rs.getString(3);
s[3]=rs.getString(4);
s[4]=rs.getString(5);
s[5]=rs.getString(6);
s[6]=rs.getString(7);
s[7]=rs.getString(8);
s[8]=rs.getString(9);
data.add(s);
}
}
catch(Exception e)
{
System.out.println("the exception is :"+e.toString());
}
}
public int getColumnCount() {
int columnCount = columnNames.length;
return columnCount;
}
public int getRowCount() {
int rowCount = data.size();
return rowCount;
}
public Object getValueAt(int rowIndex, int columnIndex) {
return data.get(rowIndex)[columnIndex];
}
public String getColumnName(int column) {
return columnNames[column];
}
public void removeRow(int r)
{
for(int i=0;i<data.size();i++)
{
String[] s = (String[])data.get(i);
if(s[0]==getValueAt(r,0))
{
try
{
//using JDBC connections to delete the data from DB//
//also removing the value from data and also updating the view//
data.remove(data.get(i));
fireTableRowsDeleted(r, r);
}
catch (Exception e)
{
System.out.println(e.toString());
}
break;
}
}
}
//I am using the following code to update the view but it doesnot work//
public void addRow(String[] a)
{
data.add(a);
fireTableRowsInserted(data.size() - 1, data.size() - 1);
}
}
I have a table class which extends CustomTableModel .
class table extends CustomTableModel
{
final JButton editButton = new JButton("Edit");
final JButton deleteButton = new JButton("Delete");
final JTable mytable = new JTable(new CustomTableModel());
.
.
.
}
I have a add button , and in its action listener i use the following code to pass the values that i wanted to add.
String[] a = {"a","b","c","d","e","f","g","h","i"};
table myTableObj = new table();
myTableObj.addRow(a);
Pls let me know where i am going wrong . Thanks
Pls let me know where i am going wrong . Thanks
String[] a = {"a","b","c","d","e","f","g","h","i"};
table myTableObj = new table();
myTableObj.addRow(a);
code lines talking about
create a new row
create a new JTable
row is added to a new JTable
result is that a new JTable is never added to visible Swing GUI
don't do that, why is a new JTable recreated on every JButtons event
add String[] a... to the CustomTableModel directly
for better help sooner post an SSCCE, short, runnable, compilable
The table class makes no sense. It is supposed to be a TableModel that shoud be set into a JTable. Instead you have JTable declared as a field inside this table class (which should be Table btw according to Java naming convention). The result is that when constructing a new table object, a JTable is constructed inside it with another CustomTableModel inside. So the tableModel you are adding rows into is not the tableModel actually used by your JTable.
You can also use the myCustomTable.fireTableStructureChanged();