Scroll Lock with JTable and JScroll Panes - java

I have a JTable which is created with the use of an EventTableModel and is in a JScrollPane. The EventTableModel takes live updates from an eventList and displays the result in the table. As new results come into the table and the new piece of information is displayed at the top of the table.
However, what I want to be able to do is freeze the table to show what is currently displayed when I press a button called 'Lock Table'. This button should have the same effect as the eclipse console 'Scroll Lock', therefore as new items appear the current items should remain on the screen and not be pushed off as new items appear. But new items should still be added just not automatically scrolled to.
Does anyone know how I can try achieve this functionality. So that as update come in, the data that is on the table is not forced off screen, therefore focuses remain on the current data when the check box is pressed.
Thanks for any help.
Michael

Basic procedure (for inserting above the current display area)
install a TableModelListener on the table's model
on enable lock: note the number of rows below the current visible rectangle
on receiving inserts while locked, scroll so that the number of rows below are kept constant
some working code (using JXTable, as it has convenience method for scrolling, for a core table simply do the calculations yourself :-)
public static class ScrollLock {
private JXTable table;
private boolean blocked;
private int rowsBelow;
public ScrollLock(JXTable table) {
this.table = table;
table.getModel().addTableModelListener(getTableModelListener());
}
private TableModelListener getTableModelListener() {
TableModelListener l = new TableModelListener() {
#Override
public void tableChanged(TableModelEvent e) {
if (!blocked) return;
if (e.getType() == TableModelEvent.INSERT) {
updateInsert(e.getFirstRow(), e.getLastRow());
}
}
};
return l;
}
protected void updateInsert(int firstRow, int lastRow) {
// PENDING: assumption is that insert always above
// need additional logic for other cases
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Rectangle r = table.getVisibleRect();
int row =table.rowAtPoint(new Point(0, r.y + r.height));
int lastVisible = table.getRowCount() - rowsBelow;
table.scrollRowToVisible(lastVisible);
}
});
}
public void block() {
Rectangle viewRect = table.getVisibleRect();
int lastVisibleRow = table.rowAtPoint(new Point(0, viewRect.y + viewRect.height));
rowsBelow = table.getRowCount() - lastVisibleRow;
blocked = true;
}
public void unblock() {
blocked = false;
rowsBelow = -1;
}
}

Related

Mouse Event not working when clicking JTable

I created a simple mouse event. When the user clicks the JTable it will fetch the records in the JTable and display them in the JTextField. In this case I am trying to display the ID from the Table into the Text Field.
public void fetchRec() {
xtable.addMouseListener(new MouseAdapter() {
public void rowClicked(MouseEvent evt){
xtable =(JTable) evt.getSource();
int row = xtable.rowAtPoint( evt.getPoint() );
int column = xtable.columnAtPoint( evt.getPoint() );
String s=xtable.getModel().getValueAt(row, column)+"";
idLabelField.setText(s);
}
});
}
I am calling the method here but it keeps telling me that rowClicked method is unused. I don't understand how its unused? Everything else I am calling is working except this.
public void bookDimensions() throws Exception {
addTextLabels();
addTextFields();
addPanelButtons();
addRecord();
addTable();
fetchRec();
}
Turn on cell selection and listen to the selection model instead of mouse events. See java: how to select only one cell in a jtable and not the whole row

Highlight changes in a JTable after fireTableDataChanged()

