I have a class which extends JScrollPane, its viewport is another class which extends JComponent and implements Scrollable. When the size of the component changes the JscrollBars do not update unless I call revalidate() however this resets the position of the scroll bars to the top left. Is there a way of updating the size of the scroll bars while maintaining their current position?
Thanks, Rob
Is the problem your custom scroll pane or your custom component? We can't begin to guess what kind of changes you may have made. Post your SSCCE that demonstrates the problem.
It works fine for me.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ScrollSSCCE extends JPanel
{
public ScrollSSCCE()
{
setLayout( new BorderLayout() );
final JPanel panel = new JPanel();
panel.setPreferredSize( new Dimension(200, 200) );
JScrollPane scrollPane = new JScrollPane( panel );
add( scrollPane );
JButton button = new JButton("Adjust");
add(button, BorderLayout.SOUTH);
button.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
Dimension d = panel.getPreferredSize();
d.width +=50;
d.height +=50;
panel.setPreferredSize(d);
panel.revalidate();
}
});
}
private static void createAndShowUI()
{
JFrame frame = new JFrame("ScrollSSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new ScrollSSCCE() );
frame.setSize(150, 150);
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
Related
That's what I did at first.
public class MyFrame extends JFrame {
public MyFrame() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(500 ,300));
setResizable(false);
pack();
setLocationRelativeTo(null);
initComponents();
}
private void initComponents() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
for (int i=0; i < 100; i++)
panel.add(new JLabel("some text"));
JScrollPane scrollPane = new JScrollPane(panel,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
// Here I create a JPanel to replace the contentPane of JFrame
JPanel contentPane = new JPanel();
contentPane.add(scrollPane);
setContentPane(contentPane);
}
If instead I replace the last 3 lines with this:
getContentPane().add(scrollPane);
everything is ok. But as I did before, the vertical scrollbar is not showing up. What is causing this? Is setting a JPanel as contentPane wrong?
Update:
If contentPane changes to BorderLayout everything work fine.
// Here I create a JPanel to replace the contentPane of JFrame
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.add(scrollPane);
setContentPane(contentPane);
So the problem is default FlowLayout?
Solved:
The problem is FlowLayout. It wraps around JScrollPane and hides the Toolbars. using
scrollPane.setPreferredSize(new Dimension(500, 400)); // longer space in x-axis
solves it.
Answer:
JSrollPane should not be used inside a Container that uses FlowLayout.
First of all - there is nothing bad in using your own component as content pane. But default content pane is also a JPanel instance so there is actually no point to replace it with your own panel, unless you want to use non-panel content pane or customized panel component.
This is how the default content pane looks like:
/**
* Called by the constructor methods to create the default
* <code>contentPane</code>.
* By default this method creates a new <code>JComponent</code> add sets a
* <code>BorderLayout</code> as its <code>LayoutManager</code>.
* #return the default <code>contentPane</code>
*/
protected Container createContentPane() {
JComponent c = new JPanel();
c.setName(this.getName()+".contentPane");
c.setLayout(new BorderLayout() {
/* This BorderLayout subclass maps a null constraint to CENTER.
* Although the reference BorderLayout also does this, some VMs
* throw an IllegalArgumentException.
*/
public void addLayoutComponent(Component comp, Object constraints) {
if (constraints == null) {
constraints = BorderLayout.CENTER;
}
super.addLayoutComponent(comp, constraints);
}
});
return c;
}
This method is taken from JRootPane. It is basically a simple JPanel with a but customized layout manager as you can see.
Now, you have a few problems in your example.
First is the order of calls - you are sizing frame before adding content into it. Simply change the order and you will see your scrollpane:
public class MyFrame extends JFrame
{
public MyFrame ()
{
super();
// Add components first
initComponents ();
// Setup frame after so it fits its new content
setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
setPreferredSize ( new Dimension ( 500, 300 ) );
setResizable ( false );
pack ();
setLocationRelativeTo ( null );
}
private void initComponents ()
{
JPanel panel = new JPanel ();
panel.setLayout ( new BoxLayout ( panel, BoxLayout.Y_AXIS ) );
for ( int i = 0; i < 100; i++ )
{
panel.add ( new JLabel ( "some text" ) );
}
JScrollPane scrollPane =
new JScrollPane ( panel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER );
// Here I create a JPanel to replace the contentPane of JFrame
JPanel contentPane = new JPanel ();
contentPane.add ( scrollPane );
setContentPane ( contentPane );
}
public static void main ( String[] args )
{
SwingUtilities.invokeLater ( new Runnable ()
{
public void run ()
{
new MyFrame ().setVisible ( true );
}
} );
}
}
It will still look different because your new JPanel () uses FlowLayout by default, instead of BorderLayout used by default content pane component:
Simply set BorderLayout and you will have the result you want to see:
JPanel contentPane = new JPanel ( new BorderLayout () );
Have a look at Rob Camick's WrapLayout, which is an extension of FlowLayout
import java.awt.*;
import javax.swing.*;
public class TestWrapLayout {
public TestWrapLayout () {
ImageIcon icon = new ImageIcon(getClass().getResource("/resources/stackoverflow2.png"));
JPanel panel = new JPanel(new WrapLayout());
for (int i = 1; i <= 250; i++) {
JLabel iconlabel = new JLabel(icon);
iconlabel.setLayout(new BorderLayout());
JLabel textlabel = new JLabel(String.valueOf(i));
textlabel.setHorizontalAlignment(JLabel.CENTER);
textlabel.setForeground(Color.WHITE);
textlabel.setFont(new Font("impact", Font.PLAIN,20));
iconlabel.add(textlabel);
panel.add(iconlabel);
}
JFrame frame = new JFrame();
frame.add(new JScrollPane(panel));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 300);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
public void run() {
new TestWrapLayout();
}
});
}
}
You must update your frame after adding your panel using pack() function. When you do
getContentPane().add(scrollPane);
the function add does that for you (ref)
Firstly, what was the reason to create the second panel? There
is already the first panel that has BoxLayout as the layout
manager set. Simply setting the scroll pane having the first panel
as parent works as expected.
You either call
setContentPane(scrollPane);
or
add(scrollPane);
Now I am going to explain what caused this unexpected behaviour. This
is a quirk that sometimes happens to those who use nesting technique
when building their layouts. When nesting is used, the layouts may
influence each other.
By choosing another layout -- FlowLayout -- as the underlying base layout,
you caused the first panel to be displayed in its preferred size. Instead
of one panel, you have now two panels, the base panel influences the
panel with labels -- it controls how it is sized. The FlowLayout
shows all its children in preferred size; it does not honour mimimum nor
maximum sizes. So the (first) visible panel is sized to show all its labels;
this is how preferred size is calculated -- just big enough to show all its
children. However, with 100 labels, it is very big; the layout is broken. It is
vertically so big that we cannot practically get to the bottom of the window.
So with our visible panel showing all its labels, what's the purpose of showing
a verticall scrollbar? No need for one, since all labels are "visible" (placed on
the window area), though the desing is broken.
So the problem does not lie with the scrollbars; they work normally. If you
set (in your example) the vertical scrollbar policy to VERTICAL_SCROLLBAR_ALWAYS
you will see the scrollbar there but without a slider, since all labels are "visible"
and there is nothing to be scrolled. (Scrollbars show items that are hidden from the
layout.) The issue lies in the fact that FlowLayout shows its components in the preferred size only.
The following is a fixed code example that works as expected:
package com.zetcode;
import java.awt.Dimension;
import java.awt.EventQueue;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
public class MyFrame extends JFrame {
public MyFrame() {
initComponents();
setTitle("Scrollbar");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(300, 200));
pack();
setLocationRelativeTo(null);
}
private void initComponents() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
for (int i=0; i < 100; i++)
panel.add(new JLabel("some text"));
JScrollPane scrollPane = new JScrollPane(panel,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
setContentPane(scrollPane);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
MyFrame ex = new MyFrame();
ex.setVisible(true);
}
});
}
}
The assignment is simple, all we need to do is have the code create a window with a red panel with a single button and label. Here is the code thus far as well as the tester class.
I got the label to display on the window, but its in a weird place. I cant get the button to display at all as well getting the background to display as red.
This is where I'm having trouble with the most:
import java.awt.Color;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class MyCustomFrame extends JFrame
{
public MyCustomFrame()
{
createComponents();
setSize(FRAME_WIDTH, FRAME_HEIGHT);
}
private void createComponents()
{
JPanel panel=new createPanel();
button=new JButton("Push Me");
label=new JLabel("This is a label");
add(button);
add(label);
}
private JButton button;
private JLabel label;
final int FRAME_WIDTH = 800;
final int FRAME_HEIGHT = 800;
public void createPanel()
{
JPanel panel=new JPanel();
panel.setBackground(Color.RED);
//button=new JButton("Push Me");
//label=new JLabel("This is a label");
}
public void createFrame()
{
JFrame frame=new JFrame();
add(frame);
frame.setVisible(true);
}
}
And this is the tester class:
import javax.swing.JFrame;
public class MyCustomFrameViewer
{
public static void main(String[] args)
{
MyCustomFrame frame = new MyCustomFrame();
frame.setTitle("My first frame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
You create the JPanel, panel, but add nothing to it, and then never add the panel to your JFrame. You should add your components to the JPanel, panel, and then add the panel object to your JFrame.
Note that a JPanel uses FlowLayout by default, and so it will more easily accept multiple other components without special add methods. The JFrame's contentPane uses BorderLayout which is slightly more complicated to use.
Add the panel to the frame instead of the label and button, and add the label and button to the panel...
private void createComponents()
{
JPanel panel=new createPanel();
add(panel);
button=new JButton("Push Me");
label=new JLabel("This is a label");
panel.add(button);
panel.add(label);
}
You may also want to take a look at Initial Threads and make sure you are creating your UI from within the context of the Event Dispatching Thread
Some side notes...
This scares me...
public void createFrame()
{
JFrame frame=new JFrame();
add(frame);
frame.setVisible(true);
}
You're trying to add a frame to a frame, which is an illegal operation in Swing, but thankfully, you're not actually calling it from what I can see.
Instead of using setSize(FRAME_WIDTH, FRAME_HEIGHT);, you may wish to use pack instead.
See also the tips in comments in this example:
import java.awt.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
// Don't extend frame, just use an instance
//public class MyCustomFrame extends JFrame {
public class MyCustomFrame {
private JFrame frame;
private JButton button;
private JLabel label;
private JPanel panel;
// better to override the preferred size of the component of interest
final int GAME_WIDTH = 300;
final int GAME_HEIGHT = 100;
public MyCustomFrame() {
createComponents();
// better to override the preferred size of the component of interest
//setSize(GAME_WIDTH, GAME_HEIGHT);
}
private void createComponents() {
// compilation error! createPanel() does not return anything..
//JPanel panel = new createPanel();
createPanel();
// create the frame!
createFrame();
}
private void createPanel() {
// creates a local instance
//JPanel panel = new JPanel();
panel = new JPanel() {
/* override the preferred size of the component of interest */
#Override
public Dimension getPreferredSize() {
return new Dimension(GAME_WIDTH, GAME_HEIGHT);
}
};
panel.setBackground(Color.RED);
button = new JButton("Push Me");
label = new JLabel("This is a label");
panel.add(button);
panel.add(label);
}
private void createFrame() {
// create a local instance of a JFrame that goes out of scope at end
// of method..
//JFrame frame = new JFrame();
frame = new JFrame("My first frame");
// add the panel to the frame!
frame.add(panel);
// better to use DISPOSE_ON_CLOSE
//frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// a good way to position a GUI
frame.setLocationByPlatform(true);
// this was trying to add a JFrame to another JFrame
//add(frame);
frame.pack();
}
public final JFrame getFrame() {
return frame;
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
MyCustomFrame myFrame = new MyCustomFrame();
JFrame frame = myFrame.getFrame();
frame.setVisible(true);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
I am using setBorder(BorderFactory.createTitledBorder(title)) in my JPanel in order to group its content in a rectangle with a title above it. How can I set a tooltip text for the title?
A possible approach is nesting components. As Borders are not components they can not have tooltips, but you can have a component with the sole purpose of holding border and the tooltip:
JPanel outer = new JPanel();
outer.setBorder(BorderFactory.createTitledBorder("Title"));
outer.setToolTipText("sample text");
JPanel inner = new JPanel();
outer.add(inner);
and then use inner as the container for the components you want to group.
You can override the getToolTipText() method of the panel to check if the mouse of over the title text:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
public class TitledBorderTest
{
private static void createAndShowUI()
{
UIManager.getDefaults().put("TitledBorder.titleColor", Color.RED);
Border lowerEtched = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED);
TitledBorder title = BorderFactory.createTitledBorder(lowerEtched, "Title");
// title.setTitleJustification(TitledBorder.RIGHT);
Font titleFont = UIManager.getFont("TitledBorder.font");
title.setTitleFont( titleFont.deriveFont(Font.ITALIC + Font.BOLD) );
JPanel panel = new JPanel()
{
#Override
public String getToolTipText(MouseEvent e)
{
Border border = getBorder();
if (border instanceof TitledBorder)
{
TitledBorder tb = (TitledBorder)border;
FontMetrics fm = getFontMetrics( tb.getTitleFont() );
int titleWidth = fm.stringWidth(tb.getTitle()) + 20;
Rectangle bounds = new Rectangle(0, 0, titleWidth, fm.getHeight());
return bounds.contains(e.getPoint()) ? super.getToolTipText() : null;
}
return super.getToolTipText(e);
}
};
panel.setBorder( title );
panel.setToolTipText("Title With ToolTip");
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( panel );
frame.setSize(200, 200);
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
This code assumes the title is on the left. If you want the title on the right then you would need to adjust the X value of the text bounds.
I don't think you can add setToolTipText to TitledBorder. you can provide tooltip for JComponent but TitledBorder is not derived from JComponent.
You can try to use JPanel instead:
ToolTipManager.sharedInstance().registerComponent(new JPanel());
//ToolTipManager.sharedInstance().setDismissDelay(800000);
TollTip isn't right Components for experiments, all goog workaround for popup or tooltips are based on JWindow/ undecorated JDialog
maybe not necessary, keys in UIManager are accesible, but in this case all TollTips has the same settings
import java.awt.*;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.*;
public class ColoredToolTipExample extends JFrame {
private static final long serialVersionUID = 1L;
public ColoredToolTipExample() {
Border line, raisedbevel, loweredbevel, title, empty;
line = BorderFactory.createLineBorder(Color.black);
raisedbevel = BorderFactory.createRaisedBevelBorder();
loweredbevel = BorderFactory.createLoweredBevelBorder();
title = BorderFactory.createTitledBorder("");
empty = BorderFactory.createEmptyBorder(1, 1, 1, 1);
Border compound;
compound = BorderFactory.createCompoundBorder(empty, line);
UIManager.put("ToolTip.foreground", new ColorUIResource(Color.red));
UIManager.put("ToolTip.background", new ColorUIResource(Color.yellow));
UIManager.put("ToolTip.font", new FontUIResource(new Font("Verdana", Font.PLAIN, 18)));
UIManager.put("ToolTip.border", new BorderUIResource(compound));
JButton button = new JButton("Hello, world");
button.setToolTipText("<html> - myText <br> - myText <br> - myText <br>");
getContentPane().add(button);
JFrame frame = new JFrame("Colored ToolTip Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 100);
frame.setVisible(true);
}
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new ColoredToolTipExample();
}
});
}
}
I want to create scrollpane like on this picture:
With arrows on component sides and with no scrollbar visible. Only horizontal scrolling is needed. Can It be done with JScrollPane?
You can make you own component by using a scrollpane and by creating your own buttons that will use the Actions of the scrollbar:
import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.basic.*;
public class ScrollPaneSSCCE extends JPanel
{
public ScrollPaneSSCCE()
{
setLayout( new BorderLayout() );
JTextArea textArea = new JTextArea(1, 80);
textArea.setText("Hopefully this will answer your question");
JScrollPane scrollPane = new JScrollPane( textArea );
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
add(scrollPane);
JScrollBar horizontal = scrollPane.getHorizontalScrollBar();
BasicArrowButton west = new BasicArrowButton(BasicArrowButton.WEST);
west.setAction( new ActionMapAction("", horizontal, "negativeUnitIncrement") );
add(west, BorderLayout.WEST);
BasicArrowButton east = new BasicArrowButton(BasicArrowButton.EAST);
east.setAction( new ActionMapAction("", horizontal, "positiveUnitIncrement") );
add(east, BorderLayout.EAST);
}
private static void createAndShowUI()
{
JFrame frame = new JFrame("ScrollPaneSSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new ScrollPaneSSCCE(), BorderLayout.NORTH);
frame.setSize(100, 100);
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
You will also need to use the Action Map Action class.
I have a text editor with Netbeans where i load a text to a JtextPane. If text is too big u can read it with the help of an horizontal scroll.Is there any way to split the text into pages of 24 lines for example so that every page is visible without scrolling and use a next page button for changing page (like eBooks do)?
It is easier to use a JTextArea to do this because you can easily specify the number of lines to display each time you scroll to a new page.
The basic solution is to add a text area to a scroll pane than then hide the scrollbars. You can then use the defaults Actions of the vertical scrollbar to do the scrolling for you. The code below uses code from the Action Map Action blog entry to easily create an Action that you can add to a JButton:
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
public class TextAreaScroll extends JPanel
{
private JTextArea textArea;
public TextAreaScroll()
{
setLayout( new BorderLayout() );
textArea = new JTextArea(10, 80);
textArea.setEditable( false );
JScrollPane scrollPane = new JScrollPane( textArea );
scrollPane.setVerticalScrollBarPolicy( JScrollPane.VERTICAL_SCROLLBAR_NEVER );
add(scrollPane);
JButton load = new JButton("Load TextAreaScroll.java");
load.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
try
{
FileReader reader = new FileReader( "TextAreaScroll.java" );
BufferedReader br = new BufferedReader(reader);
textArea.read( br, null );
br.close();
}
catch(Exception e2) { System.out.println(e2); }
}
});
add(load, BorderLayout.NORTH);
// Add buttons to do the scrolling
JScrollBar vertical = scrollPane.getVerticalScrollBar();
Action nextPage = new ActionMapAction("Next Page", vertical, "positiveBlockIncrement");
nextPage.putValue(AbstractAction.MNEMONIC_KEY, KeyEvent.VK_N);
JButton nextButton = new JButton(nextPage);
Action previousPage = new ActionMapAction("Previous Page", vertical, "negativeBlockIncrement");
previousPage.putValue(AbstractAction.MNEMONIC_KEY, KeyEvent.VK_N);
JButton previousButton = new JButton(previousPage);
JPanel south = new JPanel();
add(south, BorderLayout.SOUTH);
south.add( previousButton );
south.add( nextButton );
}
private static void createAndShowUI()
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TextAreaScroll());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}