Adding TableModelListener to AbstractTableModel - java

I've faced an issue in adding TableModelListener to AbstractTableModel. The program stops working and the JFrame doesn't response for any button-clicking or even closing the JFrame.
What I want is to make a JButton enabled iff the number of rows in myTable is equal or more than 2 rows.
Here is my code ...
My custom Table:
public class MyTable extends JPanel
{
public Main main;
public ArrayList<MyData> lstData;
public JTable table;
public MyTableModel model;
// ...
public MyTable(ArrayList<MyData> lstData, Main main)
{
this.lstData = lstData;
this.main = main;
model = new MyTableModel(lstData);
table = new JTable(model);
// ...
}
// ...
public int getTableSize()
{
return model.getRowCount();
}
public TableModel getModel()
{
return model;
}
public class MyTableModel extends AbstractTableModel
{
protected String[] columnNames = new String[ ] {"#","Name", "Phone Number"};
protected ArrayList<MyData> lstData;
protected Class[] types = new Class[]{String.class, String.class, String.class};
public MyTableModel(ArrayList<MyData> lstData)
{ this.lstData = lstData; }
public void SetData(ArrayList<MyData> lstData)
{ this.lstData = lstData; fireTableDataChanged(); }
#Override
public String getColumnName(int columnIndex)
{ return columnNames[columnIndex]; }
#Override
public Class getColumnClass(int columnIndex)
{ return types[columnIndex]; }
public Object getValueAt(int row, int column)
{
if (row < 0 || row > lstData.size()) return null;
MyData obj = lstData.get(row);
switch(column)
{
case 0: return obj.getID();
case 1: return obj.getName();
case 2: return obj.getPhoneNumber();
default: return null;
}
}
public int getRowCount() { return lstData.size(); }
public int getColumnCount() { return columnNames.length; }
}
}
Main class:
public class Main extends JFrame implements TableModelListener
{
public static ArrayList<myData> lstData;
public static MyTable table;
public static JButton myButton;
public Main()
{
// ...
table = new MyTable(lstData, this);
table.getModel().addTableModelListener(this);
myButton = new JButton();
myButton.setEnabled(false);
// ...
}
// ...
public void tableChanged(TableModelEvent e)
{
int firstRow = e.getFirstRow();
int lastRow = e.getLastRow();
int mColIndex = e.getColumn();
switch(e.getType())
{
case TableModelEvent.INSERT:
if(table.getTableSize() >= 2) myButton.setEnabled(true);
else myButton.setEnabled(false);
break;
case TableModelEvent.DELETE:
if(table.getTableSize() >= 2) myButton.setEnabled(true);
else myButton.setEnabled(false);
break;
}
}
}
Could you help me to solve this issue? Thanks in advance.
EDIT:
The GUI stop responding only if I add or delete elements from the table.
EDIT2:
No errors or exceptions are thrown after I add elements to the table, it's just freezing the gui with no response

basic tutorial about TableModelListener here or here or here
best would be camickr Table Cell Listener that implements deepest funcionalities for Listening in the TableCell

In your MyTableModel class, remove the following line:
protected TableModel model = this;
And also remove the following methods:
public void setModel(TableModel model){
this.model = model;
}
public TableModel getModel() {
return model;
}
You are already implementing a custom table model, there is no need to create that self reference inside of it. When your class is getting instantiated the this variable is not fully initialized and I suspect that is what is causing problems for you. But in any case the code is definitely not needed. Also, in your MyTable class I would recommend changing the getModel() function to defer to your wrapped table variable. Like so:
public TableModel getModel() {
return model.getModel();
}

Thank you guys for your help, I solve this issue by modifying the tableChanged method:
public void tableChanged(TableModelEvent e)
{
if(table.getTableSize() >= 2) myButton.setEnabled(true);
else myButton.setEnabled(false);
}

Related

empty TableModel after instantiating

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.

Custom TableCellRenderer vs. prepareRenderer or how to fix row highlighting issue?