I am using a JTable whose TableModel is periodically updated through fireTableDataChanged(). These changes are usually pretty small, such as a single row added or modified, however I can't predict where it will happen.
Is there a way to know which rows have been added or modified on a fireTableDataChanged() ? I would like to highlight these rows so the user will know as well.
First off, you must setup your context as appropriate for Swing: the TableModel must have enough knowledge/control about itself to fully comply to its notification contract. That is it must fire row-/cellUpdated or rowsInserted whenever such a change happens.
Then the basic approach to highlight changes (for a certain time) in the JTable is to
implement a custom renderer that decorates cells which are in some storage
configure the table with the custom renderer
listen to changes of the model
add the changeEvents (or a custom object with its relevant properties) to the storage that the renderer knows about
use timers to remove the change markers after some time
SwingX simplifies (biased me :-) the rendering part by providing Highlighters and HighlightPredicates: the former do custom visual decorations when the latter decides they should be turned on. The above approach would be adjusted to
configure the table with highlighters for visual decoration
listen to changes in the model
add the changed cell to a custom HighlightPredicate and configure the Highlighter with it
use timers to remove the change markers after some time
Below is some code, the management of the timers/predicates factored into a class called ChangeDecorator: it keeps one Highlighter for decorating updated cells and one for decorating inserted rows (Note: this is an example, obviously the logic must be extended to cover updated rows :) It's fed by a modelListener with changes and updates the predicates as needed.
JXTable table = new JXTable(model);
final ChangeDecorator controller = new ChangeDecorator();
table.addHighlighter(controller.getChangeHighlighter());
TableModelListener l = new TableModelListener() {
#Override
public void tableChanged(TableModelEvent e) {
if (TableUtilities.isUpdate(e)) {
Change change = new Change(e.getFirstRow(), e.getColumn());
controller.addChange(change);
} else if (TableUtilities.isInsert(e)) {
Change change = new Change(e.getFirstRow());
controller.addChange(change);
}
}
};
model.addTableModelListener(l);
/**
* Manages the Highlighters for inserted rows/updated cells.
*/
public static class ChangeDecorator {
private List<Change> changes;
private AbstractHighlighter update;
private AbstractHighlighter insert;
private Highlighter compound;
public ChangeDecorator() {
changes = new ArrayList<>();
}
public Highlighter getChangeHighlighter() {
if (compound == null) {
update = new ColorHighlighter(new ChangePredicate(changes, true),
Color.YELLOW, null);
insert = new ColorHighlighter(new ChangePredicate(changes, false),
Color.GREEN, null);
compound = new CompoundHighlighter(update, insert);
}
return compound;
}
public void addChange(Change change) {
startTimer(change, change.isCell ? update : insert);
}
private void startTimer(final Change change, final AbstractHighlighter hl) {
changes.add(change);
hl.setHighlightPredicate(new ChangePredicate(changes, change.isCell));
ActionListener l = new ActionListener() {
boolean done;
#Override
public void actionPerformed(ActionEvent e) {
if (!done) {
done = true;
return;
}
((Timer) e.getSource()).stop();
changes.remove(change);
hl.setHighlightPredicate(new ChangePredicate(changes, change.isCell));
}
};
Timer timer = new Timer(2000, l);
timer.setInitialDelay(100);
timer.start();
}
}
/**
* A predicate enables highlighting a cell if it
* contains a change for that cell.
*/
public static class ChangePredicate implements HighlightPredicate {
private List<Change> changes;
private boolean matchCell;
public ChangePredicate(List<Change> changes, boolean matchCell) {
this.changes = new ArrayList(changes);
this.matchCell = matchCell;
}
#Override
public boolean isHighlighted(Component renderer,
ComponentAdapter adapter) {
return changes.contains(createChange(adapter));
}
private Change createChange(ComponentAdapter adapter) {
int modelRow = adapter.convertRowIndexToModel(adapter.row);
if (matchCell) {
int modelColumn =
adapter.convertColumnIndexToModel(adapter.column);;
return new Change(modelRow, modelColumn);
}
return new Change(modelRow);
}
}
/**
* A crude class encapsulating a cell change.
*
*/
public static class Change {
int row;
int column;
boolean isCell;
public Change(int row) {
this(row, -1, false);
}
public Change(int row, int col) {
this(row, col, true);
}
private Change(int row, int col, boolean update) {
this.row = row;
this.column = col;
this.isCell = update;
}
#Override
public boolean equals(Object obj) {
if (!(obj instanceof Change)) return false;
Change other = (Change) obj;
return row == other.row && column == other.column && isCell == other.isCell;
}
}

My internal frames don't earn focus after opening... (Java)

I have a menu with items that open internal frames, but every time I need to click twice in the frame. One time to give focus to the Int.frame and the second time to actually do something (give focus to a textfield).
So, here is my question: It's possible to automatic give focus to the Int.Frame?
Code of my main screen:
public final class principal extends javax.swing.JFrame {
viewCity city = new viewCity();
public principal() {
initComponents();
myListeners();
setLocationRelativeTo(null);
}
public void myListeners() {
menuCity.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
menuCityClicked(e);
}
});
}
public void central(JInternalFrame window1) {
int lDesk = panelPrincipal.getWidth();
int aDesk = panelPrincipal.getHeight();
int lIFrame = window1.getWidth();
int aIFrame = window1.getHeight();
window1.setLocation(lDesk / 2 - lIFrame / 2, aDesk / 2 - aIFrame / 2);
}
private void menuCityClicked(MouseEvent e) {
if (!city.isVisible()) {
panelPrincipal.add(city);
central(city);
city.requestFocus(); // Nothing
city.requestFocusInWindow(); // Nothing
city.setVisible(true);
city.requestFocus(); // Nothing
city.requestFocusInWindow(); // Nothing
}
}}
No matter what, the menu will always keep the focus. For example, click in your browser's menu, and you will keep the focus, by moving the cursor you will open other menus without need to click.
By putting the properties "selection model" to null works, but give me nullpointerexception.
Ok, the problem is with the jMenu, but with jMenuItem Works fine, so... I'm using

