d1G1t4L
asked on
How to determine if a JScrollPane's vertical scrollbar is visible/necessary/drawn?
Hi all,
I have scoured the web far and wide, and have finally given up, I think. What I want to do is conceptually very simple, but I have yet to find a method for implementing it that produces the desired results. Here's the scenario:
In my JApplet, I have a JScrollPane wrapping a JTextArea. I have the scrollbar policy set to JScrollPane.VERTICAL_SCROL LBAR_AS_NE EDED (the default). I also have a button in my applet, which I want to conditionally enable only if (a) there no scrollbars are required for the JScrollPane or if (b) the scrollbar position is at the very bottom of the text area.
I've been able to accomplish the latter piece by code in an adjustmentValueChanged() event handler (my applet implements AdjustmentListener) that looks like this:
...
JScrollBar scrollBar = (JScrollBar)e.getSource();
int value = scrollBar.getValue();
int max = scrollBar.getMaximum();
int extent = scrollBar.getModel().getEx tent();
if ((value != 0) && (value == (max - extent)))
{
// code to disable button goes here
...
}
However, I have been unable to figure out how to accomplish part (a), because I have not been able to figure out how to programmatically determine if the vertical scrollbar is even drawn/necessary. Here's several things I've tried:
- JScrollPane.getVerticalScr ollBar() returns a non-null JScrollBar! (why, even when no scrollbar is drawn???)
- JScrollBar.isValid() (on the scrollbar that's returned from getVerticalScrollBar()) returns true even when there is no scrollbar
- JScrollBar.isVisible() returns false even when there *is* a scrollbar
- JScrollBar.getMaximum() always returns a positive value (not 0 or anything simple) even when there is no scrollbar
So - the question to all you experts out there - how on *EARTH* can I possibly do this? Am I being blind and missing the obvious? Also, please keep in mind that I am developing for JVM 1.3.x and higher - all solutions have to be targeted for Java 1.3.
Help!!! (This question is both urgent *and* extremely difficult, at least to me, hence the 500 pts! :-)
I have scoured the web far and wide, and have finally given up, I think. What I want to do is conceptually very simple, but I have yet to find a method for implementing it that produces the desired results. Here's the scenario:
In my JApplet, I have a JScrollPane wrapping a JTextArea. I have the scrollbar policy set to JScrollPane.VERTICAL_SCROL
I've been able to accomplish the latter piece by code in an adjustmentValueChanged() event handler (my applet implements AdjustmentListener) that looks like this:
...
JScrollBar scrollBar = (JScrollBar)e.getSource();
int value = scrollBar.getValue();
int max = scrollBar.getMaximum();
int extent = scrollBar.getModel().getEx
if ((value != 0) && (value == (max - extent)))
{
// code to disable button goes here
...
}
However, I have been unable to figure out how to accomplish part (a), because I have not been able to figure out how to programmatically determine if the vertical scrollbar is even drawn/necessary. Here's several things I've tried:
- JScrollPane.getVerticalScr
- JScrollBar.isValid() (on the scrollbar that's returned from getVerticalScrollBar()) returns true even when there is no scrollbar
- JScrollBar.isVisible() returns false even when there *is* a scrollbar
- JScrollBar.getMaximum() always returns a positive value (not 0 or anything simple) even when there is no scrollbar
So - the question to all you experts out there - how on *EARTH* can I possibly do this? Am I being blind and missing the obvious? Also, please keep in mind that I am developing for JVM 1.3.x and higher - all solutions have to be targeted for Java 1.3.
Help!!! (This question is both urgent *and* extremely difficult, at least to me, hence the 500 pts! :-)
add a HierarchyListener to your vertical scroll bar, it'll get fired whenever the scoll bar is added/removed.
>> JScrollPane.getVerticalScr ollBar() returns a non-null JScrollBar! (why, even when no scrollbar is drawn???)
Its not that a new scroll-bar will be instantiated everytime the scroll-bar appears and that it will be made null when it disappears. When you make the JScrollPane and set the policy for it, the JScrollBar objects will be constructed. They might just be invisble, depending upon whether the text exceeds the JTextArea's width/ height or not (and depending upon your policy). It doesn't mean that they are null.
Its not that a new scroll-bar will be instantiated everytime the scroll-bar appears and that it will be made null when it disappears. When you make the JScrollPane and set the policy for it, the JScrollBar objects will be constructed. They might just be invisble, depending upon whether the text exceeds the JTextArea's width/ height or not (and depending upon your policy). It doesn't mean that they are null.
ASKER
mayankeagle - so how do I determine if the JScrollBar is visible or not? isVisible() doesn't work... isShowing() doesn't work either. There has to be some (relatively easy) way of determining if the text to be rendered by the JTextArea will require a scrollbar. I'm not particularly tied to any one way of achieving my end goal - which is really just to find out if a scrollbar needs to be drawn and/or is drawn.
objects - I did a little research with the API reference, and found that a HierarchyEvent should effectively tell me the same thing as Component.isShowing(). First of all - is this a correct statement? Because if so, then isShowing() returns false whether or not the JScrollBar is visible (see above). If my statement is incorrect, though, do you have any sample code you can add to show me what the hierarchyChanged() event handler should look like? I'm not sure about this whole business of HieararchyEvents, either - are they generated for Swing objects like JScrollBar?
objects - I did a little research with the API reference, and found that a HierarchyEvent should effectively tell me the same thing as Component.isShowing(). First of all - is this a correct statement? Because if so, then isShowing() returns false whether or not the JScrollBar is visible (see above). If my statement is incorrect, though, do you have any sample code you can add to show me what the hierarchyChanged() event handler should look like? I'm not sure about this whole business of HieararchyEvents, either - are they generated for Swing objects like JScrollBar?
ASKER
In re-reading my first post, I found one point which was perhaps unclear. The text to be rendered in the JTextArea is not static. More specifically, it is based on an applet PARAM, and so can be of variable length. This is why I need to only enable the button in the applet if (a) the JTextArea has no scrollbar or (b) if the scroll position is at the maximum value.
Maybe that you could try the getSize () method which it inherits from Component. It would return a Dimension. The JScrollBar's dimension would be rigid along (fixed) one axis and variable in the other (scrolling) axis.
> and found that a HierarchyEvent should effectively tell me the same thing as Component.isShowing().
> First of all - is this a correct statement?
Not really. The event tells when/how things have changed.
> do you have any sample code you can add to show me what the hierarchyChanged() event handler should look like?
public void hierarchyChanged(Hierarchy Event e)
{
JScrollBar sb = (JScrollBar) e.getSource();
if (sb.isVisible())
{
// showing
}
else
{
// not showing
}
}
> First of all - is this a correct statement?
Not really. The event tells when/how things have changed.
> do you have any sample code you can add to show me what the hierarchyChanged() event handler should look like?
public void hierarchyChanged(Hierarchy
{
JScrollBar sb = (JScrollBar) e.getSource();
if (sb.isVisible())
{
// showing
}
else
{
// not showing
}
}
ASKER
mayankeagle - OK, I thought your new suggestion would work for sure! It did not. :-( The size (width and height) comes up as 0x0 regardless of whether or not the JScrollBar is visible.
Another thought here - are all these dimensions/metrics/functio
1. Applet's init() method creates custom panel (deriving from JPanel) - invokes custom panel's constructor
2. Custom panel constructor creates JTextArea and JScrollPane and saves JScrollPane reference in a member variable
3. Applet Constructor calls hasScrollbar() method (which I've defined) in custom panel class
4. My hasScrollBar() method gets the JScrollBar from the JScrollPane (via getVerticalScrollBar())
5. hasScrollBar() uses the as-yet non-functional method (of many already described) to determine whether or not the scroll pane has a scrollbar.
here's example of using a HierarchyListener:
import javax.swing.*;
import java.awt.event.*;
public class ScrollTest implements HierarchyListener
{
public static void main(String[] args)
{
JFrame f = new JFrame();
JTextArea ta = new JTextArea();
JScrollPane sp = new JScrollPane(ta);
f.getContentPane().add(sp) ;
f.pack();
f.show();
sp.getVerticalScrollBar(). addHierarc hyListener (new ScrollTest());
}
public void hierarchyChanged(Hierarchy Event e)
{
JScrollBar sb = (JScrollBar) e.getSource();
System.out.println("Scroll bar is visible: "+sb.isVisible());
}
}
import javax.swing.*;
import java.awt.event.*;
public class ScrollTest implements HierarchyListener
{
public static void main(String[] args)
{
JFrame f = new JFrame();
JTextArea ta = new JTextArea();
JScrollPane sp = new JScrollPane(ta);
f.getContentPane().add(sp)
f.pack();
f.show();
sp.getVerticalScrollBar().
}
public void hierarchyChanged(Hierarchy
{
JScrollBar sb = (JScrollBar) e.getSource();
System.out.println("Scroll
}
}
ASKER
objects - OK, finally I'm making some progress. I implemented code similar to yours - essentially, I added a state variable (a boolean) that I set to the value of the isVisible(). Just for kicks, I also added some println()s to see how often the event was being fired and to report the value of my state variable from my hasScrollbar() method. This is what I found for the situation where the ScrollBar is visible:
hierarchyChanged() fired! ScrollBar is visible
hierarchyChanged() fired! ScrollBar is visible
hierarchyChanged() fired! ScrollBar is visible
hierarchyChanged() fired! ScrollBar is not visible
_hasScrollBar = false
hierarchyChanged() fired! ScrollBar is visible
hierarchyChanged() fired! ScrollBar is visible
The "_hasScrollBar = false" output is generated by my hasScrollbar() method, while the other output is all from the hierarchyChanged() handler. So what is happening, then, is that the hierarchy is changed twice after we've (incorrectly) determined the scrollbar to not be visible. (This also answers my question of why isVisible() directly from my hasScrollbar() method was reporting 'false'.)
I thought about tying the hierarchyChanged() handler back to the parent JApplet by way of a custom Listener class (to which I'd register for a notification event with an add listener method, etc., etc.), and then directly enabling my button when the ScrollBar was not visible and disabling otherwise. But this can lead to problems if the hierarchy is changed after I've already changed the button status. So this is not a reliable mechanism - or at least, I don't yet know how to make it reliable.
Any thoughts? You have certainly gotten me far closer to my goal than any other solution I've attempted - if you can make this HierarchyEvent-based solution work, you've got the points!
Thanks for your help so far!
> But this can lead to problems if the hierarchy is changed after I've already changed the button status
If you're only listener for HierarchChange events on the vertical scroll bar then how would problems occur?
What other changes would cause an event to be fired?
If you're only listener for HierarchChange events on the vertical scroll bar then how would problems occur?
What other changes would cause an event to be fired?
ASKER
> If you're only listener for HierarchChange events on the vertical scroll bar then how would problems occur?
> What other changes would cause an event to be fired?
objects - that's the question to which I don't have an answer. I can't even imagine why the event is fired as many times as it currently is. I am naturally unsure, then, if there's any external stimulus that may cause the JScrollPane code to change the status of the scrollbar.
I would guess that once the scrollbar is either visible or not visible, its hierarchy wouldn't need to be changed - but this is not happening currently. Naturally, I'm a little hesitant to go with something I'm not sure about.
What happens when you run the example I posted above?
When I run it events are only fired when the scrollbar either becomes visible or is hidden.
When I run it events are only fired when the scrollbar either becomes visible or is hidden.
This test app seems to indicate that the listener objects suggested works as intended.
Whenever you change the appearing/disappearing by sizing the main window, the correct message is printed out.
/*
* MyApp.java
*
*/
import javax.swing.*;
import java.text.*;
import java.awt.*;
import java.awt.event.*;
public class MyApp extends JFrame implements HierarchyListener {
/** Creates a new instance of MyApp */
public MyApp() {
JPanel mainPanel = new JPanel(new FlowLayout(FlowLayout.LEFT ));
final JPanel p = new JPanel();
p.setLayout(new GridBagLayout());
p.setBorder(BorderFactory. createEmpt yBorder(10 , 10, 10, 10));
GridBagLayout grid = (GridBagLayout) p.getLayout();
GridBagConstraints c = new GridBagConstraints();
c.anchor = GridBagConstraints.WEST;
int row = 0;
int column = 0;
for(int i = 0; i < 40; ++i)
{
c.insets = new Insets(2, 2, 2, 2);
c.gridx = i%4;
c.gridy = i/4;
c.fill = GridBagConstraints.NONE;
JButton b = new JButton("This is button " + (i + 1));
grid.setConstraints(b, c);
p.add(b); // Just to try out.
}
p.setMaximumSize(new Dimension(600, Integer.MAX_VALUE));
mainPanel.add(p);
JScrollPane sp = new JScrollPane(
mainPanel,
ScrollPaneConstants.VERTIC AL_SCROLLB AR_AS_NEED ED,
ScrollPaneConstants.HORIZO NTAL_SCROL LBAR_AS_NE EDED
);
sp.getVerticalScrollBar(). addHierarc hyListener (this);
getContentPane().setLayout (new BorderLayout());
getContentPane().add(sp, BorderLayout.CENTER);
setSize(new Dimension(300,300));
setLocationRelativeTo(null );
}
public void hierarchyChanged(Hierarchy Event e)
{
JScrollBar sb = (JScrollBar) e.getSource();
System.out.println("Scroll bar is visible: "+ sb.isVisible());
}
public static void main(String[] args) {
MyApp app = new MyApp();
app.show();
}
}
Whenever you change the appearing/disappearing by sizing the main window, the correct message is printed out.
/*
* MyApp.java
*
*/
import javax.swing.*;
import java.text.*;
import java.awt.*;
import java.awt.event.*;
public class MyApp extends JFrame implements HierarchyListener {
/** Creates a new instance of MyApp */
public MyApp() {
JPanel mainPanel = new JPanel(new FlowLayout(FlowLayout.LEFT
final JPanel p = new JPanel();
p.setLayout(new GridBagLayout());
p.setBorder(BorderFactory.
GridBagLayout grid = (GridBagLayout) p.getLayout();
GridBagConstraints c = new GridBagConstraints();
c.anchor = GridBagConstraints.WEST;
int row = 0;
int column = 0;
for(int i = 0; i < 40; ++i)
{
c.insets = new Insets(2, 2, 2, 2);
c.gridx = i%4;
c.gridy = i/4;
c.fill = GridBagConstraints.NONE;
JButton b = new JButton("This is button " + (i + 1));
grid.setConstraints(b, c);
p.add(b); // Just to try out.
}
p.setMaximumSize(new Dimension(600, Integer.MAX_VALUE));
mainPanel.add(p);
JScrollPane sp = new JScrollPane(
mainPanel,
ScrollPaneConstants.VERTIC
ScrollPaneConstants.HORIZO
);
sp.getVerticalScrollBar().
getContentPane().setLayout
getContentPane().add(sp, BorderLayout.CENTER);
setSize(new Dimension(300,300));
setLocationRelativeTo(null
}
public void hierarchyChanged(Hierarchy
{
JScrollBar sb = (JScrollBar) e.getSource();
System.out.println("Scroll
}
public static void main(String[] args) {
MyApp app = new MyApp();
app.show();
}
}
This demo app is even closer to what you want.
Click the "Long Msg"/"Short Msg" buttons and see "The button!" button toggle between enabled/disabled.
/*
* ScrollBarDetectionDemo.jav a
*
*/
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class ScrollBarDetectionDemo extends javax.swing.JFrame
implements HierarchyListener {
public ScrollBarDetectionDemo() {
initComponents();
theScrollPane.getVerticalS crollBar() .addHierar chyListene r(this);
setSize( new Dimension(350, 150) );
setLocationRelativeTo(null );
}
private void initComponents() {
theScrollPane = new javax.swing.JScrollPane();
theTextArea = new javax.swing.JTextArea();
southPanel = new javax.swing.JPanel();
longButton = new javax.swing.JButton();
shortButton = new javax.swing.JButton();
theButton = new javax.swing.JButton();
addWindowListener(new java.awt.event.WindowAdapt er() {
public void windowClosing(java.awt.eve nt.WindowE vent evt) {
exitForm(evt);
}
});
theScrollPane.setPreferred Size(new java.awt.Dimension(20, 50));
theScrollPane.setViewportV iew(theTex tArea);
getContentPane().add(theSc rollPane, java.awt.BorderLayout.NORT H);
longButton.setText("Long Msg");
longButton.addActionListen er(new java.awt.event.ActionListe ner() {
public void actionPerformed(java.awt.e vent.Actio nEvent evt) {
longButtonActionPerformed( evt);
}
});
southPanel.add(longButton) ;
shortButton.setText("Short Msg");
shortButton.addActionListe ner(new java.awt.event.ActionListe ner() {
public void actionPerformed(java.awt.e vent.Actio nEvent evt) {
shortButtonActionPerformed (evt);
}
});
southPanel.add(shortButton );
theButton.setText("The button!");
southPanel.add(theButton);
getContentPane().add(south Panel, java.awt.BorderLayout.SOUT H);
pack();
}
private void shortButtonActionPerformed (java.awt. event.Acti onEvent evt) {
theTextArea.setText("One line message One line message");
}
private void longButtonActionPerformed( java.awt.e vent.Actio nEvent evt) {
theTextArea.append("\r\nAn other line2 message");
theTextArea.append("\r\nAn other line3 message");
theTextArea.append("\r\nAn other line4 message");
theTextArea.append("\r\nAn other line5 message");
theTextArea.append("\r\nAn other line6 message");
}
private void exitForm(java.awt.event.Wi ndowEvent evt) {
System.exit(0);
}
public void hierarchyChanged(Hierarchy Event e)
{
JScrollBar sb = (JScrollBar) e.getSource();
System.out.println("Scroll bar is visible: "+ sb.isVisible());
theButton.setEnabled( sb.isVisible() );
}
public static void main(String args[]) {
new ScrollBarDetectionDemo().s how();
}
// Variables declaration - do not modify
private javax.swing.JButton longButton;
private javax.swing.JButton shortButton;
private javax.swing.JPanel southPanel;
private javax.swing.JButton theButton;
private javax.swing.JScrollPane theScrollPane;
private javax.swing.JTextArea theTextArea;
// End of variables declaration
}
Click the "Long Msg"/"Short Msg" buttons and see "The button!" button toggle between enabled/disabled.
/*
* ScrollBarDetectionDemo.jav
*
*/
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class ScrollBarDetectionDemo extends javax.swing.JFrame
implements HierarchyListener {
public ScrollBarDetectionDemo() {
initComponents();
theScrollPane.getVerticalS
setSize( new Dimension(350, 150) );
setLocationRelativeTo(null
}
private void initComponents() {
theScrollPane = new javax.swing.JScrollPane();
theTextArea = new javax.swing.JTextArea();
southPanel = new javax.swing.JPanel();
longButton = new javax.swing.JButton();
shortButton = new javax.swing.JButton();
theButton = new javax.swing.JButton();
addWindowListener(new java.awt.event.WindowAdapt
public void windowClosing(java.awt.eve
exitForm(evt);
}
});
theScrollPane.setPreferred
theScrollPane.setViewportV
getContentPane().add(theSc
longButton.setText("Long Msg");
longButton.addActionListen
public void actionPerformed(java.awt.e
longButtonActionPerformed(
}
});
southPanel.add(longButton)
shortButton.setText("Short
shortButton.addActionListe
public void actionPerformed(java.awt.e
shortButtonActionPerformed
}
});
southPanel.add(shortButton
theButton.setText("The button!");
southPanel.add(theButton);
getContentPane().add(south
pack();
}
private void shortButtonActionPerformed
theTextArea.setText("One line message One line message");
}
private void longButtonActionPerformed(
theTextArea.append("\r\nAn
theTextArea.append("\r\nAn
theTextArea.append("\r\nAn
theTextArea.append("\r\nAn
theTextArea.append("\r\nAn
}
private void exitForm(java.awt.event.Wi
System.exit(0);
}
public void hierarchyChanged(Hierarchy
{
JScrollBar sb = (JScrollBar) e.getSource();
System.out.println("Scroll
theButton.setEnabled( sb.isVisible() );
}
public static void main(String args[]) {
new ScrollBarDetectionDemo().s
}
// Variables declaration - do not modify
private javax.swing.JButton longButton;
private javax.swing.JButton shortButton;
private javax.swing.JPanel southPanel;
private javax.swing.JButton theButton;
private javax.swing.JScrollPane theScrollPane;
private javax.swing.JTextArea theTextArea;
// End of variables declaration
}
ASKER
objects:
> What happens when you run the example I posted above?
> When I run it events are only fired when the scrollbar either becomes visible or is hidden.
When I ran your example, it worked as you stated. (More on this in a bit)
zzynx & objects:
I definitely appreciate the code you have both posted. However, the problem now, I think, is that in an applet, the hierarchyChanged() event is fired several times. I adapted the two examples you posted and came up with the following applet code:
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import javax.swing.*;
import javax.swing.event.*;
public class ScrollTest extends JApplet implements HierarchyListener
{
JButton _button = null;
JScrollPane _scrollPane = null;
public void init()
{
_button = new JButton("Button");
_button.setEnabled(false);
JTextArea textArea = new JTextArea();
textArea.setText(this.getP
textArea.setEditable(false
textArea.setWrapStyleWord(
textArea.setLineWrap(true)
_scrollPane = new JScrollPane(textArea);
_scrollPane.getVerticalScr
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.add(_scrollPan
contentPane.add(_button, BorderLayout.SOUTH);
this.setContentPane(conten
}
public void hierarchyChanged(Hierarchy
{
JScrollBar sb = (JScrollBar)e.getSource();
System.out.println(sb.isVi
//_button.setEnabled(!sb.i
}
}
I then wrote a quick-and-dirty test HTML page that feeds the applet with two possible content strings, a short one and a long one:
<html>
<script language="JavaScript">
var longParam = "<param name=\"content\" value=\"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Praesent tincidunt purus.\">";
var shortParam = "<param name=\"content\" value=\"Lorem ipsum dolor sit amet, consectetuer adipiscing elit.\">";
var appletStart = "<applet code=\"ScrollTest.class\" width=\"100\" height=\"100\">";
var appletEnd = "</applet>";
function longContent()
{
document.getElementById("a
}
function shortContent()
{
document.getElementById("a
}
</script>
<body>
<input type="button" value="long content" onclick="longContent()">
<input type="button" value="short content" onclick="shortContent()">
<div id="applet">
<!-- applet HTML auto generated by longContent() / shortContent() -->
</div>
</body>
</html>
So with these two pieces, I've now built a good model of what I would need to implement in my actual applet.
When I ran the test applet with the "long content," the console output looked like this:
true
true
true
false
true
true
Note that this is much like the output that I was seeing in my original applet.
When I ran the test applet with the "short content," the output looked like this:
true
true
true
false
false
Which is also like what I was seeing in my original applet.
The *really* weird part - when I uncomment the following line:
//_button.setEnabled(!sb.i
I *still* don't see the desired behavior in the resultant test applet! What's going on here? Have I stumbled on an obscure VM bug? (I don't think so.) Is there another way of accomplishing my goal that does not involve HierarchyEvents?
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
>> Otherwise you'll get various events at startup.
Could be true. But is that really a problem?
>> You should also remove the listener or else ...
When?
>> I *still* don't see the desired behavior in the resultant test applet!
So you are saying that even when the last traced line says "true" (=long content) the button is NOT disabled?
or even when the last traced line says "false" (=short content) the button is NOT enabled?
Or do you even have other combinations? Weird!
Could be true. But is that really a problem?
>> You should also remove the listener or else ...
When?
>> I *still* don't see the desired behavior in the resultant test applet!
So you are saying that even when the last traced line says "true" (=long content) the button is NOT disabled?
or even when the last traced line says "false" (=short content) the button is NOT enabled?
Or do you even have other combinations? Weird!
ASKER
Hrm - I seem to have forgotten to actually add my note. Anyway - objects gets the points because his idea of using HierarchyEvents was the first (and only) that actually worked.
zzynx - thanks for your help, but objects provided sample code that I could have used successfully before you did, and he also helped with the key to making this all work: the addHierarchyListener() should be implemented in the applet's start().
Thanks to all who contributed!