Table model update + fireTableDataChanged() disrupts table selection - java

I have a JTable that tracks the amount of time a person has been waiting to be seated at a restaurant. My problem is that every second, when the timer 'ticks', the selection that is on a row is removed. In other words, if you click a row it becomes highlighted with a blue background and outline, but then when the timer ticks the blue goes away.
ActionListener actListner = new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
aTable.updateTime();
}
};
Timer timer = new Timer(1000, actListner);
timer.start();
This is in the main class
public void updateTime()
{
data.updateTime();
fireTableDataChanged();
}
This is in the table model
public void updateTime()
{
Date newTime = new Date();
for (int i = 0; i < startTime.size(); i++)
{
this.setTimeWaiting(i, hoursMin.format(new Date(newTime.getTime() - startTime.get(i).getTime())));
}
}
This is in the data model.

fireTableDataChanged() tells the table that the data may have changed in any way, and essentially you use it when the entire data set has completely changed (e.g. you've replaced all of the previous contents with new information). The table will redraw itself from scratch and may also clear the selection.
Instead, use the more conservative fireTableCellUpdated(int,int) and specify each cell that may have been modified due to the time change (presumably, everything in the "wait time" column).
You could also use fireTableRowsUpdated(int,int) and specify the entire range of rows that have been updated in one call, but generally it's better to stick to the conservative side to minimize unnecessary redraws.

Related

Jtable get selected row always returns -1

I have a problem since yesterday.
I'v created a jTable with the WindowBuilder in Eclipse and currently are trying to get the number of the selected row, by using the .getSelectedRow() function, but it is always returning -1 (no row selected), even when I have selected something.
This is my current code for testing the output:
public void checkActiveItem() {
System.out.println(tableBills.getSelectedRow());
}
I try to let it run this way trough a timer and at least that seems working:
Timer time = new Timer();
time.schedule(new TimerTask() {
public void run() {
Frontend f = new Frontend();
f.checkActiveBill();
f.checkActiveItem();
}
}, 250, 250 );
The table currently has just one entry, but even the first row dosn't get returned on selection.
I can create new rows by clicking add (and name them by entering a name in the textfield next to the add button before)
To create a new row I use this code, maybe there is the problem?
public void addBill() {
//maybe need this value somewhere else to, so let -1 and +1 as it is
int numbersOfBills = tableBills.getRowCount() - 1;
Bill newBill = new Bill(txtBillName.getText(), numbersOfBills + 1);
DefaultTableModel billModel2 = (DefaultTableModel) tableBills.getModel();
int billNb = numbersOfBills + 1;
billModel2.addRow(new Object[] {newBill.getBillNr(),newBill.getBillName(), newBill.getItemsInBill()});
}
The reason why you are getting -1 from getSelectedRow() even though you seem to have "added a row" is, because the Frontend object you are calling checkActiveItem() on is a completely different Frontend object than the one you are seeing.
The issue is here, inside of your Timers run():
Frontend f = new Frontend();
You create a new Frontend object for each timer iteration. And you call checkActiveItem() on exactly this object, not on the frontend you are seeing and pressing buttons on. Hence, the incorrect output.
As a solution, don't create new Frontends, instead, call checkActiveItem() on your original frontend object, which you made visible.

JLabel keeps speeding up on each List Selection

list.getSelectionModel().addListSelectionListener(e -> {
timer = new Timer( DELAY, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent arg0) {
StockItem p = list.getSelectedValue();
label.setText(p.toString2());
label.setLocation(label.getLocation().x+1, label.getLocation().y);
if(label.getLocation().x >= frame.getWidth()) {
label.setLocation(0-label.getWidth(),label.getLocation().y);
}
}
});
timer.start();
});
I created a list selection listener to check which current items are being selected inside of a JList, and whatever item is being selected will call a toString() method in another class and set this as the text for a JLabel. However, I just implemented moving text on my JLabel and every time an item in the JList is selected, the speed of the JLabel will increase. I would like it so the speed stays constant but i'm not sure how to do that.
The part of the code that controls the speed is
label.setLocation(label.getLocation().x+1, label.getLocation().y);
Any explanations would be very appreciated thank you
every time an item in the JList is selected, the speed of the JLabel will increase.
Because your code starts another Timer with each selection.
A Timer will keep running until you stop it. Since you never stop it you will have multiple Timers running at the same time.
You want to define your Timer outside of the ListSelectionListener.
Then the logic inside the listener becomes something like:
(if !timer.isRunning())
timer.start();
Or the question is do you even need the ListSelectionListener?
You could just start the Timer automatically when you class is created. Each time it fires the logic will get the selected value.
Of course with this approach you need to make sure the getSelectedValue() method doesn't return null (as no item will be selected when the GUI is first displayed).

