I have a JTable in a JScrollPane, and the table gets new rows of data every once in a while. Eventually there are more rows of data than can be displayed at once and so the ScrollPane kicks in. I want the Scroll Pane to jump to the bottom (to its maximum value) every time new data is added, so I wrote this, which is called right after the new row is added:
jScrollPane1.getVerticalScrollBar().setValue(jScrollPane1.getVerticalScrollBar().getMaximum());
It works quite well, but there is a problem- It doesn't scroll to the complete bottom. It always leaves out one row of the table that you need to manually scroll down to reach the complete bottom.
My suspicion is that the new row of data is somehow added only after the method is over (it is activated after pressing a JButton and activating an actionPerformed kind of method).
How to fix this?
you need to queue it at the end of current processing events using SwingUtilities.invokeLater;
it would look like this:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
jScrollPane1.getVerticalScrollBar().setValue(jScrollPane1.getVerticalScrollBar().getMaximum());
}
}
);
otherwise will run during the current event and will happen before any actual update.
remember to make them variable final to pass them into an anonymous inner class
Related
I have a JXTreeTable with an add button. The button works as expected: it adds a new node to the end, with values that (for the purpose of testing) are hard-coded into my program. The line:
modelSupport.fireChildAdded(new TreePath(root), noOfChildren-1, dataNode);
tells the JXTreeTable to recognise the update made to the TreeTableModel, and to refresh accordingly.
I then execute:
treeTable.changeSelection(treeTable.getRowCount()-1, 0, false, false);
to automatically select this new node. This works as expected, BUT I also expected it to automatically scroll to bring this new node into sight, which it does not. Instead, it scrolls to show only the penultimate node, leaving the new node hidden just slightly beyond view.
I've also tried using tricks to programmatically scroll to the final record. However, this code:
JScrollBar vertical = scrollPane.getVerticalScrollBar();
vertical.setValue(vertical.getMaximum());
revealed the same problem.
Bizarrely, if I attach either of the above two blocks of code to a "Scroll to Bottom" button, and press this button manually after adding the new node, it all works fine! Please can anyone isolate the cause of this bug, or offer a fix/workaround?
JXTreeTable should have something like:
JXTreeTable.scrollPathToVisible(...)
Does this work for you?
EDIT:
I assume it works with your Button, because the Scrollpane is already "rendered/painted" with the new added node (when you click the button).based on this, the internal max/min calculation (e.g in scrollpane) can differ.
For anyone else having this problem, it seems to be caused by a peculiarity with the order in which GUI actions are executed (due in part to the need to be executed on the Dispatch thread).
From memory, this is how I solved it. (I haven't looked at the code in several months, so this code might not work perfectly, but you get the idea.)
Wrap the "vertical.setValue(vertical.getMaximum());" code in a SwingUtilities.invokeLater() method call. In other words:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JScrollBar vertical = scrollPane.getVerticalScrollBar();
vertical.setValue(vertical.getMaximum());
}
});
I'm not understanding Java GUI's as well as I thought. In my paint method for a frame, I'd like to wipe all of the current buttons, and add new ones. (The overall goal is to have an interface where the user can see characters and click on the buttons to download documents related to the character. Since every character is different, when the user selects a new user from my list, a new set of documents and buttons will be available to them.)
This is a test frame that I just wrote that shows where things go sideways. It has the similar paradigms that I use in my actual program, without too much clutter:
public class GUITest extends JFrame
{
/**
* #param args
*/
public static void main(String[] args)
{
Container gui_test = new GUITest();
}
private JComponent content = null;
public GUITest()
{
super();
setVisible(true);
}
public void paint(Graphics g)
{
this.removeAll();
content = new JPanel();
JComponent test_button = new JButton("New Button 1");
JComponent button = new JButton("New Button 2");
content.add(button);
content.add(test_button);
this.add(content);
super.paint(g);
}
}
Without the call to removeAll(), buttons will continue to be thrown on top of the JPanel, but with the call, nothing shows up. I don't know why this is, as I'm adding the components appropriately, right?
Edit
Got it, let me give you a more detailed breakdown. A client is navigating my program by looking at a list of characters in a game on a west panel. They can select a row from the list which will show char details on the east panel. The details are an image and description. Recently, I added relevant documents for that particular char, which will show on the bottom of the east panel. I created key listener's, so the client can quickly view the document by pressing a num key, but I also want to give them the ability to click on the button to launch a pdf view and see the contents of the document.
Since every char has different related docs and different number of docs, I repainted the buttons every time, to reflect the amount of related docs and the appropriate titles for the docs. This is where the repaint is acting strange. You gave me a good explanation of what's going wrong, but I don't know how to give the client access to the docs now, aside from painting a description of the doc along with the hot key needed to launch it. Does that make sense?
Never add components to your GUI or remove components in the paint or paintComponent methods. Just don't do it. Ever. Period.
These methods are for drawing only, and need to be as fast as possible, else your program will appear unresponsive. Not only that, you do not have full control over when or even if these methods will be called, so program logic and structure should not go into these methods.
Instead react to user events with event listeners such as ActionListeners, ListSelectionListeners, or with key bindings.
Edit
Regarding
Got it, let me give you a more detailed breakdown. A client is navigating my program by looking at a list of characters in a game on a west panel. They can select a row from the list which will show char details on the east panel. The details are an image and description. Recently, I added relevant documents for that particular char, which will show on the bottom of the east panel. I created key listener's, so the client can quickly view the document by pressing a num key, but I also want to give them the ability to click on the button to launch a pdf view and see the contents of the document.
I'd use a JList to hold the list of selectable information on the left, and would react to it with a ListSelectionListener. In the listener, I'd change the related displayed information. I also avoid using KeyListeners with Swing but instead gravitate towards Key Bindings as they're more flexible and less rigid regarding focus.
Regarding
Since every char has different related docs and different number of docs, I repainted the buttons every time, to reflect the amount of related docs and the appropriate titles for the docs. This is where the repaint is acting strange. You gave me a good explanation of what's going wrong, but I don't know how to give the client access to the docs now, aside from painting a description of the doc along with the hot key needed to launch it. Does that make sense?
I'm not sure what you're doing here or what you're trying to do.
Since every char has different related docs and different number of docs, I repainted the buttons every time, to reflect the amount of related docs and the appropriate titles for the docs. This is where the repaint is acting strange. You gave me a good explanation of what's going wrong, but I don't know how to give the client access to the docs now, aside from painting a description of the doc along with the hot key needed to launch it. Does that make sense?
So rather then "painting" the buttons, why not just change there text (setText(...)).
When a user selects a "char". You are going to need to rebuild portions of your screen. Change the list model (as suggest above) and remove/add any buttons you need on the document container.
Completely new to Java and I'm at a complete brick wall.
I have a JTextArea on my system that I'd like to have a live update, so when something is added to table2 (in my database), my server pulls the new values from the database and then updates the JTextArea.
I have absolutely no idea how to do this, though I have worked out that I need to use Thread to get it to work.
Any/all help is greatly appreciated (I'm a little pressed for time with this)
What you can do is have your thread poll your database at given periods of time, or else, have the process which is updating the database fire some kind of event which your GUI class can pick up.
Once this happens, you can then use the SwingUtilities.invokeLater() to update your JTextArea. Something like this should do:
if (eventIsFired)
{
final String jtextAreaText = ...
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
jTextArea.setText(jTextAreaText);
}
});
}
The assumption is that jTextArea is your actual JTextArea which is declared as a global variable. jTextAreaText will need to be declared final so that it can be accessed through the inner class.
I'm using Netbeans and I've designed a window with JTable and added MouseEvent listener on JTable component and added this code:
private void productsTableMousePressed(java.awt.event.MouseEvent evt) {
if(evt.isPopupTrigger()) {
tablePopupMenu.setLocation(evt.getXOnScreen(), evt.getYOnScreen());
tablePopupMenu.setVisible(true);
System.out.println("Fired!");
}
}
private void productsTableMouseReleased(java.awt.event.MouseEvent evt) {
if(evt.isPopupTrigger()) {
tablePopupMenu.setLocation(evt.getXOnScreen(), evt.getYOnScreen());
tablePopupMenu.setVisible(true);
}
}
But it works only when I click on some cells. I want to get it working on whole JTable area. How?
Assuming your table is inside a JScrollPane, it may not cover the entire viewport. To ensure the whole area of your viewport is covered, call setFillsViewportHeight(true) on your table.
But it works only when I click on some cells, but i want to get it working on whole JTable area
The MouseListener will work on all cells. I don't know if you should be using the setLocation(...) method.
See Bringing Up a Popup Menu for example code.
Or a better approach is to use:
table.setComponentPopupMenu(...);
I've found that in my JTable (which is in a JScrollPane, nested in a JInternalFrame), there can be issues with scrolling and resizing when the JTable was larger than the JScrollPane.
Basically, if the Frame is on my left monitor, but I've scrolled the table all the way to the right, the pop-up appears on my right monitor.
I reviewed the results of four different options:
the getMousePositions() for both the frame and the scroll pane, plus the mouse event getX and getXOnScreen().
The only one that gave me the results I wanted was the getMousePositions() for the frame. Everything else was offset by it's own internal view of the world, which makes sense to me.
So I guess what I'm saying is be careful where you get your mouse coordinates.
I'm using 'hidemode 3' in MigLayout so that hidden components aren't visible at all (and don't have any impact on layout).
I'm using this so that I can show inline errors underneath textboxes, which show only if there is an error
Whenever I show these inline error boxes (they're text areas so I just call setVisible(true)) the scroll pane the form is embedded within automatically scrolls down to whatever is being set to visible (so I call setVisible(true) on something, it causes a layout change and the scroll pane auto-scrolls downward to where ever the component is)
Now, this isn't the problem - I think I get why the above part happens (presuambly the panel changing size to accomodate the new layout screws up the scale of the scroll bar, so it appears to scroll downward)
What I can't understand is how to work around it - for example I've tried doing this:
// validateModel will cause the setVisible() calls to occur
if (!syncControl_.validateModel())
{
// Here I try and counteract the layout change by going back
// to the top
variableScrollPane_.getViewport().setViewPosition(new Point(0,0));
}
But it doesn't work - or rather, it does work for a moment: The scroll occurs, but the apparent 'auto scroll' then happens a few milliseconds afterwards. It seems whatever redoes the layout either gets called at regular intervals, or setVisible actually fires some kind of event
I've tried calling validate(), invalidate() repaint() etc. prior to the scroll change to no avail.
So I suppose my question is: When I use hidemode 3 and call setVisible() what method is it that does the 'reacting'? Is it during validation / doLayout() etc. or is there something completely separate happening?
Thanks
Just accidentally answered my own question:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
variableScrollPane_.getVerticalScrollBar().setValue(0);
}
});
Turns out MigLayout doesn't factor into this at all (and it seems the above is a well-known mechanism for resetting the scrollbar in many situations)