JXTreeTable scroll upon adding new node? - java

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());
}
});

Related

Java - Setting position of jScrollBar

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

SWT Tree and TreeItem: Remove or change the drawing behavior of the expand/collapse indicator

I'm working in SWT (no JFace), and I'm attempting to customize the behavior of a Tree that I'm using a sidebar.
The top-level items in the tree shouldn't be selectable; they're basically headers. Only the children of these items should be selectable. As a result, I would like the UI to behave in a way that indicates this; clicking on one of these top-level items should expand or collapse but shouldn't provide any sort of visual feedback outside of the indicator changing its state and the children's visibility changing.
The problem seems to be that on OS X, the expand/collapse indicator is a triangle (don't know if it's an image or a unicode character) that either points right or down, but is also colored based on the "selection" state. I've managed to override all of the relevant behavior for the top-level items except for the arrow changing color.
I've used an SWT.EraseItem listener to hook in to the background drawing so that the background doesn't change color. This works as expected.
I've used an SWT.PaintItem listener to make sure that the text in the top-level item doesn't change color. This works as expected, but doesn't seem to have any influence over the indicator; I've even tried not resetting the GC's foreground color, but the color of the indicator still changes:
Not Selected:
Selected
Some of the things that I've attempted to do, all of which have failed:
Just drawing a rectangle on top of indicator. The indicator seems to always be on top, no matter what I attach the PaintListener to.
Using a Listener on the Selection event type, checking to see if the event.item is one of the top-level items, and then fiddling with the event bits and redraw flags. This doesn't seem to work well at all; the behavior is completely unpredictable. The code looks something like this:
sideBar.addListener(SWT.Selection, new Listener()
{
#Override
public void handleEvent(Event event)
{
TreeItem eventItem = (TreeItem) event.item;
if(sideBarRoots.contains(event.item))
{
event.detail = 0;
event.type = 0;
event.doit = false;
sideBar.setRedraw(false);
}
else
{
sideBar.setRedraw(true);
event.type = SWT.Selection;
event.doit = true;
}
}
});
Since the redraw flag is just a hint, sometimes it gets set properly and others it doesn't. It can have either no effect, or can get locked in to a state where nothing redraws in the sidebar.
I'm aware that a lot of this behavior is highly coupled to the behavior of the underlying native widgets, but I was wondering if there was any way to get the behavior that I'm looking for without roll a custom widget from scratch?
I think in a situation as yours you should ideally not allow selection on a tree header.
You can either cancel the selection event, by not allowing it to be clickable. But that might be a little kludgy implementation, especially for key navigation.
The safer approach, if possible with your data, would be to just move the selection whenever a tree parent node is selected. So it should work like this that if a parent node is selected, either by keyboard or mouse: you expand the node and move the selection to the first child.
This way you can avoid all the paint trickery to hide the selection state.

Adding components in GUI upon repaint method

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.

Miglayout + hidemode 3 = unwanted autoscroll when showing controls

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)

Enabling/disabling an AWT Button

I wrote the following piece of code, which is supposed to return a panel with one checkbox and one button. The idea is that the button should be enabled only if the checkbox is checked.
It works, meaning that if the checkbox is not checked, and I try to push the button, nothing happens. However, the visual appearance of the button is wrong, it appears as disabled when it should appear as enabled, and vice-versa.
Any idea what's wrong with my code ?
Thanks a lot in advance
public Panel createCalibrationPanel(final ImagePlus imp) {
final Panel panel = new Panel();
panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
panel.add(Box.createHorizontalStrut(20));
final Checkbox checkbox = new Checkbox(
"Use image spatial calibration for q scale", true);
final Button button = new Button("Set scale");
useCalibration = checkbox.getState();
button.setEnabled(checkbox.getState());
panel.add(checkbox);
panel.add(button);
checkbox.addItemListener(new ItemListener() {
public void itemStateChanged(final ItemEvent e) {
boolean state = checkbox.getState();
setUseCalibration(state);
button.setEnabled(state);
}
});
button.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent e) {
imp.unlock();
IJ.run(imp, "Set Scale...", "");
imp.lock();
}
});
return panel;
}
The logic controlling the button is correct. I modified your sample code and it works as you described. (This is the version I used: http://pastebin.com/f6cd6cfac, tested on Sun Java 6).
But, there are some other methods that you call, but which you haven't shown us: setUseCalibration, imp.unlock, and IJ.run. Are you sure they are returning correctly? You should remove the external calls from code, and add them back in one at time until you find which one(s) is causing the problem.
I was pretty sure you're doing everything correctly, and my test bore this out. I removed the image reference and the action code, and the button behaves as intended.
I'm running JDK 6 from Sun on Ubuntu Linux. But that shouldn't make any difference.
I wonder if imp.lock() is doing something heinous. If this is the kind of lock associated with synchronization and concurrent processing, it looks intuitively wrong, because you're keeping the poor imp locked up for most of the program's lifetime. In any case, you could have a look at what happens when you comment out the action code.
I can't reproduce the problem either using Sun Java 6 on 64 bit OpenSuse 11.0. However, this doesn't mean that the code is correct. It just happens to work for me on my machine, OS and JRE today.
From your description it sounded like button.setEnabled(state) wasn't updating the appearance of the button, so I wondered if adding a button.repaint() directly after the call would make it work? This is more of an experiment than a solution, as even if it works it doesn't explain why your original code runs for me and not for you.
Either way I suspect one of:
Something in the way that the code is called.
Something in the library code.
A bug in the JRE (if it's a Sun JRE you can search the bugs
database).
A threading problem (make sure that the AWT code runs on the
EventDispatchThread and synchronize on mutable data members that are shared between threads).

Categories

Resources