I'm using a JTable to display data. When a row is selected programmatically, it should (automatically) scroll that row into view so that it is the first row on top. For this I have the following method:
private static void scrollToSelectedRow(JTable table)
{
JViewport viewport = (JViewport) table.getParent();
Rectangle cellRectangle = table.getCellRect(table.getSelectedRow(), 0, true);
Rectangle visibleRectangle = viewport.getVisibleRect();
table.scrollRectToVisible(new Rectangle(cellRectangle.x, cellRectangle.y, (int) visibleRectangle.getWidth(), (int) visibleRectangle.getHeight()));
}
The problem is that calling this may or may not scroll correctly, e.g. the selected row may be on top or not in a non-deterministic way. In order to fix this, I tried calling the method twice with a delay in-between as well as repainting it:
public static void setSelectedRow(JTable table, int rowIndex, int columnIndex)
{
table.setRowSelectionInterval(rowIndex, columnIndex);
scrollToSelectedRow(table);
try
{
Thread.sleep(100);
} catch (InterruptedException exception)
{
exception.printStackTrace();
}
scrollToSelectedRow(table);
table.repaint();
}
This works a lot better but still sometimes glitches the table columns out since apparently it is not smart to halt the EDT while it is updating. The repaint() call appears to prevent the rows from "overlapping" in a similar fashion. The graphical glitches can be fixed easily by moving the mouse over the affected rows/columns though but it should not happen.
How can I gracefully perform the scrolling without waiting and without having graphical glitches?
Code looks reasonable. Normally the trick is to make sure the Rectangle width/height is the size of the viewport to force the scroll.
Two thoughts.
Get rid of the Thread.sleep(). all that will do is prevent the GUI from repainting itself for 100ms.
Try wrapping the scrollRectToVisible(...) in a SwingUtlities.invokeLater(...).
If point 2 doesn't help then post a proper [mcve] that demonstrates the problem.
Related
I need to show multi-line content in a JTable. The actual content is a collection of objects maintained in a custom model, which extends DefaultTableModel and generates cell content on the fly by overriding getValueAt().
In order to have multi-line content, I have implemented a custom TableCellRenderer:
private class MultiLineCellRenderer extends JTextArea implements TableCellRenderer {
public MultiLineCellRenderer() {
setLineWrap(true);
setWrapStyleWord(true);
setOpaque(true);
setBorder(new EmptyBorder(-1, 2, -1, 2));
setRows(1);
}
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
String text = value == null ? "" : value.toString();
if (!getText().equals(text)) {
setText(text);
int newHeight = table.getRowHeight() * getLineCount();
if (table.getRowHeight(row) != newHeight)
table.setRowHeight(row, newHeight);
}
if (isSelected) {
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(table.getBackground());
}
return this;
}
}
Now if I populate the table with a few hundred rows (column count is 2), I see the AWT worker thread starting to max out one CPU core. At the same time, memory consuption goes up from ~100 MB to ten times that amount and further. That happens even if the application is not actually doing anything (no data loaded in the background, no user interaction) and stops only when I clear the collection from which the table gets its content.
By commenting out select sections of code, I have identified these lines as the culprit:
int newHeight = table.getRowHeight() * getLineCount();
if (table.getRowHeight(row) != newHeight)
table.setRowHeight(row, newHeight);
If I comment out this section, all table rows have the same height (1 row of text), but memory consumption stays around ~100 MB.
If I replace these lines with a single call to table.setRowHeight(row, 32), i.e. with a fixed value, memory consumption starts going up again indefinitely.
The following modification works, at the expense of all rows having the same height:
int newHeight = getRowHeight() * getLineCount();
if (table.getRowHeight() < newHeight)
table.setRowHeight(newHeight);
Bottom line: it seems setting individual row heights in a JTable creates a massive memory leak. Am I doing something wrong, or have I encountered an actual bug? In the latter case, are there any known fixes/workarounds?
Setting the row height triggers a redraw, which in turn triggers another call to the renderer. Therefore, it is important to set the row height only if it is different from the current one, in order to avoid an endles loop. This is what happens when you call setRowHeight() unconditionally, even with a fixed value.
A second issue is that each row comprises two cells, which may have different heights. The code above will set the row height to match the cell being rendered right now. When the other cell of that row gets rendered and has a different height, the row height gets changed again. That will trigger a redraw, also of the first column in the row. Since that will result in another height change, there’s the infine loop again.
Proof: the following code fixes this:
int newHeight = table.getRowHeight() * getLineCount();
if (table.getRowHeight(row) < newHeight)
table.setRowHeight(row, newHeight);
Now the row height will only increase, but never decrease, thus breaking the infinite loop. Side effect: if the cell contents change and now occupy fewer rows than before, the row height will not change to reflect this.
Bottom line: rendering a JTable with multiline cells is non-trivial, and SO has quite a few buggy examples. The only working example I found (thanks to another SO post) is at https://www.javaspecialists.eu/archive/Issue106.html.
Their solution is to store cell heights internally in the renderer (though this could also be done in the table model, whichever works best for your implementation). When calculating the height of a cell, store it, then get the maximum height of any cell in the row and use that. Also be sure to set the row height only if it differs from the current one.
That has fixed the memory leak/processor consumption issue, in addition to finally giving me a working example of how to calculate cell height properly.
I'm writing a plugin for a miscropy program and have problems with the repaint() method.
short question:
Is there any way to get informed as soon as the repaint of a JPanel was done or synchronize the code with it?
detailed version:
My program can plot a set of data in a xy-chart to a JPanel and show it using jfree.chart; In another part of the programm I have many datasets (~100) that I want to plot and save as images. I've also found a solution, but I really don't like it. The Problem can be reduced to a notification about the paint status of a JPanel.
In the part that shall save all images I have this solution:
PlotSpectrum spectrumWindow = getTheWindow(); //pseudo code...
// some stuff
ti = storage.getImage(channel, slice, frame, position);
spectrumWindow.plotData(false, andor.captureSpectrum(ti.pix), wave,
centerWave, fineGrating, exposureTime,
slitWidth, substractBackground);
spectrumWindow.repaint(); // probably not necessary
sleep(100); // this annoys me...
spectrumWindow.savePlot(path, true, config, null);
spectrumWindow is a JPanel that is also displayed in another window and it all works fine.
BUT I really don't like that sleep(100) in there... without it I'm asking for a repaint but it isn't done till I try to save a "snapshot" of (thats what savePlot is doing...). I know, other Thread and these damn synchronization problems...
With the sleeping I'm just making it unnecessary slow and if I wait not long enough the images are not completly drawn (eg lower half missing)
Is there any way to get informed as soon as the repaint was done? I probably would be also fine with a Listener, better would be a solution with a monitor or sth comparable or a method that is repainting NOW (doesn't exists as far I know?)
The main GUI (include the JPanel spectrumWindow) and the earlier pasted code are running in different Threads.
The probably also important parts of my code are following here. Please excuse if some brackets aren't matching or some variables aren't declared, I removed very much code.
thanks
schetefan24
class PlotSpectrum extends ApplicationFrame // that extends JFrame
{
public void plotData(boolean backgroundGiven, int[] spect, double[] wave_,
double centerWave, boolean fineGrating_, double exposureTime,
double slitWidth, boolean substractBackground)
{
//process data and store internally
replot();
}
private void replot()
{
XYSeries series = new XYSeries("Spectrum");
//add data to series
XYSeriesCollection collection = new XYSeriesCollection(series);
//setting up some labels and such stuff...
JFreeChart chart = ChartFactory.createXYLineChart(
title,
"Wavelength [nm]",
yLabel,
collection,
PlotOrientation.VERTICAL,
false,
false,
false
);
dataPanel.add(new ChartPanel(chart)); // this is contained in a Frame
}
public void savePlot(String path, boolean overWriteAll, HashMap<String,String> config, int[][] addData)
{
File output = new File(path);
//some more stuff, ask overwrite etc
if(image)
{
BufferedImage im = createImage();
String extension = path.substring(path.lastIndexOf(".")+1, path.length());
ImageIO.write(im, extension, output);
} else {
//that is an textexport, works fine
}
}
public BufferedImage createImage()
{
JPanel panel = (JPanel) flipChart.getSelectedComponent();
int w = panel.getWidth();
int h = panel.getHeight();
BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g = bi.createGraphics();
panel.paint(g);
return bi;
}
}
that I want to plot and save as images.
add the data to a non visible panel.
create a BufferedImage of the panel
create an ImageIcon using the Image from above
update a JLabel (that has already been added to the frame) using the setIcon(...) method
the above step should generate a PropertyChange event when the Icon changes. You can use a ProperChangeListener to listen for this event. When you receive the event you can repeat steps 1 - 4.
Check out Screen Image. It will help you create an image of a non-visible component.
Note, you don't really need steps 4-5. I just added them so you have a visual of the plots as they are being processed. If you don't want the visual then maybe you just display text on a JLabel indicating which plot is currently being converted.
I am writing a small POS application, which shows a JTable with ticket information inside its cells. CellRenderer is a class which extends JPanel and implements TableCellRenderer, and contains a few JTextFields showing basic information (quantity, descripcion, price).
Also, I have another class extending JPanel and implementing TableCellEditor, which is used as CellEditor. This class contains more JTextFields and also a few jButtons.
What I need is simple: when I edit any cell by clicking with the mouse (or touching the screen, which is, as far as I know, the same event), dynamically increase the height of the cell I'm going to edit, so it can show all the components inside the editor. And when I finish editing, return cell height to its previous value.
Any idea about doing it?
Thanks in advance. :-)
CellRenderer is a class which extends JPanel and implements TableCellRenderer, and contains a few JTextFields showing basic information (quantity, descripcion, price). Also, I have another class extending JPanel and implementing TableCellEditor, which is used as CellEditor. This class contains more JTextFields and also a few jButtons.
better could be to create popup window (JDialog) based on event from JPopupMenu,
Dynamically Increase JTable row height when editing, and decrease when finish edit
don't confused users and wrong concept could be caused by jumping JTables row on the screen
What I need is simple: when I edit any cell by clicking with the mouse (or touching the screen, which is, as far as I know, the same event), dynamically increase the height of the cell I'm going to edit, so it can show all the components inside the editor. And when I finish editing, return cell height to its previous value.
don't do that, but have to override, is possible by
DefaultCellEditor#setClickCountToStart(int) for TableCellEditor
start, stop and cancelEdit for CellEditor
have to notify or re_Layout JTable, the same on stop and cancelEdit
Not an answer to how-to-adjust-rowHeights, but for an alternative mentioned in my comment: "oversize" the editorComponent only instead of updating the complete rowHeight (which I think would be too irritating to users, but up to you to decide, of course :)
// fake a bigger editing component
JTextField oversized = new JTextField() {
#Override
public Dimension getPreferredSize() {
Dimension dim = super.getPreferredSize();
dim.height *= 5;
return dim;
}
};
TableCellEditor editor = new DefaultCellEditor(oversized);
JTable table = new JTable(new AncientSwingTeam()) {
#Override
public boolean editCellAt(int row, int column, EventObject e) {
boolean result = super.editCellAt(row, column, e);
if (result) {
// adjust cell bounds of the editing component
Rectangle bounds = getEditorComponent().getBounds();
bounds.height = getEditorComponent().getPreferredSize().height;
getEditorComponent().setBounds(bounds);
}
return result;
}
#Override
public void removeEditor() {
int editingColumn = getEditingColumn();
super.removeEditor();
if (editingColumn >= 0) {
// cleanup
repaint();
}
}
};
table.getColumnModel().getColumn(0).setCellEditor(editor);
Didn't try it, but I would say implementing MouseListener's mouseClicked() method is the way to go. Keep track of whether the cells height was already increased, and change the height accordingly.
Since MouseListener is an interface, CellRenderer could implement this interface too, keeping all cell-behavior in one class.
I have a question regarding the callback speed of the mouseDragged message of the MouseMotionListener in Java Swing. This post is sort of related but it's not entirely the same so I started a question of my own.
I'm making a small in-house application with no eye on commercial distribution that is basically a digitalized TCG (Trading Card Game) emulator. For any of you familiar with MtG (Magic the Gathering), you might've heard from such a similar program. I'm trying to create something that looks sort of like this, but less fancy.
My GUI consists of a JFrame with menu and then some panels containing various buttons and labels, but I'll only go over the relevent parts to explain my problem.
In essence, I'm using a vertical split JSplitPane with a JPanel on the left, with in that a JScrollPane with a JList in it, which represents at any time the cards in your hand that you can play. On the right side of the split, I have a JLayeredPane with a background image in the DEFAULT_LAYER (subclass of JPanel that overrides the draw function to add an image) and, on various layers above the PALETTE_LAYER, I display the cards that are in play (gathered in an ArrayList) by means of custom CardPanels (another subclass of JPanel that illustrates a card). The entire JLayeredPane is thus a representation of the table in front of you with all the cards you've already played.
I first started by adding a MouseListener and a MouseMotionListener to the JLayeredPane to pick up events, allowing me to register a mouse press, check if this was above a card, then use the mouse dragged event to move the card around and finally mouse release to place it back . This all works perfectly fine and if I add logging information I can see the mouseDragged callback function is called often, allowing for a visually fast dragging motion without lag.
Today I decided to add functionality to allow the user to drag a card from his hand to the "table" (instead of double clicking on the card in the JList), so I added the appropriate listeners to the JList along with filling in some functions like MousePressed and MouseReleased. On a mouse press, I check what card from the list was clicked, I lock the list, create a custom CardPanel (but don't add it anywhere yet, I just allocate and initiate it!) and set a flag. In mouse dragged, I check if this flag is set. If it is, I check where the cursor is. If it is anywhere above the JLayeredPane, I add the CardPanel to the DRAG_LAYER and set another flag. If this second flag is set in successive calls to mouse dragged, I don't add the panel again but I just change the location. This functionality is practically the same as the one in my previous mouse dragged callback. On mouse release, I unlock the list and add the CardPanel on the correct layer in the JLayeredPane.
Everything is working as intended so I'm pretty sure my code is okay, but there is just one slight issue:
When dragging a card from the list to the layered pane (instead of from the layered pane to the layered pane), I notice the mouseDragged callback is called at a pretty low frequency by the JList (approx 10 times per second), introducing some visually disturbing lag (compared to approx 30 times per second in the first case of dragging).
I'm going to add some code snippets as to clarify my problem but I'm afraid adding all the code to allow you to run it yourself would be serious overkill.
The main question in this post is: does anybody know why the mouseDragged is called faster by one MouseMotionListener than by another MouseMotionListener? The listener to the JLayeredPane component makes fast successive calls, the listener to the JList calls significantly slower.
Note: I'm developing in Netbeans and I'm using the built-in graphical Swing Interface Builder. I'm using a JFrame form as my main class.
public class MyFrame extends JFrame{
...
protected JLayeredPane layeredPane;
protected JList cardsInHandList;
...
...
protected ArrayList<String> cardsInHand;
...
private void attachListeners(){
layeredPane.addMouseListener(new MouseAdapter(){
public void MousePressed(MouseEvent e){
// set a flag, start a drag
}
public void MouseReleased(MouseEvent e){
// unset a flag, stop a drag
}
});
layeredPane.addMouseMotionListener(new MouseMotionAdapter(){
public void MouseDragged(MouseEvent e){
// drag the card around
// gets called a lot!
// actual code:
if (e.getButton() == MouseEvent.BUTTON1) {
if (!dragging) return; // the flag
int x = e.getX() - 10;
int y = e.getY() - 10;
// snap to grid
x /= GRIDX;
x *= GRIDX;
y /= GRIDY;
y *= GRIDY;
// redraw the card at its new location
draggedCard.setLocation(x, y);
}
}
});
cardsInHandList.addMouseListener(new MouseAdapter(){
public void MousePressed(MouseEvent e){
// set a flag, start a drag
}
public void MouseReleased(MouseEvent e){
// unset a flag, stop a drag
}
});
cardsInHandList.addMouseMotionListener(new MouseMotionAdapter(){
public void MouseDragged(MouseEvent evt){
// check cursor location, drag if within bounds of layeredPane
// gets called a whole lot less!! _Why?_
// actual code:
if (!draggingFromHand) return; // the flag
// check location of cursor with own method (contains() didn't work for me)
if (isCursorAtPointAboveLayeredPane(evt.getLocationOnScreen())) {
// calculate where and snap to grid
int x = (int) (evt.getLocationOnScreen().getX() - layeredPane.getLocationOnScreen().getX())-10;
int y = (int) (evt.getLocationOnScreen().getY() - layeredPane.getLocationOnScreen().getY())-10;
// snap to grid
x /= GRIDX;
x *= GRIDX;
y /= GRIDY;
y *= GRIDY;
if(!draggingFromHandCardPanelAdded){
layeredPane.add(draggingFromHandCardPanel, JLayeredPane.DRAG_LAYER);
draggingFromHandCardPanelAdded = true;
} else {
draggingFromHandCardPanel.setLocation(x,y);
}
}
}
});
}
I'll try to build a short runnable example reproducing the problem and then attach the code somewhere but right now I got to skoot.
Thanks in advance
PS: I am aware that there is another way to drag in Java, involving TransferHandlers and all that but it just seems like too much hassle and it isn't an actual answer to my question of how come the one callback seems to be called more than the other, so please don't tell me to use that instead.
Once you drag outside the list, Java start generating synthetic mouse events for the list, which might be the cause. See the javadoc for JComponent#setAutoscrolls(boolean).
You might get better results using a global event listener, see
http://tips4java.wordpress.com/2009/08/30/global-event-listeners/
Got a problem with ComponentListener. I'm using it to check if a certain component is resized, and to update some stuff if it is.
The code looks like this though this probably won't be much use to you:
#Override
public void componentResized(ComponentEvent e) {
// Graph resized, reposition slice nodes
Component c = e.getComponent();
if (graphHeight > 0) {
if (c == graph) {
int offset = (c.getHeight() - graphHeight) / 2;
if (offset != 0) {
try {
controller.shiftSlice1NodesY(offset);
} catch (GraphIntegrityException e1) {
logger.error(e1.getMessage(), e1);
}
graphHeight = c.getHeight();
}
}
} else {
graphHeight = c.getHeight();
}
}
For one section of my code I need to disable this listener. That looks like this:
graph.removeComponentListener(this);
controller.parseFile(filename);
graph.addComponentListener(this);
Everything goes smoothly until I add the component listener again. At this point the componentResized method is called about 20 times. It has possibly buffered the resize events from what I can see. Anyone know what's going on here?
Looks like you are trying to modify a component off the Event Dispatch Thread (EDT). That's not even a good idea in AWT, let alone Swing.
Other than get all the AWT/Swing stuff on the right thread, part of the fix should be to get the listener check state to see if it should execute its body, rather than attempting to remove and add the listener.