JTable Duplicates Columns when "Hiding" and "Showing" them

I am currently using a JTable to display a few patient details. I have a JCheckBox, that when ticked adds a new column to the table and adds the data to the new column. When it is unticked, it should remove the column, sort of like adding extra filters to the table. However, when I tick the box again after unticking it, it duplicates the columns in the table.
I tried to use the fireTableStructureChanged() method, but it would never update the table and so the columns stay there even if the checkbox was unticked. But if I remove it, then when I untick the the checkbox it works, but then the duplication problem comes back. I pasted my actionPerformed() method for when the checkbox is ticked. The first image is of my table before I click my Checkbox. The second is when I click the Checkbox and the column is added. Lastly the third is when I untick the checkbox and tick again. Any help will be much appreciated.
#Override
public void actionPerformed(ActionEvent e)
{
if (gui.getChkCholesterol().isSelected() == true)
{
try {
gui.getRightTableModel().addColumn("Cholesterol");
newColumnIndeces.put("2093-3",gui.getRightTable().getColumnCount()-1);
gui.getRightTableModel().addColumn("Cholesterol Effective Date/Time");
newColumnIndeces.put("2093-3e",gui.getRightTable().getColumnCount()-1);
if(gui.getRightTable().getRowCount()>0)
{
int columnNumberValue = newColumnIndeces.get("2093-3");
int columnNumberDate = newColumnIndeces.get("2093-3e");
for (int i = 0; i<gui.getRightTable().getRowCount(); i++)
{
String value = op.getPatientObservationValue(gui.getRightTable().getValueAt(i,0).toString(),"2093-3");
String date = op.getPatientObservationEffectiveDate(gui.getRightTable().getValueAt(i,0).toString(),"2093-3");
gui.getRightTableModel().setValueAt(value, i, columnNumberValue);
gui.getRightTableModel().setValueAt(date, i, columnNumberDate);
}
}
} catch (ParseException parseException) {
parseException.printStackTrace();
}
setCellColourForTable();
timer.schedule(new RefreshTable(), 0, seconds);
}
else
{
gui.getRightTable().removeColumn(gui.getRightTable().getColumnModel().getColumn(newColumnIndeces.get("2093-3")));
gui.getRightTable().removeColumn(gui.getRightTable().getColumnModel().getColumn(newColumnIndeces.get("2093-3e")-1));
newColumnIndeces.remove("2093-3");
newColumnIndeces.remove("2093-3e");
timer.cancel();
timer = new Timer();
}
gui.getRightTableModel().fireTableStructureChanged();
}
You add a column to the model by using:
gui.getRightTableModel().addColumn("Cholesterol");
This will notify the view that the data has changed and the table will also be updated.
However, you remove the column from the table using:
gui.getRightTable().removeColumn(…);
This only removes the column from the "table view". The column has not been removed from the DefaultTableModel. So the next time you add a column it just gets added to the end of the DefaultTableModel.
So the solution is to remove the column from the DefaultTableModel, not the table.
Unfortunately there is no removeColumn(…) method.
However because your requirement is to only add/remove columns from the end of the DefaultTableModel you can use:
model.setColumnCount(model.getColumnCount() - 2);
which will effectively remove the last two columns from the model.
The other option is to not keep adding columns to the DefaultTableModel, but instead you can just add/remove TableColumns from the TableColumnModel directly. So this would mean the data for the Cholestoral column would always be in the model, but the view would just not display the columns.
So instead of using the model.addColumn(…) you would use the table.addColumn(…) method.

How to make JScrollPane not to scroll after adding rows in the beginning of table

