Creating a custom Table Model to provide data from ArrayList - java

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.

Related

how to display the last added row in the top of the jTable

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).

JTable not filled

I have got JTable with datas from DataBase. Once the table is filled but somotimes (totally random) not. Connection with DB is correct. I haven't got any idea why in one situation table is filled and in other not. there is absolutely no rule.
Model class:
public class MaterialModel extends AbstractTableModel
{
public List<Material> materials = new ArrayList<Material>();
String[] columns = {"ID_Material", "Nazwa"};
public int getRowCount() {
return this.materials.size();
}
public int getColumnCount() {
return columns.length;
}
public String getColumnName(int col){
return columns[col];
}
public Object getValueAt(int rowIndex, int columnIndex) {
Material material = materials.get(rowIndex);
switch (columnIndex)
{
case 0: return material.id;
case 1: return material.name;
}
return null;
}
public void setListMaterials(List<Material> listMaterials){
this.materials = listMaterials;
}
public void reset(){
this.materials.clear();
}
}
Form class:
public class Form extends JFrame{
private JTable materialTable;
MaterialDAO materialDAO;
public MaterialModel materialModel;
public Form() throws SQLException {
super("Magazyn");
setContentPane(mainPane);
pack();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(500,500);
this.initComponets();
materialDAO = new MaterialDaoImpl();
materialTable.setModel(new MaterialModel());
this.fillTable();
}
public void fillTable() throws SQLException {
this.materialTable.repaint();
materialModel = (MaterialModel) this.materialTable.getModel();
materialModel.reset();
materialModel.setListMaterials(materialDAO.getAllMaterials());
}
}
Thank you a lot for your help.
As i see there is no SQL query, check if you are executing a sql query, i don't know if you are using Hibernate or something other, try to put the part of executing sql query, by this way we can see the error you did.

Threadsafety in SwingWorker - Updating JTable in a Threadsafe way

I have a list of travel offers that I read and parsed from a XML file and added them to my GUI using JTable. I also have some update functionalities (at interval and instantly on click) that updates the GUI as soon as new offers are added to the XML. My aim is to add the offers in the GUI in thread safe way.
This is the class (UpdateData.java) where i perform doInBackground() using Swingworker and more concern about safety. (Other classes are also shown below if anyone is interested to take a deeper look) Can SwingUtilities.invokeLater() be used to make it thread-safe? Does overriding Swingworkers done(), execute() and process() will help in some way to achieve safety? In that case how? (newbie at thread prog) (Other classes are given below if anyone is interested to get a deeper look). Some Help / Feedback will be highly appreciated.
Class: UpdateData.java
public class UpdateData extends SwingWorker<Integer, Integer> {
private ArrayList<RawTravelData> listOfOffer;
private TravelData offerData;
private XMLReader parseData;
//the controller
private ControlUpdate updtController;
//constructor
public UpdateData(TravelData o, ControlUpdate offerController) {
updtController = offerController;
parseData = new XMLReader();
offerData = o;
}
#Override
protected Integer doInBackground() throws Exception {
listOfOffer = parseData.fetchData();
offerData.setData(listOfOffer);
updtController.setOfferArray(listOfOffer);
return null;
}
}
Class: RawTravelData.java
public class RawTravelData {
private String destination = "";
private String travelDate = "";
private int currPrice;
//empty constructor
public RawTravelData() {
}
//setters ad getters for destination, travel date and currprise
}
Class: TravelData.java
public class TravelData extends AbstractTableModel {
//the table header strings
private String[] colNames = { "Destination", "Date", "Price", "Details" };
private static final long serialVersionUID = 1L;
//arraylist of the offer data
private ArrayList<RawTravelData> offerList;
//constructor
public TravelData(ArrayList<RawTravelData> rtd) {
offerList = rtd;
}
//second constructor to create empty list
public TravelData() {
offerList = new ArrayList<RawTravelData>();
}
//add the list
public void setData(ArrayList<RawTravelData> o) {
offerList = o;
this.fireTableDataChanged();
}
//get the offer list
public ArrayList<RawTravelData> getOfferList() {
return offerList;
}
#Override
public Class<?> getColumnClass(int columnIndex) {
switch (columnIndex) {
case 0:
return String.class;
case 1:
return Integer.class;
case 2:
return String.class;
case 3:
return String.class;
default:
break;
}
return String.class;
}
#Override
public int getColumnCount() {
return colNames.length;
}
#Override
public int getRowCount() {
return offerList.size();
}
#Override
public Object getValueAt(int arg0, int arg1) {
switch (arg1) {
case 0:
return offerList.get(arg0).getDestination();
case 1:
return offerList.get(arg0).getPrice();
case 2:
return offerList.get(arg0).getTravelDate();
case 3:
return "Details";
default:
break;
}
return "null";
}
#Override
public String getColumnName(int col) {
return colNames[col];
}
}
Class: XMLReader.java
public class XMLReader {
//Method to fetch and read all the data from the XML file
public ArrayList<RawTravelData> fetchData() {
//parse data and return as arraylist of offers
return arrayOfOffer;
}
}
Class: ControlUpdate.java
//This class is responsible for controlling the updating of the offer data in the background
public class ControlUpdate {
private TablePanel tablePane;
private ArrayList<RawTravelData> offerArray;
//..
//Constructor
public ControlUpdate(TablePanel tablePane) {
settingsVal = new SaveSettings();
this.tablePane = tablePane;
tablePane.getOfferTable().addMouseListener(
new TableSortListener(tablePane.getOfferTable(), this));
runUpdateTask();
setUpdateInterval(settingsVal.readSettings());
}
//run the updates
private void runUpdateTask() {
//used Timer and ScheduledThreadPool
}
//get the table panel
public TablePanel getTablePanel() {
return tablePane;
}
//setting the list to a new offer list for the updater
public void setOfferArray(ArrayList<RawTravelData> rtd) {
offerArray = rtd;
}
}
All modifications of Components and their models need to be performed in the AWT event dispatch thread, not in a background thread. The second and third lines of your doInBackground method should be moved to the done method, which is guaranteed to be executed in the AWT event thread.
It is also customary to have the SwingWorker's value type be the data you're obtaining in the background.
public class UpdateData
extends SwingWorker<List<RawTravelData>, Integer> {
// ...
#Override
protected List<RawTravelData> doInBackground() throws Exception {
return parseData.fetchData();
}
#Override
protected void done() {
try {
List<RawTravelData> listOfOffer = get();
offerData.setData(listOfOffer);
updtController.setOfferArray(listOfOffer);
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
// Someone wants us to exit cleanly.
e.printStackTrace();
}
}
}

