Link to home
Start Free TrialLog in
Avatar of d1G1t4L
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_SCROLLBAR_AS_NEEDED (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().getExtent();
       
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.getVerticalScrollBar() 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!  :-)
Avatar of Mick Barry
Mick Barry
Flag of Australia image

add a HierarchyListener to your vertical scroll bar, it'll get fired whenever the scoll bar is added/removed.
>> JScrollPane.getVerticalScrollBar() 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.
Avatar of d1G1t4L
d1G1t4L

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?
Avatar of d1G1t4L

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(HierarchyEvent e)
       {
             JScrollBar sb = (JScrollBar) e.getSource();
             if (sb.isVisible())
            {
                // showing
            }
            else
            {
                // not showing
            }
       }
Avatar of d1G1t4L

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/functions returning possibly incorrect data because the scrollbar has not been drawn yet (though I can't imagine why this might even be the case)?  The method call sequence is something like this:

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().addHierarchyListener(new ScrollTest());
      }
      
      public  void hierarchyChanged(HierarchyEvent e)
       {
             JScrollBar sb = (JScrollBar) e.getSource();
             System.out.println("Scrollbar is visible: "+sb.isVisible());
       }
       
}
Avatar of d1G1t4L

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?

Avatar of d1G1t4L

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.
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.createEmptyBorder(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.VERTICAL_SCROLLBAR_AS_NEEDED,
            ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED
         );
         sp.getVerticalScrollBar().addHierarchyListener(this);

         getContentPane().setLayout(new BorderLayout());
         getContentPane().add(sp, BorderLayout.CENTER);
         
         setSize(new Dimension(300,300));
         setLocationRelativeTo(null);
    }
   
    public  void hierarchyChanged(HierarchyEvent e)
    {
        JScrollBar sb = (JScrollBar) e.getSource();
        System.out.println("Scrollbar is visible: "+ sb.isVisible());
    }
   
    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.java
 *
 */

import javax.swing.*;
import java.awt.event.*;
import java.awt.*;

public class ScrollBarDetectionDemo extends javax.swing.JFrame
                                    implements HierarchyListener {
   
    public ScrollBarDetectionDemo() {
        initComponents();
        theScrollPane.getVerticalScrollBar().addHierarchyListener(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.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                exitForm(evt);
            }
        });

        theScrollPane.setPreferredSize(new java.awt.Dimension(20, 50));
        theScrollPane.setViewportView(theTextArea);

        getContentPane().add(theScrollPane, java.awt.BorderLayout.NORTH);

        longButton.setText("Long Msg");
        longButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                longButtonActionPerformed(evt);
            }
        });

        southPanel.add(longButton);

        shortButton.setText("Short Msg");
        shortButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                shortButtonActionPerformed(evt);
            }
        });

        southPanel.add(shortButton);

        theButton.setText("The button!");
        southPanel.add(theButton);

        getContentPane().add(southPanel, java.awt.BorderLayout.SOUTH);

        pack();
    }

    private void shortButtonActionPerformed(java.awt.event.ActionEvent evt) {
        theTextArea.setText("One line message One line message");
    }

    private void longButtonActionPerformed(java.awt.event.ActionEvent evt) {
        theTextArea.append("\r\nAnother line2 message");
        theTextArea.append("\r\nAnother line3 message");
        theTextArea.append("\r\nAnother line4 message");
        theTextArea.append("\r\nAnother line5 message");
        theTextArea.append("\r\nAnother line6 message");
    }
   
    private void exitForm(java.awt.event.WindowEvent evt) {
        System.exit(0);
    }
   
    public void hierarchyChanged(HierarchyEvent e)
    {
        JScrollBar sb = (JScrollBar) e.getSource();
        System.out.println("Scrollbar is visible: "+ sb.isVisible());
        theButton.setEnabled( sb.isVisible() );
    }
   
    public static void main(String args[]) {
        new ScrollBarDetectionDemo().show();
    }
   
   
    // 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
   
}
Avatar of d1G1t4L

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.getParameter("content"));
      textArea.setEditable(false);
      textArea.setWrapStyleWord(true);
      textArea.setLineWrap(true);

      _scrollPane = new JScrollPane(textArea);
      _scrollPane.getVerticalScrollBar().addHierarchyListener(this);
      
        JPanel contentPane = new JPanel(new BorderLayout());
        contentPane.add(_scrollPane, BorderLayout.CENTER);
        contentPane.add(_button, BorderLayout.SOUTH);

        this.setContentPane(contentPane);
    }

    public void hierarchyChanged(HierarchyEvent e)
    {
      JScrollBar sb = (JScrollBar)e.getSource();

      System.out.println(sb.isVisible());
      //_button.setEnabled(!sb.isVisible());
    }
}

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("applet").innerHTML = appletStart + longParam + appletEnd;
}

function shortContent()
{
      document.getElementById("applet").innerHTML = appletStart + shortParam + appletEnd;
}
</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.isVisible());

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
Avatar of Mick Barry
Mick Barry
Flag of Australia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
>> 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!
Avatar of d1G1t4L

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!