How many times is getListCellRendererComponent called?

I'm trying to understand how getListCellRendererComponent method works but I don't get it. I made a separate class that extends BasicComboBoxRenderer and I added a counter which is printed every time getListCellRendererComponent is called. I then run a test class with a main method that shows a frame with just a JComboBox that uses my custom renderer class. This combobox has 3 items in total and I've set setMaximumRowCount(2) so it only shows 2 of them.
When I first run the program and the frame with the combobox appears, the counter informs that getListCellRendererComponent is called 6 times.
When the frame looses focus (when I click on my desktop for example) the method executes 1 time.
When the frame regains focus (click back on my frame) the method executes 1 time.
When I click on the arrow button and the drop-down list appears for the FIRST time, counter says that the method executes 8 times.
When I click again on the arrow button and the list disappears, the method is called 1 time (this happens always).
When I click on the arrow button AFTER the first time, the method is called 5 times.
When I click on the scrollbar button to go up or down, the method executes 1 time.
When I move the cursor on a not-selected item of the list, the method executes 2 times and after a second 1 more time (this is the most absurd)
When I click on an item of the list the method executes 4 times.
At first I thought that this method will be executed as many times as the number of the items in the list (plus one more that appears on the combobox display area).
But I can only understand one or two cases from the above, for example when I click the scrollbar buttons and the method executes 1 time, probably because a new item is rendered. The rest of them seem insane...
I would expect no less then n + 1 iterations of the renderer to be called at any one time.
The component needs to
Figure out the best size for the contents. This can be achieved by using a prototype value or, if none is specified, iterating through all the items to find the max bounds (thats 3 times)
Render the selected item if one exists +1 times
Render the list if the popup is visible +3 times
Possibly look for a tooltip
= a possible 7 iterations
When losing focus, the component needs to render the selected item +1
When regaining focus, the component will try a render te selected item again +1
When the popup is displayed, see the first part
The second time probably indicates that the component has cached the result of the first popup action (it's possible that the component is invalidating its internal cache between focus events)
Each time you change the view of the scrollpane, it needs to render any items not previously shown on the screen, this is done for optimisation reasons (imagine a lst with 100s of items, rendering all of them is a waste of time, hence the inclusion of the prototype value)
Mouse actions can be triggering a series of different actions, mouse in, mouse out, mouse move. Most likly these are to do with the tooltip manager and component trying to determine if a tooltip is available
Try setting a prototype value & see if that changes the number of iterations when the component displays its popup
I think that you forgot to describe how and which Object(s) you create or re_create in the Renderer,
you forgot to sent here your view about Renderer in the SSCCE form
then everything is in the academic level and too hard to write something about your Renderer ...
Renderer react to the every Mouse and Key events
plain Renderer with output from all important methods to the System.out.println("yyyy")
.
import java.awt.Component;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.plaf.basic.BasicComboBoxRenderer;
public class ComboBoxHoverOver {
private JComboBox combo = new JComboBox();
public ComboBoxHoverOver() {
combo.setPrototypeDisplayValue("XXXXXXXXXXXXXXXXXXXXXX");
combo.setRenderer(new ComboToolTipRenderer(combo));
combo.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
System.out.println("itemStateChanged");
}
});
combo.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("actionPerformed");
}
});
combo.addItem("");
combo.addItem("Long text 4");
combo.addItem("Long text 3");
combo.addItem("Long text 2");
combo.addItem("Long text 1");
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(combo);
f.pack();
f.setVisible(true);
}
private class ComboToolTipRenderer extends BasicComboBoxRenderer {
private static final long serialVersionUID = 1L;
private JComboBox combo;
private JList comboList;
ComboToolTipRenderer(JComboBox combo) {
this.combo = combo;
}
#Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
System.out.println(value + ", " + index + ", " + isSelected + ", " + cellHasFocus);
if (comboList == null) {
comboList = list;
KeyAdapter listener = new KeyAdapter() {
#Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyCode() == KeyEvent.VK_UP) {
int x = 5;
int y = comboList.indexToLocation(comboList.getSelectedIndex()).y;
System.out.println("keyReleased " + comboList.getSelectedIndex());
}
}
};
combo.addKeyListener(listener);
combo.getEditor().getEditorComponent().addKeyListener(listener);
comboList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
JList list = (JList) e.getSource();
int item = list.getSelectedIndex();
if (item > -1) {
String string = list.getSelectedValue().toString();
System.out.println("valueChanged " + list.getSelectedValue().toString());
}
}
}
});
}
if (isSelected) {
System.out.println("isSelected " + value.toString());
}
return this;
}
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ComboBoxHoverOver comboBoxHoverOver = new ComboBoxHoverOver();
}
});
}
}

