In my form I have a JTable with a TableModelListener. The tableChanged method updates the logic on my JButtons. This all works correctly. When a user edits a value in a cell in the jtable - the tableChanged method executes and the buttons are refreshed accordingly.
The problem I am having and it is a show stopper. The JTable displays objects and some attributes of the object. The user selects the objects from the application.
If I there is a object selected and being displayed in the jtable. If that user changes a attribute value in the application and not edit it in the JTable. The jtable is still refreshed and the changed value is displayed. But a TableModelEvent is not taking place and my button logic is never refreshed.
I have looked at TableCellListeners - but that is still looking for a edit in the table. So I don't think that will work here.
How can I tell that something has changed and the Table has been updated without a Event taking place in the jtable itself?
EDIT: Placing some of the jtable code
This is in my base dialog class
selectTable = new JTable(SingletonSelectTable.getInstance());
selectTable.getModel().addTableModelListener(this);
selectTable.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component comp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
Font font = comp.getFont();
if (SingletonSelectTable.getInstance().isCellBold(row, column) == true){
comp.setFont(font.deriveFont(Font.BOLD));
}
return comp;
}
});
selectTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
selectTable.setCellSelectionEnabled(false);
selectTable.setRowSelectionAllowed(true);
selectTable.setColumnSelectionAllowed(false);
JScrollPane ScrollPane = new JScrollPane(selectTable);
the tableChanged method
public void tableChanged(TableModelEvent e) {
setRemoveButtonVisibility();
setRemoveAllButtonVisibility();
setCommentButtonVisibility();
setOKButtonEnabledStatus();
}
my singleton class
public class SingletonSelectTable extends AbstractTableModel {
I hope this helps
How can I tell that something has changed and the Table has been updated without a Event taking place in the jtable itself?
There is never an event in the JTable. It is the underlying TableModel that changes, and it is the TableModel that fires an event.
The JTable registers a listener to the TableModel so it knows when it should update the displayed table content.
So if you are saying that the JTable gets updated (automatically, so without you scrolling/selecting/hovering/...) means that the TableModel does fire an event, meaning you can also listen for it.
In the scenario an element of your TableModel is changed in your application, something should fire the TableModelEvent from the TableModel. Typically this is done by either letting the TableModel listen for changes in the objects it contains, or letting the code that changes the object also notify the TableModel that the underlying data has been changed. Either way, the TableModel needs to fire an event, so there is no reason why your listener would not receive it, unless it is simply not fired (meaning an incorrect TableModel).
My best guess, based on your comments, is that you have an incorrect implementation of your TableModel and that your TableModel simply does not fire an event, and that the JTable gets updated 'by accident' (in my experience is a JTable rather robust for missing events, certainly when no rows are added/removed ... definetely compared to a JTree)
The getValueAt() method picks up the change and then updates the jtable
This really sounds incorrect. The getValueAt() method is normally called by the JTable after it receives an event. Thanks to the event, the JTable knows it must be updated so it queries the model for the new data. So the getValueAt method does not pick up the change, but gets called as a result of the change.
I would strongly suggest to take a look at the Swing table tutorial, and certainly the sections about Creating a table model, Listening for data changes and Firing data change events
Related
I'm starting to learn how implement MVC pattern in swing. I have a confusion when in comes to the data in model.
In the application that I'm doing, I have several JTextFields. The data set in the fields come from JTable. I have a ListSelectionListener in my controller added to that jtable (in the view) so that when selection has changed, the data from the selected row will be reflected to the respective textfields
public void transferTableDataToFields(){
if(tblProduct.getSelectedRows().length != 0){
int selRow = tblProduct.getSelectedRow();
txtID.setText(tblProduct.getValueAt(selRow, 0).toString());
txtName.setText(tblProduct.getValueAt(selRow, 1).toString());
txtDescIn.setText(tblProduct.getValueAt(selRow, 2).toString());
txtSupplier.setText(tblProduct.getValueAt(selRow, 4).toString());
txtPrice.setText(tblProduct.getValueAt(selRow, 5).toString());
}
}
My question is, am I doing it the right way? Should I define fields in the model that corresponds to the textfields in my view, then change my code to controller setting the model's fields to values from view's jtable then let the model fire a property change notification to its listener, then let the listener call the update method in the view based on the property change event?
My problem with this approach is that, the view should have a method that will return the row selected from the jtable which I find a bit ugly.
EDIT
#trashgod this is what I'm saying.
Inside my TableModel implementation, is it better to populate the data here?
public MyTableModel extends AbstractTableModel{
List<Row> data;
Row header;
public MyTableModel(){
initializeData();
}
public void initializeData{
//query database here then put it in the list
}
//other methods to implement e.g. getvalueAt(int x, inty){data.get(x).get(y);}
}
Is it better if I make it like this? because currently I stored and populate the data of my table in my main model (also containing fields with corresponding textfields in the SelectedRow view) then I pass the data in my JTable's model.
In this case, the current selection is a property of the view, an instance of JTable, not the model, an implementation of TableModel.
If you choose to supersede editing in the JTable itself, a ListSelectionListener is the correct way to update your (anonymous) dependent view, e.g. SelectedRow. Your controller should have little to do except add SelectedRow as a listener to your JTable. As SelectedRow contains (presumably) editable fields, you are responsible for several things:
Propagate any changes back to the original table's TableModel, typically via setValueAt().
Convert between view and model coordinates, mentioned here.
Preclude (or synchronize via the ListSelectionListener) editing in the source JTable.
Addendum: Inside my TableModel implementation, is it better to populate the data here?
The answer depends on the application. Your TableModel implementation should expose a public API that provides as much (or as little) as required to support the application's requirements for concurrency and latency. For reference, DefaultTableModel is a general purpose example, although you'll want to use something more recent than Vector internally.
I am using a custom cell renderer that implements TableCellRenderer and displays JTextArea (instead of JLabel) for each row. I am basically overriding getTableCellRendererComponent(...) method with mine which does some additional calculations per row. These calculations have to be done just once per table update. Since getTableCellRendererComponent method is being called with every mouse move, lag occurs. So I thought I should prevent getTableCellRendererComponent from being called to avoid lag.
Considerations:
1) My table has only 1 column and has no header.
2) My data is static and is read from an ArrayList by getValueAt(int row, int column) method in a custom tablemodel class implementing AbstractTableModel.
3) I don't need to watch over mouse motion events.
4) I don't expect much data, so I might want to display whole table at once or cache it completely.
5) Most lag is caused by setting text each time when returning from getTableCellRendererComponent, because some rows are using Right-to-Left chars and RTL text requires extra time to render.
Do not try to limit the number of getTableCellRendererComponent calls. Instead, make your implementation of the renderer better by caching the results of the calculation.
You can easily add a listener to the tablemodel so that your renderer knows when the model is updated. Only then it should mark that the stored calculation results are invalid, and recalculate them on the next getTableCellRendererComponent call.
I have a column in JTable that binds to the underlying boolean property on a list of business objects. I also have a combobox, which should select which items should be selected. I basically added the following code as a handler to the combobox:
macroCombo.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JComboBox comboBox = (JComboBox) e.getSource();
Predicate filter = (Predicate) comboBox.getSelectedItem();
for(SelectableKey key : tableEntries){
key.setSelected(filter.evaluate(key));
}
}
});
I also have a few other controls I want to change based on the value. At the moment, only a few cells in the table change their state to be selected/deselected. Only when I click on the row, or select multiple rows, the UI updates itself. Is there a call from the handler I need to make to tell GUI to redraw itself? ALos, if I modify other controls than JTable, how would I tell them to change their state?
Thanks
When you update a value in your TableModel, the model should fire a corresponding TableModelEvent (type: UPDATE).
If your TableModel for example extends from AbstractTableModel, you can call the fireTableRowsUpdated method after you have made the changes.
Another approach is a TableModel which knows when it gets updated (for example by adding listeners to the objects it contains). This allows other code to simply update the objects contained in the TableModel, without having knowledge of the TableModel. The TableModel itself will then fire the event when it detects changes made to the objects it contains.
I prefer the second approach, as this avoids that I have to pass that TableModel around to all my other classes.
Consult the table tutorial for more information.
I have a custom JTable (15 rows by 20 cols) that was created to work for all JComponents. I'm currently using it for a mixture of JComboBoxes, JTextFields, and JButtons. Oh, and I'm using Java5 (a requirement).
I have two questions:
1) The first regards the TableCellRenderer class, and its single method:
public Component getTableCellRendererComponent(final JTable table,
final Object value,
final boolean isSelected,
final boolean hasFocus,
final int row,
final int column) {...}
All it does is cast the Object value argument to a JComponent, and potentially change the background color. No big deal. The question I have is why is this method is called sooooo often. When selecting a single cell, it is called 23 times. When Alt-Tabbing between two UNRELATED applications (I use Win7), this method is called over 200 times (and only for JButtons and JTextFields)!
Is this in any way necessary, and if not, how can I put a stop to unnecessary rendering calls?
2) The second question regards the JTable itself. When I'm editing a cell (cursor in a JTextField and blinking) and I click on another cell, that cell is only selected. If I then click on another cell, however, I start editing that cell. All I can think is that from the initial editing component, I'm selecting the JTable, and then selecting the component within. Is there any way to change this? Either going one way (always selects the jtable on first click) or the other (always enters cell on first click). I would prefer the first option, if possible.
Thansk to anyone who can grant some insight/help!
1) Why are you storing Components in the TableModel? That is not efficient. You should be storing data in the TableModel. JTable was designed to render data using a single component. The default renderer is generally a JLabel. Then when you edit a cell a different component is used.
When you click on a cell you may need to re-render the previous row (to remove the row selection) and then render the current row with the selection. So the renderer is called for each visible cell that is affected. Tabbing to the application probably causes all the visible cells to be re-rendererd.
2) Hard to answer since this is not the default behavour. You must also be using custom editors and I don't know what your custom code looks like. The default editor has a setClickCountToStart() method which defaults to 2. Maybe you set this to 1.
Change your solution to use data (not Components) in the TableModel and post your SSCCE if you have further questions.
I have been stumped with this for quite some time now. I understand you use the table model to refresh the actual table with the new values however I cannot seem to get this to work. I have added a tablemodellistener to my form and have a tableChanged method. However, I cannot seem to figure out why the tableChanged method isn't getting called when I insert into a the table.
public void tableChanged(TableModelEvent e) {
int row = e.getFirstRow();
int column = e.getColumn();
DefaultTableModel model = (DefaultTableModel)e.getSource();
// String columnName = model.getColumnName(column);
//Object data_1 = model.getValueAt(row, column);
//model.fireTableCellUpdated(row, column);
//model.fireTableDataChanged();
//customerTable.repaint();
}
Could I completely rebuild the table if I click the refresh button on my form? Would that at all be possible? If not, do I have to call my tableChanged method from my refresh button's action performed method in order for it to trigger? I've been stuck on this for quite some time now and I would just like to get this figured out for the benefit of learning.
If you have the option and it fits your needs, I'd reccomend taking a look at GlazedLists. Then you won't have to worry about updating your table models--it's all handled for you.
Here's a jump to a relevant part of the GlazedLists tutorial.
I believe you need to manually add your Table as Listener of table TableModel.