Why refreshing JTable doesn't work after deletion

I have JTable where I can update and delete rows. My problem is that when I want to print out records table refreshes but when I delete/update it doesn't.
PrisonerEvent contains data to delete in database. There is no problem with that. Here is my listener:
class DeletePrisonerListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
int row = getSelectedRow();
PrisonerEvent evt = getPrisonerEvent();
String message = "Are you sure you want to delete this prisoner?";
int option = JOptionPane.showOptionDialog(null, message, "Confirm", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, null);
if(option == JOptionPane.OK_OPTION) {
prisonerController.removePrisoner(evt.getId());
}
tablePanel.getTableModel().fireTableDataChanged();
}
}
And here is my TableModel
public class PrisonerTableModel extends AbstractTableModel {
private List<Prisoner> db;
private String[] colNames = { "Name", "Surname", "Date of birth", "Height", "Eye color", "Hair color",
"Country of origin", "Gender"};
public PrisonerTableModel(){
}
public String getColumnName(int column) {
return colNames[column];
}
public void setData(List<Prisoner> db) {
this.db = db;
}
public int getColumnCount() {
return 8;
}
public int getRowCount() {
return db.size();
}
public Object getValueAt(int row, int col) {
Prisoner prisoner = db.get(row);
switch(col) {
case 0:
return prisoner.getName();
case 1:
return prisoner.getSurname();
case 2:
return prisoner.getBirth();
case 3:
return prisoner.getHeight();
case 4:
return prisoner.getEyeColor();
case 5:
return prisoner.getHairColor();
case 6:
return prisoner.getCountry();
case 7:
return prisoner.getGender();
}
return null;
}
}
Your PrisonerTableModel doesn't have a method to remove a row of data from the TableModel. If you want to remove data from the table then you need to remove data from the TableModel. The TableModel will then invoke the fireTableRowsDeleted(...) method. Your application code should never invoke a fireXXX(...) method of the TableModel.
The basic logic for removing a row of data would be something like:
public void removePrisoner(int row)
{
db.remove(row);
fireTableRowsDeleted(row, row);
}
Check out Row Table Model for a more complete example of how to better implement the logic in your TableModel.

ArrayOutOfBoundsException when removing Objects from a JTable

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);
}
}

Categories