To put this short:
What is this about
I have a JTable with Model which displays data fetched from an SAP system.
My Goal is in a specific column to display only a part of the data which is in the model. For example the row of the model has Object["a","b"] but the user is only supposed to see a.
So I read a lot of threads here on StackOverflow and a lot of tutorials on how to use custom tablecellrenderers and editors etc. but I am not able to fix my problem, which is that the cell where i registered the renderer will not be highlighted when selected.
A possible solution is described HERE but this does not work for me.
Here is my custom Renderer:
public class SapChangeNumberCellRenderer extends DefaultTableCellRenderer{
private static final long serialVersionUID = -2649719064483586819L;
private SapChangeNumberTable table;
private int valuesSize;
public final static String ASM_AMOUNT = LanguageServer.getString("71", "Baugruppen");
public SapChangeNumberCellRenderer(SapChangeNumberTable table) {
super();
this.table = table;
}
#Override
public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected,
final boolean hasFocus,
final int row, final int column) {
// components & Layout
JPanel panel = new JPanel(new BorderLayout());
JButton buttonDots = new JButton("...");
JLabel text = new JLabel();
List<String> values = (List<String>) value;
valuesSize = values.size();
if (valuesSize > 0) {
// set values
buttonDots.setPreferredSize(new Dimension(14, 14));
text.setText(values.get(0));
} else {
text.setText("");
}
if (valuesSize > 1) {
// button to open dialog only if we have more than 1 item
panel.add(buttonDots, BorderLayout.EAST);
}
panel.add(text, BorderLayout.WEST);
panel.setOpaque(true);
return panel;
}
#Override
public String getToolTipText(MouseEvent e) {
String toolTip = String.valueOf(valuesSize) + Initializer.SPACE + ASM_AMOUNT;
return toolTip;
}
public SapChangeNumberTable getTable() {
return table;
}
}
So as you can see depending on the list size of the values I manipulate the component which will be given back from the method. The setOpaque(true) method does somehow not achieve my goal.
Here is the according JTabel (note: BaseTable is just a wrapper for JTable with some goodies I need...nothing fancy here)
public class SapChangeNumberTable extends BaseTable {
/** the model */
private SapChangeNumberTableModel model = new SapChangeNumberTableModel();
/** parent frame */
private SapPanel parent = null;
public SapChangeNumberTable(SapPanel parent) {
this.parent = parent;
this.init();
}
/**
* init the table
*/
private void init() {
// set model (not filled yet)
this.setModel(model);
// set renderer
setRendererAndEditor();
// add search filter row, enabling sorting is included
this.addFilterSearchRowPanel();
// single selection
this.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
// hide
this.hideColumns();
this.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
}
/**
* sets the default table cell renderer
*/
private void setRendererAndEditor() {
getColumnModel().getColumn(convertColumnIndexToView(SapChangeNumberTableModel.COL_ID_SPM_ASM_NUMBER))
.setCellRenderer(new SapChangeNumberCellRenderer(this));
getColumnModel().getColumn(convertColumnIndexToView(SapChangeNumberTableModel.COL_ID_SPM_ASM_NUMBER))
.setCellEditor(new SapChangeNumberAsmRefTableCellEditor(this));
}
#Override
public void setStatusBarDataCount(boolean value) {
}
#Override
public void hideColumns() {
}
#Override
public int getColModelSortIndex() {
return 0;
}
#Override
public void load() {
}
#Override
public SapChangeNumberTableModel getModel() {
return model;
}
public boolean isChanging() {
return model.isFilling();
}
public SapFactoryChange getRow(int row) {
return model.getSapFactoryChange(row);
}
#Override
public void clear() {
model.clear();
}
#Override
public Component prepareRenderer(TableCellRenderer renderer, int rowIndex, int vColIndex) {
Component comp = super.prepareRenderer(renderer, rowIndex, vColIndex);
if (vColIndex == SapChangeNumberTableModel.COL_ID_SPM_ASM_NUMBER) {
//what the hack to do here to manipulate the comp ? I can't add a JPanel to a plain Component
}
return comp;
}
}
In the table I tried some stuff with prepareRenderer but here I can't manipulate the data (values) and all other stuff I am doing in the custom renderer. Maybe I have a basic understanding problem of how to approach this. I am thankful for any hints !
I just found a very simple solution which I thought would overwrite my wanted behavior, but it doesn't.
Just implemented this into the Table:
#Override
public Component prepareRenderer(TableCellRenderer renderer, int rowIndex, int vColIndex) {
Component comp = super.prepareRenderer(renderer, rowIndex, vColIndex);
if (isRowSelected(rowIndex)) {
comp.setBackground(Color.ORANGE);
}
return comp;
}
works like a charme!

How to stop adding already added value from JCombobox to JTable

