I'm a complete newbie to Java. I've only been learning it for half a year now. Right now I am working on a Project for school and I've hit a complete wall:
I am basically making a Program to manage your own Books. I have a class that is "Books", which holds the data for the book objects. Then there's the class "Library" which holds an ArrayList of Books. For the TableModel, I am using a class ("LibraryTableModel") that extends AbstractTableModel. I then have a GUI class wich displays the table.
The table actually works, but there are two instances where the program crashes:
When I add a Book to an empty Library, the table doesn't update. However, the Book IS added when I start the program anew (I save the Library class as a .ser file).
Then the instance which I am asking about: I have a button that removes Books from the table. the Button itself works fine, but when I remove a book, the program throws an ArrayOutOfBoundsException. When I create the table anew, it updates and the book is removed. What is the problem here, why does the program crash instead of update the table?
Code for TableModel:
public class LibraryTableModel extends AbstractTableModel
{
private String[] columnNames = {"Titel", "Autor", "Status", "Genre", "Verlag", "Seitenzahl", "ISBN", "Sprache", "Bewertung"};
private Object[][] data = {};
ArrayList<Book> lib;
public LibraryTableModel(Library l)
{
//This Method returns the ArrayList in the Library class
lib = l.getList();
int libSize = lib.size();
data = new Object[bib.size()][];
for (Book b : lib)
{
int index = bib.indexOf(b);
//(...)
//CODE HERE gets all the data that is supposed to be displayed
//from each book in the ArrayList
Object[] oA = {tit, aut, sta, gen, ver, sei, isb, spr, bew};
data[index] = oA;
}
}
public int getColumnCount()
{
return columnNames.length;
}
public int getRowCount()
{
return data.length;
}
public String getColumnName(int col)
{
return columnNames[col];
}
public Object getValueAt(int row, int col)
{
return data[row][col];
//When I try to remove a Book, the ArrayOutOfBounds Exception comes from here
}
public Class getColumnClass(int c)
{
return getValueAt(0, c).getClass();
}
public void setValueAt(Object value, int row, int col)
{
data[row][col] = value;
fireTableCellUpdated(row, col);
}
public void removeRow(int row)
{
lib.remove(row);
fireTableRowsDeleted(row, row);
}
Code in the GUI class that is revolving around the table and the tablemodel:
public class GUI implements ActionListener
{
JTable table;
LibraryTableModel model;
TableRowSorter<BibliothekTableModel> sorter;
Library lib;
JMenuItem deleteBook;
(...) The Library is loaded through the .ser file
public void showTable() //This method is envoked in the GUI constructor through pressing a button
{
model = new LibraryTableModel(lib);
table.setModel(model);
deleteBook.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int row = table.getSelectedRow();
model.removeRow(row);
//Code that saves the library at this point
table.setModel(new LibraryTableModel(lib));
}
});
popupMenu.add(deleteBook);
table.setComponentPopupMenu(popupMenu);
sorter = new TableRowSorter<BibliothekTableModel>(model);
table.setRowSorter(sorter);
JScrollPane scrollTable = new JScrollPane(table);
//Next is code, that adds this ScrollPane to my Frame
}
When I add a Book to an empty Library, the table doesn't update. However, the Book IS added when I start the program anew (I save the Library class as a .ser file).
There is no information provided which demonstrates how this works...and what's a .ser file?
Then the instance which I am asking about: I have a button that
removes Books from the table. the Button itself works fine, but when I
remove a book, the program throws an ArrayOutOfBoundsException. When I
create the table anew, it updates and the book is removed. What is the
problem here, why does the program crash instead of update the table?
There are two issues...
First, because you are using a RowSorter on the table, the visual row index returned by JTable#getSelectedRow and the physical row index in the model won't be the same, you need to use JTable#convertRowIndexToModel
int row = table.getSelectedRow();
row = table.convertRowIndexToModel(row);
model.removeRow(row);
//Code that saves the library at this point
Second, you're removing the book from the lib, but not updating the internal cache...
public void removeRow(int row)
{
lib.remove(row);
fireTableRowsDeleted(row, row);
}
The model isn't using the lib as it's source for the data, but is using the data array, which you have not updated.
While you could simply rebuild the data array, a better solution would be to get rid of it and use the lib directly, for example...
public class LibraryTableModel extends AbstractTableModel {
private String[] columnNames = {"Titel", "Autor", "Status", "Genre", "Verlag", "Seitenzahl", "ISBN", "Sprache", "Bewertung"};
private Object[][] data = {};
private Library lib;
public LibraryTableModel(Library l) {
lib = l;
}
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
return lib.getList().size();
}
public String getColumnName(int col) {
return columnNames[col];
}
public Object getValueAt(int row, int col) {
Book book = lib.getList().get(row);
Object value = null;
switch (col) {
case 0:
value = ...;
break;
case 1:
value = ...;
break;
case 2:
value = ...;
break;
case 3:
value = ...;
break;
case 4:
value = ...;
break;
case 5:
value = ...;
break;
case 6:
value = ...;
break;
case 7:
value = ...;
break;
case 8:
value = ...;
break;
}
return value;
//When I try to remove a Book, the ArrayOutOfBounds Exception comes from here
}
public Class getColumnClass(int c) {
// Don't do this, know the actualy value and return it
// Otherwise you could end up with a NullPointerException
return getValueAt(0, c).getClass();
}
public void setValueAt(Object value, int row, int col) {
// Use a simular technquie to getValueAt to extract the Book for the give
// row and update the Book's attributes
fireTableCellUpdated(row, col);
}
public void removeRow(int row) {
lib.remove(row);
fireTableRowsDeleted(row, row);
}
}
Related
Hello I have a question I want to know if is there a way to display the last added row at the top of jTable in runtime I updated the select statement "SELECT * FROM table ORDER BY ID DESC" which fills my table but the recent added row show up at the bottom of the table till I close the program and I open it again then it shows up at the top
TableItemsModel:
public class TableItemsModel extends AbstractTableModel{
ItemsDao itemDao = new ItemsDao();
private List<Items> items;
public TableItemsModel() throws Exception {
this.items = (ArrayList<Items>)itemDao.getItemsList();
}
private DateFormat df = new SimpleDateFormat("dd-MM-yyyy");
#Override
public int getRowCount() {
return items.size();
}
#Override
public int getColumnCount() {
return 3;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
Items item = items.get(rowIndex);
switch(columnIndex){
case 0: return p.getProductName();
case 1: return p.getProductCategory();
case 2: return p.getProductPrice();
default: return "";
}
}
public String getColumnName(int column){
switch(column){
case 0: return "PRODUCT NAME";
case 1: return "PRODUCT CATEGORY";
case 2: return "PRICE";
default: return "";
}
}
public void addRow(Items item){
items.add(item);
fireTableRowsInserted(items.size()-1, items.size()-1);
}
public void deleteRow(Items item){
items.remove(item);
fireTableRowsInserted(items.size()-1, items.size()-1);
}
}
jFrame:
private final TableItemsModel model;
public Products() throws Exception {
this.model = new TableItemsModel();
private void btnAddItemActionPerformed(java.awt.event.ActionEvent evt) {
String productName = txtProductName.getText();
String productCategory = txtProductCategory.getText();
int productPrice = Integer.valueOf(txtPrice.getText());
try {
int count = ItemsDao.getInstance().insert(itemDao);
if (count == 1) {
model.addRow(Items);
JOptionPane.showMessageDialog(null,"item successfully added");
} else {
JOptionPane.showMessageDialog(null, "Cannot Add Item");
}
} catch (Exception ex) {
Logger.getLogger(AddNewPatient.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
First of all why are you calling your custom object Items. That is plural. Objects should be given the singular version of the name. So a better name would be Item or maybe even Product, since you use "product" in all you method names. Be consistent.
can you tell me how to include the method insertRow into my code please
You are using a List to hold the data. Read the List API and you will see that there are two add(...) methods. One method adds the element at the end of the List and the other method inserts the element at the specified index of the List.
So to add an Item at the beginning of the List you would specify 0 as the index.
To do this you copy the logic of your addRow(...) method except you want two parameters:
public void insertRow(int index, Item item)
Then you change the logic in the method to:
insert the Item at the specified index of the List.
invoke the fireTableRowsInserted(..) method at the specified index.
By passing in the index you make the method very flexible. You can insert an Item at the start or in the middle (if required).
I'm trying create a JTable that creates the table based on information from an ArrayList. The ArrayList is filled with information that the user enters in the GUI. Every time the user presses a JButton it should add a new row of data to the table.
After googling it seems that the best way to do this is to create a custom table model. I cannot find a decent tutorial on how to do this. I've looked at the official documentation, and a few random sites and other postings. Hopefully one of you can point me in the right direction.
Here's a picture of my current GUI. The JTable should populate the center region:
And here is some of my code:
class dataModel extends AbstractTableModel
{
private String[] columnName = {"Emp Num", "Base Pay", "Hours Worked", "Pay Amt"};
public int getColumnCount()
{
return 4;
}
public int getRowCount()
{
return empData.size();
}
public Object getValueAt(int row, int col)
{
return new Integer(row*col);
}
}
The class that occurs on button click.
class listener implements ActionListener
{
#SuppressWarnings("unchecked")
public void actionPerformed(ActionEvent e)
{
ArrayList empData = new ArrayList();
int empNum = 0;
double hourlyRate = 0;
double hoursWorked = 0;
try
{
empNum = Integer.parseInt(empNumField.getText());
}
catch(NumberFormatException event)
{
JOptionPane.showMessageDialog(null, "Invalid entry.\n\nPlease enter a number for the Employee ID.", "Error", JOptionPane.WARNING_MESSAGE);
return;
}
try
{
hourlyRate = Double.parseDouble(basePayField.getText());
}
catch(NumberFormatException event)
{
JOptionPane.showMessageDialog(null, "Invalid entry.\n\nPlease enter a number for the Hourly Pay Rate.", "Error", JOptionPane.WARNING_MESSAGE);
return;
}
try
{
hoursWorked = Double.parseDouble(hrsField.getText());
}
catch(NumberFormatException event)
{
JOptionPane.showMessageDialog(null, "Invalid entry.\n\nPlease enter a number for the Hours Worked.", "Error", JOptionPane.WARNING_MESSAGE);
return;
}
double payAMT = calculatePay(hourlyRate, hoursWorked);
empData.add(empNum);
empData.add(hourlyRate);
empData.add(hoursWorked);
empData.add(payAMT);
}
You could...
Use a DefaultTableModel, which should cover your basic needs
private DefaultTableModel model;
//...
model = new DefaultTableModel(new String[] {"Emp Num", "Base Pay", "Hours Worked", "Pay Amt"}, 0);
table.setModel(model);
Then you could just add new data directly to it
class listener implements ActionListener {
private DefaultTableModel model;
listener(DefaultTableModel model) {
this.model = model;
}
#Override
public void actionPerformed(ActionEvent e) {
int empNum = 0;
double hourlyRate = 0;
double hoursWorked = 0;
double payAMT = calculatePay(hourlyRate, hoursWorked);
model.addRow(new Object[]{empNum, hourlyRate, hoursWorked, payAMT});
}
}
This is okay, it's simple, but it does mean you need to understand the structure of the data you are presenting and doesn't allow you to manage objects which might have more you might want to use later
Now, if you want something that's more customisable...
You could...
Use a custom TableModel, first, let's start with a requirement
public interface Employee {
public int getNumber();
public double getHourlyRate();
public double getHoursWorked();
public double getPay();
}
This defines the basic expectations of the data our model can manage. Why? Because you might have different types of employees or employee implementations which have more information, which is irrelevant to this table model
Next, we implement the TableModel...
public class DefaultWorkSheetTableModel extends AbstractTableModel {
private List<Employee> items;
public DefaultWorkSheetTableModel() {
items = new ArrayList<>(25);
}
public DefaultWorkSheetTableModel(List<Employee> items) {
this.items = items;
}
#Override
public int getRowCount() {
return items.size();
}
#Override
public int getColumnCount() {
return 4;
}
#Override
public Class<?> getColumnClass(int columnIndex) {
switch (columnIndex) {
case 0: return Integer.class;
case 1:
case 2:
case 3: return Double.class;
}
return Object.class;
}
#Override
public String getColumnName(int column) {
switch (column) {
case 0: return "Emp Num";
case 1: return "Base Pay";
case 2: return "Hours Worked";
case 3: return "Pay Amount";
}
return null;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
Employee item = items.get(rowIndex);
switch (columnIndex) {
case 0: return item.getNumber();
case 1: return item.getHourlyRate();
case 2: return item.getHoursWorked();
case 3: return item.getPay();
}
return null;
}
public void add(Employee item) {
items.add(item);
int row = items.indexOf(item);
fireTableRowsInserted(row, row);
}
public void remove(Employee item) {
if (items.contains(item)) {
int row = items.indexOf(item);
items.remove(row);
fireTableRowsDeleted(row, row);
}
}
}
There's a lot going on here, but this covers all the basic requirements for allow the JTable to present the data we want.
Now when you want to add data, you create a new instance of Employee and simply add it to the model...
class listener implements ActionListener {
private DefaultWorkSheetTableModel model;
listener(DefaultWorkSheetTableModel model) {
this.model = model;
}
#Override
public void actionPerformed(ActionEvent e) {
int empNum = 0;
double hourlyRate = 0;
double hoursWorked = 0;
//...
DefaultEmployee employee = new DefaultEmployee(empNum, hourlyRate, hoursWorked);
model.add(employee);
}
}
Yes, I know I didn't pass payRate, that's because it's a calculated field specific to the implementation of Employee ;)
JTable that creates the table based on information from an ArrayList.
Every time the user presses a JButton it should add a new row of data to the table.
So your real requirement is to get data from some text fields and add the data to a table.
So there is no need to use an ArrayList to do this.
You can use the DefaultTableModel for this. The DefaultTableModel supports an addRow(...) method. Using this method you can add a Vector or an Array containing the data from the 4 text fields.
So the only real change to your code would be:
//ArrayList empData = new ArrayList();
Vector empData = new Vector();
and then after you add all the data to the Vector you need to add the Vector to the DefaultTableModel:
empData.add(payAMT);
model.addRow( empData );
So of course you need to have created an empty DefaultTableModel with just the columns names first and your listener would need access to this model.
it seems that the best way to do this is to create a custom table model. I cannot find a decent tutorial on how to do this
Another option is to create a custom TableModel that contains Employee objects. In this case you could use an ArrayList to hold the individual Employee objects. For an example of creating a TableModel for this approach check out Row Table Model.
Modified a sample i found that uses a model.
Originally the sample would only add a new record.
The code was modified to allow editing of the fields and included the setValueAt() call
in order to maintain any changes.
The data is stored within an ArrayList of an Object array and this is producing errors when trying to update the ArrayList.
When using:
al.set(row, value);
It produces the error :
TableWithModel.java:66: error: no suitable method found for set(int,Object)
al.set(row, value);
^
method ArrayList.set(int,Object[]) is not applicable
(actual argument Object cannot be converted to Object[] by method invocation conversion)
1 error
Tried doing a variety of different syntax but just get different errors.
Something obvious is being overlooked but not sure what.
import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
public class TableWithModel extends JFrame {
MyModel model;
TableWithModel(Object[][] obj, String[] header) {
super("Static JTable example");
JPanel panel = new JPanel(new BorderLayout());
model = new MyModel(obj, header);
JTable table = new JTable(model);
panel.add(new JScrollPane(table));
add(panel); // adding panel to frame
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setVisible(true);
pack();
}
public static void main(String[] args) {
String[][] rowAndColumn = {
{"Row1", "Column2"},
{"Row2", "Column2"},
{"Row3", "Column2"},
{"Row4", "Column2"}
};
String[] header = {"Column1", "Column2"};
TableWithModel twm = new TableWithModel(rowAndColumn, header);
}
class MyModel extends AbstractTableModel {
ArrayList<Object[]> al;
String[] header;
MyModel(Object[][] obj, String[] header) {
this.header = header;
al = new ArrayList<Object[]>();
for(int i = 0; i < obj.length; ++i)
al.add(obj[i]);
}
public int getRowCount() {
return al.size();
}
public int getColumnCount() {
return header.length;
}
public Object getValueAt(int rowIndex, int columnIndex) {
return al.get(rowIndex)[columnIndex];
}
public String getColumnName(int index) {
return header[index];
}
public boolean isCellEditable(int row, int col)
{ return true; }
public void setValueAt(Object value, int row, int col) {
al.set(row, value);
fireTableCellUpdated(row, col);
}
}
}
The ArrayList variable al holds arrays of objects. To update the object array element at column col stored at index row in the ArrayList, you need to use the following:
public void setValueAt(Object value, int row, int col) {
al.get(row)[col] = value;
fireTableCellUpdated(row, col);
}
al.get(row)[col] = value; is a shorthand for the following:
Object[] data = al.get(row);
data[col] = value;
This is similar to what's already implemented in the getValueAt method except that you're setting the array element instead of retrieving it.
The reason why al.set(row, value); does not compile is that the set method expects the second argument to be an Object[] array, and not an Object, since that's what the ArrayList holds.
Thanks for the reply.
Ran a quick test and it works.
What is a bit strange is call "get" instead of "set".
Shortly after posting I was able to find a solution using "set"
Object[] data = al.get(row);
data[col] = value;
al.set(row, data);
It makes sense using the set but what is it that makes it work in your example using "get"?
i've declared a JTable (inside a class extended JPanel constructor) such as
data_table = new JTable(info, header) {
#Override
public boolean isCellEditable(int row, int column) {
//disable table editing
return false;
}
};
where info and column are declared
static String[][] info = new String[row][cols];
static String[] header = {"h1", "h2", "h3"};
Now i need to update, when some events occours, the table content by invoke a static method. How can i do it?
i don't have a tableModel, i've a matrix of string
All tables use a TableModel. When you create the table the matrix of Strings is used by the TableModel.
To update your data you do something like:
table.setValueAt(...);
This will cause the model to be updated and the model will tell the table to repaint itself.
Read the Swing tutorial on How to Use Tables for more information about tables.
Also, you should NOT be using static variables or method. If you are then you program is poorly designed. Again read the tutorial for a better example of how to structure your code.
If you want to notify your JTable about changes of your data, call a method on your tablemodel that will update the data and call fireTableDataChanged()
Its is a good practice to use a JTableModel to maintain your data inside the JTable.
http://docs.oracle.com/javase/tutorial/uiswing/components/table.html
And that is your tablemodel that will fire the fireTableDataChanged() when value will change.
class MyTableModel extends AbstractTableModel {
private String[] columnNames = ...//same as before...
private Object[][] data = ...//same as before...
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
return data.length;
}
public String getColumnName(int col) {
return columnNames[col];
}
public Object getValueAt(int row, int col) {
return data[row][col];
}
public Class getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
/*
* Don't need to implement this method unless your table's
* editable.
*/
public boolean isCellEditable(int row, int col) {
//Note that the data/cell address is constant,
//no matter where the cell appears onscreen.
if (col < 2) {
return false;
} else {
return true;
}
}
/*
* Don't need to implement this method unless your table's
* data can change.
*/
public void setValueAt(Object value, int row, int col) {
data[row][col] = value;
fireTableCellUpdated(row, col);
}
...
}
I have a table model. I want to delete a row from a table. I am not able to find the model.removeRow method. Can someone tell he how to remove a row? Below is the code for the model.
class TableModel extends AbstractTableModel {
private Object[][] data;
private String[] columnNames;
public TableModel(Object[][] data) {
this.data = data;
columnNames=new String[1];
columnNames[0]="Data";
}
public int getRowCount() {
return data.length;
}
public int getColumnCount() {
return columnNames.length;
}
public Object getValueAt(int rowIndex, int columnIndex) {
return data[rowIndex][columnIndex];
}
public Class getColumnClass(int c) {
return String.class;
}
public boolean isCellEditable(int rowIndes, int columnIndex) {
return false;
}
public String getColumnName(int index) {
return columnNames[index];
}
public void setValueAt(Object value, int rowIndex, int columnIndex) {
data[rowIndex][columnIndex] = value;
fireTableCellUpdated(rowIndex, columnIndex);
}
}
Use DefaultTableModel. It has public void removeRow(int row) method.
you must be missing something that is obvious in the default model, maybe you're mot overriding properly if you're doing your own and you might thing it's missing :
DefaultTableModel model = new DefaultTableModel();
JTable table = new JTable(model);
// Create some data
model.addColumn("Col1");
model.addRow(new Object[]{"r1"});
model.addRow(new Object[]{"r2"});
model.addRow(new Object[]{"r3"});
// Remove the first row
model.removeRow(0);
// Remove the last row
model.removeRow(model.getRowCount()-1);
DefaultTableModel is one solution. But if yo still want to use abstrac table model, this is how you do.
This is the new model definition.
class TableModel extends AbstractTableModel {
private Object[][] data;
private String[] columnNames;
public TableModel(Object[][] data) {
this.data = data;
columnNames=new String[1];
columnNames[0]="Data";
}
public int getRowCount() {
return data.length;
}
public int getColumnCount() {
return columnNames.length;
}
public Object getValueAt(int rowIndex, int columnIndex) {
return data[rowIndex][columnIndex];
}
public Class getColumnClass(int c) {
return String.class;
}
public boolean isCellEditable(int rowIndes, int columnIndex) {
return false;
}
public String getColumnName(int index) {
return columnNames[index];
}
public void removeRow(int row)
{
Object [][]newData=new Object[data.length-1][data[0].length];
for(int i=0;i<newData.length;i++){
for(int j=0;j<data[0].length;j++){
newData[i][j]=data[i+1][j];
}
}
data=new Object[newData.length][columnNames.length];
data=newData;
fireTableRowsDeleted(row, row);
}
public void setValueAt(Object value, int rowIndex, int columnIndex) {
data[rowIndex][columnIndex] = value;
fireTableCellUpdated(rowIndex, columnIndex);
}
}
You first need to remove the row data from the data object. and then remove the row.
Kaushik, if you really want to make your own implementation of the TableModel interface and deal with the storage of rows manually (as you obviously do by using Object[][] data), then you must implement the removeRow() method because abstract table model does not "know" how to do it. removeRow() will have to fireTableRowsDeleted() at the end...
A good alternative is to refactor your class and make it extend the DefaultTableModel.
Another suggestion - since table model changes a lot, a dynamic array may be a bad choice for storage because you must either reallocate space every time you add a row (bad), or you preallocate enough space for certain number of rows, then whenever there is no enough space you allocate new Object[][] with some extra space (for next N rows). Typically people allocate enough space for 2^n rows during the reallocation process.