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());
}
}
}
Related
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!
Does someone know a good way to display the sorting icons in the header of a JTable, without using the build in sort functionality?
The sorting is done by the table model (actually a database) and not by the JTable itself. Thats why the automatic display of the icons doesn't work. Maybe one can insert a dummy RowSorter that does nothing, but makes the sort icons appear?
I found a better Solution
I just wrote my own RowSorter, so that the sorting does not have any effect, but redirects the sorting request to the model instead. That way the sort order is displayed by the look and feel itself. Some Pseudocode:
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import javax.swing.RowSorter;
import xyz.SortableTableModel;
public class MyRowSorter<M extends SortableTableModel> extends RowSorter<M> {
private M tableModel;
private List<? extends SortKey> sortKeys = new LinkedList<>();
public MyRowSorter(M tableModel) {
this.tableModel = tableModel;
}
#Override
public M getModel() {
return tableModel;
}
#Override
public void toggleSortOrder(int column) {
// redirecting sort request to model and modification of sortKeys
List<? extends SortKey> newSortKeys = ...;
setSortKeys(newSortKeys);
}
#Override
public int convertRowIndexToModel(int index) {
return index; // will always be the same
}
#Override
public int convertRowIndexToView(int index) {
return index; // will always be the same
}
#Override
public void setSortKeys(List<? extends SortKey> keys) {
if (keys == null) {
sortKeys = Collections.EMPTY_LIST;
} else {
sortKeys = Collections.unmodifiableList(keys);
}
fireSortOrderChanged();
}
#Override
public List<? extends SortKey> getSortKeys() {
return sortKeys;
}
#Override
public int getViewRowCount() {
return tableModel.getRowCount();
}
#Override
public int getModelRowCount() {
return tableModel.getRowCount();
}
// no need for any implementation
#Override public void modelStructureChanged() { }
#Override public void allRowsChanged() { }
#Override public void rowsInserted(int firstRow, int endRow) { }
#Override public void rowsDeleted(int firstRow, int endRow) { }
#Override public void rowsUpdated(int firstRow, int endRow) { }
#Override public void rowsUpdated(int firstRow, int endRow, int column) { }
}
In that case you can try to write a custom TableCellRenderer for JTableHeader.
Here is simple example of renderer:
private static class MyRenderer implements TableCellRenderer {
private ImageIcon icon1;
private ImageIcon icon2;
private TableCellRenderer defaultRenderer;
MyRenderer(JTable t){
defaultRenderer = t.getTableHeader().getDefaultRenderer();
icon1 = new ImageIcon(getClass().getResource("1.png"));
icon2 = new ImageIcon(getClass().getResource("2.png"));
}
#Override
public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
Component c = defaultRenderer.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, col);
if(col%2 == 0){
((JLabel)c).setIcon(icon1);
} else {
((JLabel)c).setIcon(icon2);
}
return c;
}
}
Here icon1 and icon2 is your sorting icons.
And you can set that renderer for your JTableHeader like next:
table.getTableHeader().setDefaultRenderer(new MyRenderer(table));
table - is your JTable.
The sorting is done by the table model (actually a database) and not by the JTable itself.
Check out the DefaultRowSorter class. Maybe you use the setSortsOnUpdates(...) and setSortKeys(...) so the sorting icons match the sort from the database. You could try:
Creating an empty model
Set the sort keys
use setSortsOnUpdates(false);
Update the model using the setDataVector() (or some equivalent method if using a custom model)
Note this approach assumes you have created the TableModel with column names and no data and added the model to the JTable. I think you will then also need to use:
table.setAutoCreateColumnsFromModel(false);
to prevent the TableColumnModel from being recreated when you load the data into the model.
Solution is tricky when you want your code to work with other existing Swing layouts (I am talking about com.formdev .... flatlaf ). These L&Fs create a special Header renderer.
Here is a simple solution that will work with all main L&Fs on the market (tatoo, formdev, jgoodies). The trick is to subclass from DefaultTableCellHeaderRenderer but also to pass the table look and feel current header renderer as parameter.
// this custom renderer will display the sorting icon for all afftected columns.
class CustomTableHeaderRenderer extends DefaultTableCellHeaderRenderer implements TableCellRenderer{
final private Icon ascIcon = UIManager.getIcon("Table.ascendingSortIcon");
final private Icon descIcon = UIManager.getIcon("Table.descendingSortIcon");
TableCellRenderer iTableCellRenderer = null;
public CustomTableHeaderRenderer(TableCellRenderer tableCellRenderer)
{
iTableCellRenderer = tableCellRenderer;
}
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
JLabel label = (JLabel) iTableCellRenderer.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column) ;
List<? extends SortKey> sortKeys = table.getRowSorter().getSortKeys();
label.setIcon(null);
for (SortKey sortKey : sortKeys) {
if (sortKey.getColumn() == table.convertColumnIndexToModel(column)){
SortOrder o = sortKey.getSortOrder();
label.setIcon(o == SortOrder.ASCENDING ? ascIcon : descIcon);
break;
}
}
return label;
}
}
yourTable.getTableHeader().setDefaultRenderer( new CustomTableHeaderRenderer( yourTable.getTableHeader().getDefaultRenderer() ));
Following Situation: I have a J(X)Table with RowHeader (As guidline I used one of Rob Camicks great Examples). All worked as expected.
By requirement the data I receive from server already contains a tablerownumber, which I have to show in the rowheader and the data should be filterable. So I extended the example, and I added a filter. When I filtered the view I saw gaps in my row numbers (for example: 1, 3, 6,..), which is the desired effect.
To be able to filter and sort the table by my own tablerow, I added a TableRowSorter. And here I started to get confused. The Example uses the same TableModel and SelectionModel for mainTable and rowHeaderTable:
setModel( main.getModel() );
setSelectionModel( main.getSelectionModel() );
This is great, since I don’t have to synchronize them. But concerning TableRowSorter I suddenly wasn’t sure, if I also can or even have to use the same TableRowSorter-Instance or if I have to create a TableRowSorter for each table. First I added the same to both Tables, since this seemed practically, but then I got IndexOutOfBound-Exceptions in many cases. After some digging I found out that this is because the TableRowSorter gets updated twice at each TableModelEvent, because each table (RowHeader and MainTable) notifies the TableRowSorter about tablechanges on its own.
Now I am not sure which the right way to go is. Following solutions came into my mind: Should I add a second TableRowSorter (one for each table) and synchronize these, or should I wrap the TableModel within the RowHeaderTable and let it not fireing any Events? Or maybe I should create my own kind of RowHeaderTable which doesn’t notify Sorters about changes at all?
Here's a quick (beware: not formally tested! the usage example works fine, though) implementation of a wrapping RowSorter.
does nothing on receiving notification of model changes
delegates all status queries
listens to wrapped rowSorter and propagates its events
It's client's responsibility to keep it in synch with the rowSorter used in the main table
Usage example (in terms of SwingX test infrastructure and with SwingX sortController/table):
public void interactiveRowSorterWrapperSharedXTable() {
final DefaultTableModel tableModel = new DefaultTableModel(list.getElementCount(), 2) {
#Override
public Class<?> getColumnClass(int columnIndex) {
return Integer.class;
}
};
for (int i = 0; i < tableModel.getRowCount(); i++) {
tableModel.setValueAt(i, i, 0);
tableModel.setValueAt(tableModel.getRowCount() - i, i, 1);
}
final JXTable master = new JXTable(tableModel);
final TableSortController<TableModel> rowSorter = (TableSortController<TableModel>) master.getRowSorter();
master.removeColumn(master.getColumn(0));
final JXTable rowHeader = new JXTable(master.getModel());
rowHeader.setAutoCreateRowSorter(false);
rowHeader.removeColumn(rowHeader.getColumn(1));
rowHeader.setRowSorter(new RowSorterWrapper<TableModel>(rowSorter));
rowHeader.setSelectionModel(master.getSelectionModel());
// need to disable selection update on one of the table's
// otherwise the selection is not kept in model coordinates
rowHeader.setUpdateSelectionOnSort(false);
JScrollPane scrollPane = new JScrollPane(master);
scrollPane.setRowHeaderView(rowHeader);
JXFrame frame = showInFrame(scrollPane, "xtables (wrapped sortController): shared model/selection");
Action fireAllChanged = new AbstractAction("fireDataChanged") {
#Override
public void actionPerformed(ActionEvent e) {
tableModel.fireTableDataChanged();
}
};
addAction(frame, fireAllChanged);
Action removeFirst = new AbstractAction("remove firstM") {
#Override
public void actionPerformed(ActionEvent e) {
tableModel.removeRow(0);
}
};
addAction(frame, removeFirst);
Action removeLast = new AbstractAction("remove lastM") {
#Override
public void actionPerformed(ActionEvent e) {
tableModel.removeRow(tableModel.getRowCount() - 1);
}
};
addAction(frame, removeLast);
Action filter = new AbstractAction("toggle filter") {
#Override
public void actionPerformed(ActionEvent e) {
RowFilter filter = rowSorter.getRowFilter();
if (filter == null) {
rowSorter.setRowFilter(RowFilter.regexFilter("^1", 1));
} else {
rowSorter.setRowFilter(null);
}
}
};
addAction(frame, filter);
addStatusMessage(frame, "row header example with RowSorterWrapper");
show(frame);
}
The RowSorterWrapper:
/**
* Wrapping RowSorter for usage (f.i.) in a rowHeader.
*
* Delegates all state queries,
* does nothing on receiving notification of model changes,
* propagates rowSorterEvents from delegates.
*
* Beware: untested!
*
* #author Jeanette Winzenburg, Berlin
*/
public class RowSorterWrapper<M> extends RowSorter<M> {
private RowSorter<M> delegate;
private RowSorterListener rowSorterListener;
public RowSorterWrapper(RowSorter<M> delegate) {
this.delegate = delegate;
delegate.addRowSorterListener(getRowSorterListener());
}
/**
* Creates and returns a RowSorterListener which re-fires received
* events.
*
* #return
*/
protected RowSorterListener getRowSorterListener() {
if (rowSorterListener == null) {
RowSorterListener listener = new RowSorterListener() {
#Override
public void sorterChanged(RowSorterEvent e) {
if (RowSorterEvent.Type.SORT_ORDER_CHANGED == e.getType()) {
fireSortOrderChanged();
} else if (RowSorterEvent.Type.SORTED == e.getType()) {
fireRowSorterChanged(null); }
}
};
rowSorterListener = listener;
}
return rowSorterListener;
}
#Override
public M getModel() {
return delegate.getModel();
}
#Override
public void toggleSortOrder(int column) {
delegate.toggleSortOrder(column);
}
#Override
public int convertRowIndexToModel(int index) {
return delegate.convertRowIndexToModel(index);
}
#Override
public int convertRowIndexToView(int index) {
return delegate.convertRowIndexToView(index);
}
#Override
public void setSortKeys(List keys) {
delegate.setSortKeys(keys);
}
#Override
public List getSortKeys() {
return delegate.getSortKeys();
}
#Override
public int getViewRowCount() {
return delegate.getViewRowCount();
}
#Override
public int getModelRowCount() {
return delegate.getModelRowCount();
}
#Override
public void modelStructureChanged() {
// do nothing, all work done by delegate
}
#Override
public void allRowsChanged() {
// do nothing, all work done by delegate
}
#Override
public void rowsInserted(int firstRow, int endRow) {
// do nothing, all work done by delegate
}
#Override
public void rowsDeleted(int firstRow, int endRow) {
// do nothing, all work done by delegate
}
#Override
public void rowsUpdated(int firstRow, int endRow) {
// do nothing, all work done by delegate
}
#Override
public void rowsUpdated(int firstRow, int endRow, int column) {
// do nothing, all work done by delegate
}
}
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);
}
});
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);
}