I am working on netbeans and creating a swing application. I have created a JComboBox and a JTable. I am able to add value from JComboBox to JTable on a button click but if I repeat the same process the same value is again again added to the table. How to stop adding the existed value of JComboBox.
This is the code of JComboBox
private void populateCombo(){
organizationComboBox.removeAllItems();
for (Organization.Type type : Organization.Type.values()){
organizationComboBox.addItem(type);
}
}
This is the code of JTable
private void populateTable(){
DefaultTableModel model = (DefaultTableModel) organizationTable.getModel();
model.setRowCount(0);
for (Organization organization : organizationDirectory.getOrganizationList()){
Object[] row = new Object[2];
row[0] = organization.getOrganizationID();
row[1] = organization.getName();
model.addRow(row);
}
}
This is the code for my add button
Type type = (Type) organizationComboBox.getSelectedItem();
Organization o = organizationDirectory.createOrganization(type);
if(type.equals(Type.Receptionist)){
o.getSupportedRole().add(new ReceptionistRole());
}else
if(type.equals(Type.Doctor)){
o.getSupportedRole().add(new DoctorRole());
}else
if(type.equals(Type.VaccineManager)){
o.getSupportedRole().add(new VaccineManagerRole());
}else
if(type.equals(Type.LabAssistant)){
o.getSupportedRole().add(new LabAssistantRole());
}else
if(type.equals(Type.Donor)){
o.getSupportedRole().add(new DonorRole());
}else
if(type.equals(Type.Patient)){
o.getSupportedRole().add(new PatientRole());
}
populateTable();
Thanks in advance.
Don't use DefaultTableModel. This class is only for simple cases and demo applications. Simply look here for example of your own model.
So your model will looks like:
public class OrganizationModel extends AbstractTableModel {
protected String[] columnNames;
protected List<Organization> dataVector;
public OrganizationModel(String[] columnNames) {
this.columnNames = columnNames;
dataVector = new ArrayList<Organization>();
}
public String getColumnName(int column) {
return columnNames[column];
}
public boolean isCellEditable(int row, int column) {
return false;
}
public Class getColumnClass(int column) {
return String.class;
}
public Object getValueAt(int row, int column) {
return column == 0? dataVector.get(row).getOrganizationID() : dataVector.get(row).getName();
}
public void addRowWhenNotExist(Organization o) {
if (!dataVector.contains(o)) {
dataVector.add(o);
fireTableRowsInserted(dataVector.size() - 1, dataVector.size() - 1);
}
}
}
For correct working of this example you also need correct definition of methods equals and hashCode for your class Organization.
public class Organization {
// your stuff
public boolean equals(Object another) {
if (another instanceof Organization) {
return getOrganizationID() == ((Organization) another).getOrganizationID();
} else {
return false;
}
}
public int hashCode() {
return getOrganizationID();
}
}

Listen jtable change

I have a problem with table model listener. It doesn't work, and I don't know why. I have tried different methods, and read a lot of questions here, but haven't found the solution.
I've read this: Listening to JTable changes and this Row refreshing when cell is edited
but it doesn't work.
I also have read this and this
but result is the same.
Here is my code. First of all definition of the table:
private void prepareTable(JTable table, Map<String, String> tableData, int colsCount, int rowsCount, int nGram) {
//Load data, set model, remove header
NGramsTableModel nGramModel = new NGramsTableModel(tableData, allowedSymbols, colsCount, rowsCount, nGram);
nGramModel.addTableModelListener(new NGramsTableListener());
table.setModel(nGramModel);
table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
table.setTableHeader(null);
//Set editor
JTextField jtf = new JTextField();
jtf.setDocument(new NGramsTableCellDocument(nGram));
table.setDefaultEditor(String.class, new DefaultCellEditor(jtf));
//Colorize rows
for (int i = 0; i < table.getColumnModel().getColumnCount(); i++) {
table.getColumnModel().getColumn(i).setCellRenderer(new NGramsTableCellRenderer());
}
}
Here is the model listener class:
public class NGramsTableListener implements TableModelListener {
#Override
public void tableChanged(TableModelEvent e) {
System.out.println("something changed...");
System.out.println(e);
}
}
And the table model class:
public class NGramsTableModel extends AbstractTableModel implements TableModel {
private Set<TableModelListener> listeners = new HashSet<TableModelListener>();
...
...
#Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
...
//it's OK, i see this message with entered symbols
System.out.println("setValueAt: " + aValue);
//I tried use every of this, but it doesn't work. A don't see any massage from NGramsTableListener class
fireTableCellUpdated(rowIndex, columnIndex);
fireTableDataChanged();
fireTableRowsInserted(rowIndex, columnIndex);
fireTableRowsUpdated(rowIndex, columnIndex);
}
#Override
public void addTableModelListener(TableModelListener l) {
listeners.add(l);
}
#Override
public void removeTableModelListener(TableModelListener l) {
listeners.remove(l);
}
}
Actually I need to get updated object with coords(rowIndex, colIndex), because later I want get updated object and object with coords(rowIndex-1, colIndex) if exists.
Where is my mistake?
Thanks
The AbstractTableModel already implements the table model listener methods. That is the benefit of extending AbstractTableModel. The solution to your problem is to get rid of all that code.
When you extend AbstractTableModel you are responsible for implementing the other methods of TableModel, like getColumnClass(), getValueAt(...), setValueAt(...) etc.
You need to provide a method which will fireXXX notified all registered listeners for example:
public class NGramsTableModel extends AbstractTableModel implements TableModel {
private LinkedList<TableModelListener> listeners = new LinkedList<TableModelListener>();
...
...
#Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
...
//it's OK, i see this message with entered symbols
System.out.println("setValueAt: " + aValue);
//Use your fireXXX method
fireNGramTableChanged();
}
#Override
public void addTableModelListener(TableModelListener l) {
listeners.add(l);
}
#Override
public void removeTableModelListener(TableModelListener l) {
listeners.remove(l);
}
protected void fireNGramTableChanged(){
for(TableModelListener next : listeners){
next.tableChanged(new TableModelEvent());
}
}
}