Populating a JTable does not change the contents of the selected cell, and invokes editing on the cell; how can I work around this?

I have an application which uses JTables to display data, and the cells are editable so that the user can change the data. The user can also revert the changes, or load data from an external source. However, if the user reverts/loads the data using a keyboard shortcut, so that mouse focus is not taken away from the table, the currently selected cell does not get reverted. In fact, after the refresh, the cell goes into edit mode! Then when the user navigates away from this cell, a change event is triggered, so the old value gets committed back to the data store.
I have a short example program that demonstrates this problem. It shows a table in which every cell displays the value 0. There is also a File menu with a single menu item called "Increment", which has a keyboard shortcut of Ctrl-I. Each time the Increment command is invoked, it increments the number displayed in all of the cells. To see the problem, do the following:
Compile and run the program
Hit Ctrl-I a bunch of times to invoke the Increment command. Notice that the cell values increment each time.
Click a cell.
Hit Ctrl-I a bunch of times to invoke the Increment command. Notice that all cell values increment except the one that is selected.
I have tried various methods to remove the selection from the table before refreshing it, to no avail. Neither
table.editCellAt(-1, -1);
nor
table.getSelectionModel().clearSelection();
worked, for example.
Here is the sample program:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
public class TableBug extends JFrame {
private static final int ROW_COUNT = 3;
private static final int COL_COUNT = 3;
private int mDataValue = 0;
private DefaultTableModel mTableModel;
// Constructor
public TableBug() {
setTitle("TableBug");
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
// Create table model and table
mTableModel = new DefaultTableModel();
for (int col = 0; col < COL_COUNT; col++) {
mTableModel.addColumn("Value");
}
JTable table = new JTable(mTableModel);
setUpTable(table);
refresh();
// Create menu bar
int keyMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
JMenu fileMenu = new JMenu("File");
JMenuItem incrementMenuItem = new JMenuItem("Increment");
incrementMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, keyMask));
incrementMenuItem.addActionListener(new AbstractAction() {
public void actionPerformed(ActionEvent e) {
doIncrement();
}
});
fileMenu.add(incrementMenuItem);
JMenuBar mainMenuBar = new JMenuBar();
mainMenuBar.add(fileMenu);
// Populate GUI
setJMenuBar(mainMenuBar);
add(new JScrollPane(table), BorderLayout.CENTER);
// Display window
pack();
setVisible(true);
}
// Configures the table
private void setUpTable(JTable table) {
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.getTableHeader().setReorderingAllowed(false);
table.getTableHeader().setResizingAllowed(false);
table.setRowSelectionAllowed(false);
}
// Populates the table
private void refresh() {
mTableModel.setRowCount(ROW_COUNT);
for (int col = 0; col < COL_COUNT; col++) {
for (int row = 0; row < ROW_COUNT; row++) {
mTableModel.setValueAt(mDataValue, row, col);
}
}
}
// Handles the Increment menu item
public void doIncrement() {
mDataValue++;
refresh();
}
// Main program
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TableBug();
}
});
}
}
In your refresh function, check if the table is being edited. If it is, get the row and column that are being edited and stop the cell editing.
private void refresh() {
if (table.isEditing()) {
int row = table.getEditingRow();
int column = table.getEditingColumn();
table.getCellEditor(row, column).stopCellEditing();
}
...
To do this, you'll need to make your table variable accessible (make it a class variable).

Categories

Resources