I have window that shows log events in table.
For example, user is reading text in some row of table. When new logs come, they are added in the beginning of the table, and the row, the user was reading moves down. I have a requirement to prevent JScrollPane to scroll when new rows are added at the beginning of the table. I tried diffrent things but nothing helped. Can someone advice me how to implement this?
Thanks in advance!
Tricky task :-) The moving out happens because by default the scroll position is not adjusted in any way: adding rows above simply keeps the vertical scroll position which then points to a different row as before.
What's needed:
keep track of the visibleRect/last row before the insert
listen to model changes of type insert
calculate the new visible rect such that the old last row is scrolled back into the view
trigger the scroll
It's tricky because we need to listen to model changes. That listener is a neighbour of the modelListener registered internally by the table which updates itself on changes. So we need to be sure to act after the internal changes are done and at the same time use information before the internal update.
A dirty solution - depending on the usual sequence of swing listener notification which is last-added-first-notified (beware: DONT in production code! It will break easily, f.i. when changing the LAF) - is to gather the before-state when notified and adjust the scroll position wrapped in an invokeLater:
final DefaultTableModel model = new DefaultTableModel(20, 2);
for (int i = 0; i < model.getRowCount(); i++) {
model.setValueAt(i, i, 0);
}
final JTable table = new JTable(model);
Action action = new AbstractAction("add row at 0") {
#Override
public void actionPerformed(ActionEvent e) {
model.insertRow(0, new Object[] {});
}
};
JXFrame frame = wrapWithScrollingInFrame(table, "modify scroll");
TableModelListener l = new TableModelListener() {
#Override
public void tableChanged(TableModelEvent e) {
if (!TableUtilities.isInsert(e)) return;
final Rectangle r = table.getVisibleRect();
final int lastRow = table.rowAtPoint(
new Point(0, r.y + r.height - 5));
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
// assuming
// a) the insert was exactly 1 row
// b) was _before_ the old last row in view-coordinates
Rectangle cell = table.getCellRect(lastRow + 1, 0, true);
cell.translate(0, -r.height + cell.height);
cell.height = r.height;
table.scrollRectToVisible(cell);
}
});
}
};
model.addTableModelListener(l);
The appropriate way of getting the before-state cleanly depends on your exact context. You can keep track of the first/last row during the lifetime of the table, updating them in (one or all)
a ListSelectionListener to the row selectionModel (if the row the user is reading is always selected)
a ChangeListener to the vertical scrollBar
a ComponentListener to scrollPane's size changes

Nesting of timers

This post is related to my last post.The block of code changes the text to desired color after desired time.
however, now I want to change the color of a perticular word such that each letter gets the equal time.EG if "hello" have been given a time of 1000 milliseconds (have 5 letters) then 'h''e''l''l''o' each letter should get 1000/5 milliseconds i.e 200 milliseconds each.
I implemented swing timer for this :
public Reminder() {
a[0]=2000;
a[1]=1000;
a[2]=3000;
a[3]=5000;
a[4]=3000;
ActionListener actionListener = new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
point =point +arr[i].length();
i++;
doc.setCharacterAttributes(0,point+1, textpane.getStyle("Red"), true);
timer.setDelay(a[i]);
}
};
timer = new Timer(a[i], actionListener);
timer.setInitialDelay(0);
timer.start();
For this to happen, shoud I use another Timer inside the actionListener to give further timings to a perticular letter?Or should I first break the time by .length()
and then use the timer?I cannot decide a better way.Any ideas?
You should never need more than one timer. Since you know ahead of time exactly when everything should happen, just calculate those times, put them in a list (sorted by time) and execute each one.
public colorize(int offset, int length) {
long triggerTime[] = new long[length];
long startTime = System.currentTimeMillis();
for (int i=0; i<length; i++) {
triggerTime[i] = startTime + (1000*i)/length;
}
for (int i=0; i<length; i++) {
//just wait for the next time to occur
Thread.sleep(triggerTime[i]-System.currentTimeMillis());
doc.setCharacterAttributes(offset, i+1, textpane.getStyle("Red"), true);
}
}
This may strike you as very pedestrian because it does not use a TimerTask object, but it is effective, efficient, and easy to debug. You simplty call this method on whatever thread you want, and it occupies the entire thread, and the word will be colorized at a rate such it is completed in 1 second.
You could, if you don't have a thread handy, make a timer that calls this, but the only reason for that is to access a thread. The real point is: don't set up multiple timers, just make an array of time values. After one event is satisfied, set to delay until the next time. You never need multiple timers.
It would be a little cleaner if you made an object that represented the coloring of a character (or whatever action you want) and you put together a collection of these actions. Then sort the entire collection by time they are to go off. The loop above would walk through the collection, waiting until the time for the action arrives, and then executing it. Another advantage of this approach is that you could clear the collection and that would terminate the loop.
See the discussion of the overuse of timers on my website to understand why this is bad.

Categories

Resources