Controlled editing of a row selection in JTable

I have a JTable displaying rows from an SQL database. The table is relatively small (only 4 columns and up to 1000 rows).
I would like to give the user the opportunity to edit any cells in the table but want to avoid restricting it so much so that they must use an edit dialog box (this makes for far easier error checking and validation but is less intuitive)
I have tried a few different ways of controlling edit selections using the valueChanged method of my JTable but haven't had much luck.
I would like each row to be edited and written to the database at the conclusion of editing. I would like that once a cell has been clicked to start the editing of that row, no other rows can be selected until the user has finished editing the row (other rows are grayed out). After editing each cell and pressing enter, the edit selection should jump to the next column in the same row.
Can anyone give pointers on how I can achieve this?
// Create table with database data
table = new JTable(new DefaultTableModel(data, columnNames)) {
public Class getColumnClass(int column) {
for (int row = 0; row < getRowCount(); row++) {
Object o = getValueAt(row, column);
if (o != null){
return o.getClass();
}
}
return Object.class;
}
#Override
public boolean isCellEditable(int row, int col){
return true;
}
#Override
public boolean editCellAt(int row, int column) {
boolean ans = super.editCellAt(row, column);
if (ans) {
Component editor = table.getEditorComponent();
editor.requestFocusInWindow();
}
return ans;
}
#Override
public void valueChanged(ListSelectionEvent source) {
super.valueChanged(source);
if (table!=null)
table.changeSelection(getSelectedRow(), getSelectedColumn()+1, false, false);
}
};
Edit - custom cell editor with table pointer seems to be a start
public class ExchangeTableCellEditor extends AbstractCellEditor implements TableCellEditor {
private JTable table;
JComponent component = new JTextField();
public ExchangeTableCellEditor(JTable table) {
this.table = table;
}
public boolean stopCellEditing() {
boolean ans = super.stopCellEditing();
//now we want to increment the cell count
table.editCellAt(table.getSelectedRow(), table.getSelectedColumn()+1);
return ans;
}
#Override
public void cancelCellEditing() {
//do nothing... must accept cell changes
}
#Override
public Object getCellEditorValue() {
return ((JTextField)component).getText();
}
#Override
public Component getTableCellEditorComponent(JTable arg0, Object value,
boolean arg2, int arg3, int arg4) {
((JTextField)component).setText((String)value);
return component;
}
}
The default renderer and editor is typically adequate for most data types, but you can define custom renderers and editors as needed.
Addendum: I'm unfamiliar with the approach shown in your fragment. Instead, register a TableModelListener with your model, as shown below, and update the database with whatever granularity is warranted. See also How to Use Tables: Listening for Data Changes.
Addendum: #kleopatra is correct about your TableCellEditor. One convenient way to notify listeners is to invoke the super implementation, as shown here. Note that the delegate invokes fireEditingStopped().
/** #see https://stackoverflow.com/questions/9155596 */
public class NewJavaGUI extends JPanel {
private final JTable table;
public NewJavaGUI() {
String[] colNames = {"C1", "C2", "C3"};
DefaultTableModel model = new DefaultTableModel(colNames, 0) {
#Override
public boolean isCellEditable(int row, int col) {
// return your actual criteria
return true;
}
#Override
public Class getColumnClass(int col) {
// return your actual type tokens
return getValueAt(0, col).getClass();
}
};
// Add data; note auto-boxing
model.addRow(new Object[]{"A1", "A2", 42});
model.addRow(new Object[]{"B1", "B2", 42d});
model.addTableModelListener(new TableModelListener() {
#Override
public void tableChanged(TableModelEvent e) {
// DML as indicated
}
});
table = new JTable(model);
this.add(table);
}
private void display() {
JFrame f = new JFrame("NewJavaGUI");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new NewJavaGUI().display();
}
});
}
}
The behaviour you mention can be achieved by forcing your table to start editing again.
First make sure you now yourRow and Column and that you add your own tablecelleditor that extands from the AbstractCellEditor
then add this to your stopCellEditing method:
EventQueue.invokeLater(new Runnable()
{
public void run()
{
yourTable.editCellAt( yourRow, yourColumn+1);
}
});

Categories