Working with the JTreeWithScrollbar example, but scaled it back significantly to focus on the issue.
The original code would have the vertical scrollbars appear as needed.
Here, there is plenty of space and no scrollbars are needed.
If the panel is moved enough, the scrollbar will appear.
Once the following line of code was added, the scrollbars stopped appearing.
tree.setUI(new MyTreeUI());
Notice no scrollbar.
If the above line of code is commented out, the vertical scrollbar appears.
Checking the documentation for BasicTreeUI and there isn't anything related to showing/hiding scrollbars.
2 Questions
1 - When utilizing the BasicTreeUI object, what is required to ensure the scrollbars still function?
2 - Why is it the Horizontal scrollbar never appears even if the line of code is commented out?
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTree;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.DefaultMutableTreeNode;
import java.awt.Dimension;
public class JTreeWithScrollbar extends JPanel {
private JEditorPane htmlPane;
private JTree tree;
public JTreeWithScrollbar()
{
//Create the nodes.
DefaultMutableTreeNode top = new DefaultMutableTreeNode("The Java Series");
DefaultMutableTreeNode book1Node = new DefaultMutableTreeNode("Book 1");
DefaultMutableTreeNode book2Node = new DefaultMutableTreeNode("Book 2");
top.add(book1Node);
top.add(book2Node);
tree = new JTree(top);
tree.setUI(new MyTreeUI()); ///Comment out this line of code and the vertical scrollbar appears.
JScrollPane treeView = new JScrollPane(tree);
JScrollPane htmlView = new JScrollPane(htmlPane);
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
splitPane.setTopComponent(treeView);
splitPane.setBottomComponent(htmlView);
Dimension minimumSize = new Dimension(100, 50);
htmlView.setMinimumSize(minimumSize);
splitPane.setDividerLocation(100);
splitPane.setPreferredSize(new Dimension(500, 300));
add(splitPane);
}
public static void main(String[] args)
{
//Create and set up the window.
JFrame frame = new JFrame("TreeDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel jp = new JPanel();
jp.add(new JTreeWithScrollbar());
frame.add(jp);
//Display the window.
frame.pack();
frame.setVisible(true);
}
private static class MyTreeUI extends BasicTreeUI
{
public MyTreeUI()
{
super();
}
#Override
protected void updateCachedPreferredSize() {
treeState.invalidateSizes();
tree.treeDidChange();
}
}
}
When utilizing the BasicTreeUI object, what is required to ensure the scrollbars still function?
As shown in the minimal example below, BasicTreeUI correctly shows each scroll bar when needed; resize the frame to see the effect.
Why does the horizontal scrollbar never appear even if the line of code is commented out?
After pack() the frame has been resized to adopt the preferred size of it content. Making the frame slightly smaller illustrates the effect. Your example adds the tree to a JPanel having a default FlowLayout which ignores preferred sizes; the example below adds the tree to the center of the frame's default BorderLayout which responds to preferred sizes.
I am assuming the updateCachedPreferredSize() must be doing other stuff behind the scenes…
Exactly. Each invocation of updateCachedPreferredSize() updates the component's preferred size to reflect any change in state (resize, expand, etc.); when the preferred size exceeds the viewport size, the scroll bars appear. As you observed, invoking super.updateCachedPreferredSize() allows normal operation, and any further customization must preserve that functionality.
In addition,
Expand rows as need like this.
Construct and manipulate Swing GUI objects only on the event dispatch thread.
Don't use setSize() when you really mean to override getPreferredSize() or illustrates a resize effect; more here.
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.plaf.basic.BasicTreeUI;
public class JTreeWithScrollbar {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
//Create and set up the window.
JFrame frame = new JFrame("TreeDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTree tree = new JTree(); //default model
for (int i = 0; i < tree.getRowCount(); i++) {
tree.expandRow(i);
}
tree.setUI(new MyTreeUI());
frame.add(new JScrollPane(tree));
//Display the window.
frame.pack();
frame.setSize(frame.getWidth() - 10, frame.getHeight() - 100);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
private static class MyTreeUI extends BasicTreeUI {
}
}
Related
This is the add(main) version
This is the add(scroll) version
Im trying to get a window full of lables and make it scrollable, this is my code for that purpose:
public class JobHistoryListScreen extends JFrame implements View
{
#Override
public void showScreen()
{
setSize(800, 800);
setLayout(new BorderLayout());
setDefaultCloseOperation(EXIT_ON_CLOSE);
JPanel main = new JPanel();
main.setSize(500,500);
JScrollPane scroll = new JScrollPane(main,JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scroll.setSize(500,500);
//Font
//Font david50 = new Font("David", Font.BOLD, 50);
for(int i=0; i<1000; i++)
{
JLabel empty = new JLabel("No jobs to display!");
empty.setBounds(0,i+250,400,100);
empty.setFont(david50);
main.add(empty);
}
add(main);
setVisible(true);
}
public static void main(String[] args) {
JobHistoryListScreen v = new JobHistoryListScreen();
v.showScreen();
}
}
For some reason the window gets filled with the labels but is not scrollable at all.
Learn about layout managers. Refer to Laying Out Components Within a Container. Default for JPanel is FlowLayout and because the JPanel is inside a JScrollPanel, the labels will not wrap. And since you set the horizontal scroll bar policy to NEVER, there is no horizontal scroll bar and hence you cannot scroll horizontally. Try using BoxLayout to display all the labels one under the other. Alternatively you could use a GridLayout with 0 (zero) rows and 1 (one) column. Refer to the tutorial for more details.
EDIT
Here is my modified version of your code. Explanatory notes appear after the code.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.WindowConstants;
public class JobHistoryListScreen implements Runnable {
private JFrame frame;
#Override // java.lang.Runnable
public void run() {
showScreen();
}
public void showScreen() {
frame = new JFrame("Jobs");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JPanel main = new JPanel(new GridLayout(0, 1));
JScrollPane scroll = new JScrollPane(main,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scroll.setPreferredSize(new Dimension(500, 500));
Font david50 = new Font("David", Font.BOLD, 50);
for(int i=0; i<1000; i++) {
JLabel empty = new JLabel("No jobs to display!");
empty.setFont(david50);
main.add(empty);
}
frame.add(scroll);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
JobHistoryListScreen v = new JobHistoryListScreen();
// Launch Event Dispatch Thread (EDT)
EventQueue.invokeLater(v);
}
}
I don't know what interface View is so I removed that part.
No need to extend class JFrame.
No need to explicitly call setSize() on JFrame. Better to call pack().
Default content pane for JFrame is JPanel and default layout manager for that JPanel is BorderLayout so no need to explicitly set.
No need to call setSize() on JPanel.
Call setPreferredSize() rather than setSize() on JScrollPane.
Add the JScrollPane to the JFrame and not the JPanel.
No need to call setBounds() because GridLayout handles this.
Explicitly launch EDT (Event Dispatch Thread) by calling invokeLater().
Here is a screen capture of the running app. Note the vertical scroll bar.
I have a JPanel with layout set to null and the background is white. Then I added that JPanel to JScrollPane.
import java.awt.Color;
import java.awt.Dimension;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
public class TestJScollPane extends JPanel {
private static final long serialVersionUID = 1L;
public TestJScollPane() {
initUI();
}
private void initUI()
{
JScrollPane scrollPane = new JScrollPane();
scrollPane.getViewport().setBackground(Color.GRAY);
scrollPane.setBounds(1, 1, 200, 200);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
setBorder(BorderFactory.createEtchedBorder(Color.GREEN, Color.MAGENTA));
setBackground(Color.WHITE);
setLayout(null);
JFrame frame = new JFrame();
scrollPane.setViewportView(this);
frame.add(scrollPane);
frame.setLayout(null);
frame.setVisible(true);
frame.setSize(800, 500);
frame.getContentPane().setBackground(Color.BLACK);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(30,30);
}
public static void main(String[] args)
{
new TestJScollPane();
}
}
My scenario is I have a zoom tool that if I zoom out the JPanel and all its shapes that were painted were scaled using AffineTransform. So I expect that if I zoom out, the background of JScrollPane was color gray but the actual was color white.
Apologies, I added a sample. Actually, this is not the actual code I created this so that I can provide a sample for you guys to help me.
I set the preferred size of JPanel to 30x30 so I expect that the background of JScrollPane will become visible but it was not.
Thanks in advance for any help.
By default the panel is sized to fit the viewport so you will not see the background of the viewport.
You need to implement the Scrollable interface of your JPanel to tell the scroll pane you want the panel displayed at its preferred size.
Or, instead of implementing the Scrollable interface yourself you can use the Scrollable Panel which, by default, will display the panel at its preferred size.
Changes to your code would be:
ScrollablePanel panel = new ScrollablePanel();
panel.setPreferredSize( new Dimension(30, 30) );
panel.setBorder(BorderFactory.createEtchedBorder(Color.GREEN, Color.MAGENTA));
panel.setBackground(Color.WHITE);
//panel.setLayout(null);
scrollPane.setViewportView(panel);
//scrollPane.setViewportView(this);
Change:
scrollPane.setViewportView(this);
To something like:
JPanel centerPanel = new JPanel(new GridBagLayout());
centerPanel.add(this);
scrollPane.setViewportView(centerPanel);
A GridBagLayout (by default, unless configured otherwise) will respect the preferred size of the child components and won't stretch them to fill the 'cell'. A scroll pane on the other hand, will stretch the content to (at least) fill the visible area.
Result:
But seriously, drop the use of null layouts. If the effect cannot be achieved using an existing layout (inbuilt or 3rd party) or a combination of layouts, it must have such esoteric positioning constraints that it deserves a custom layout manager.
I am writing a program that attempts to simulate the evolution of a species, and it has a window that looks like this:
Originally the empty area in the bottom right was a Panel, and it is intended to draw a visual representation of the specimens, locations, and travel paths(doesn't really matter). However, you will be able to open some sort of window that allows you to create/edit different items(like species, locations, and travel paths). Originally I planned for those to simply be popup windows. But, I was thinking I would perhaps use JInternal panes for the popups, and the visual representation screen.
So in my JFrames constructor:
JDesktopPane pane = new JDesktopPane();
this.setContentPane(pane);
setLayout(new BorderLayout());//To layout the menubar, and the items on the left
panel = new GraphicsPanel(manager);
panel.setVisible(true);
And in Graphics Panel constructor:super("Graphic Project View",true,false,true,true);
This locks the Panel to BorderLayout.CENTER, and it fills up the entire space, not allowing for anything else. My guess this is because JDesktopPanes use an OverlayLayout, and when I set the layout to BorderLayout that overrides the OverlayLayout, and so my InternalFrame just gets added to the center.
So the question is:
How do I layout the things like the JMenuBar, and the left ward Panel as they are now, whilst still maintaining the capability to have JInternalFrames?
For now I am going to add the JMenuBar via JFrame.setJMenuBar(JMenuBar) instead of JFrame.add(menuBar,BorderLayout.NORTH), and then change the panel on the left into a JInternal frame, but if possible I'd rather have it as is. I would like it if I could just have the DesktopPane be added to the JFrame at BorderLayout.CENTER, and then just add the frame to the Desktop pane. If the InternalFrame were limited to that region I wouldn't care, as long as it's still mobile, ect.
EDIT: How I add JInternalFrame(Sorry it still says panel, but it has been converted to a JInternalFrame):
panel = new GraphicsPanel(manager);
panel.setSize(desktop.getSize());
panel.setLocation(0,0);
panel.setVisible(true);
desktop.add(panel);
I would start with a single JPanel (lets all it the base pane), which will house the other containers.
Using a border layout, I would add a "controls" panel to the WEST position of the base pane. Onto the CENTER position I would add the JDesktopPane.
I would set the main windows layout to BorderLayout and add the base pane to it. This will allow you to use JFrame#setJMenuBar to manage the menu bar while maintaining the result of the layout.
This will allow you to contain to use the JInternalFrames on the desktop without effecting the rest of the layout...
Simple Example
This is an overly simplified example used to demonstrate the basic concept described above...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class SimpleLayout {
public static void main(String[] args) {
new SimpleLayout();
}
public SimpleLayout() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JMenuBar mb = new JMenuBar();
mb.add(new JMenu("File"));
mb.add(new JMenu("Add"));
mb.add(new JMenu("Edit"));
mb.add(new JMenu("Analize"));
mb.add(new JMenu("About"));
JFrame frame = new JFrame("Testing");
frame.setJMenuBar(mb);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new BasePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class BasePane extends JPanel {
private JTextArea species;
private JTextArea locations;
private JTextArea travelPaths;
private JDesktopPane desktopPane;
public BasePane() {
setLayout(new BorderLayout());
desktopPane = new JDesktopPane();
species = new JTextArea("Species");
locations = new JTextArea("Locations");
travelPaths = new JTextArea("TravelPaths");
JPanel controls = new JPanel(new GridLayout(3, 0));
controls.add(new JScrollPane(species));
controls.add(new JScrollPane(locations));
controls.add(new JScrollPane(travelPaths));
add(controls, BorderLayout.WEST);
add(desktopPane);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
Your requirements might be slightly difference, but the basic concept should get you moving.
Depending on the structure of your application, I might be tempted to separate the Controls pane into a separate class as well.
I want to put FlowLayout with lets say 5 labels inside BorderLayout as north panel (BorderLayout.NORTH), and when I resize my window/frame I want the labels to not disappear but instead move to new line.
I have been reading about min, max values and preferredLayoutSize methods. However they do not seem to help and I am still confused.
Also, I would not like to use other layout like a wrapper or something.
One of the annoying things about FlowLayout is that it doesn't "wrap" it's contents when the available horizontal space is to small.
Instead, take a look at WrapLayout, it's FlowLayout with wrapping...
The following code does exactly what you asked.
The program has a frame whose contentPane is set for BorderLayout. It contains another panel flowPanel that has a flow layout and is added to the BorderLayout.NORTH.
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class PanelFun extends JFrame {
final JPanel flowPanel;
public PanelFun() {
setPreferredSize(new Dimension(300,300));
getContentPane().setLayout(new BorderLayout());
flowPanel = new JPanel(new FlowLayout());
addLabels();
getContentPane().add(flowPanel, BorderLayout.NORTH);
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
PanelFun.this.getContentPane().remove(flowPanel); //this statement is really optional.
PanelFun.this.getContentPane().add(flowPanel);
}
});
}
void addLabels(){
flowPanel.add(new JLabel("One"));
flowPanel.add(new JLabel("Two"));
flowPanel.add(new JLabel("Three"));
flowPanel.add(new JLabel("Four"));
flowPanel.add(new JLabel("Five"));
}
public static void main(String[] args) {
final PanelFun frame = new PanelFun();
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.pack();
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
frame.setVisible(true);
}
});
}
}
So, how does it work?
The key to having the components inside flowPanel realign when the frame is resized is this piece of code
PS: Let me know if you are new to Swing and do not understand some part of the code.
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
PanelFun.this.getContentPane().remove(flowPanel);
PanelFun.this.getContentPane().add(flowPanel);
}
});
Without this code the flowPanel will not realign its components as it is not its normal behaviour to reposition components when the containing frame is resized.
However, it is also its behaviour that when flowPanel is added to a panel, it would position components as per the available space. So, if we add the flowPanel everytime the frame resizes, the inner elements will be repositioned to use the available space.
Update:
As camickr pointed out correctly, this method will not work in case you add anything to the center (BorderLayout.CENTER)
For a GUI application I am creating in Java, I have the following:
A JFrame, set to have a minimum size of (300,200)
A JSplitPane, in which lies:
On the left, a JScrollPane (containing a JTree) with a minimum size of (100,0) (I only want to restrict the width to 200)
On the right, a JPanel with a minimum size of (200,0)
The sizing does not give me any issue under the following conditions:
Resizing the JSplitPane all the way to the left (to the JScrollPane's minimum size), and subsequently resize the window afterward
Just resizing the window, to a certain degree
The problem occurs when I move the JSplitPane too close to the right, whereupon resizing the window the JPanel in the right of the JSplitPane fails to adhere to the minimum width I set.
I attempted setting a maximum width on the JScrollPane, which did not seem to help at all.
Is there something involving maximum sizes I must do? Or perhaps there is a way to attach a Listener to one of the panels to force my desired behavior? Ultimately, I just want the right panel in the JSplitPane to never be less than 200px wide.
Here is an example with behavior I am experiencing:
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTree;
public class ResizeTest
{
private JFrame frame;
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
try
{
ResizeTest window = new ResizeTest();
window.frame.setVisible(true);
}
catch(Exception e)
{
e.printStackTrace();
}
}
});
}
public ResizeTest()
{
initialize();
}
private void initialize()
{
frame = new JFrame();
frame.setMinimumSize(new Dimension(300, 200));
frame.setBounds(100,100,450,300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new GridLayout(0, 1, 0, 0));
JSplitPane splitPane = new JSplitPane();
frame.getContentPane().add(splitPane);
JScrollPane scrollPane = new JScrollPane();
scrollPane.setMinimumSize(new Dimension(100, 0));
splitPane.setLeftComponent(scrollPane);
JTree tree = new JTree();
tree.setMinimumSize(new Dimension(100, 0));
scrollPane.setViewportView(tree);
JPanel panel = new JPanel();
panel.setMinimumSize(new Dimension(200, 0));
splitPane.setRightComponent(panel);
}
}
Update:
I'm afraid I don't fully understand the point trying to be made in the proposed solutions, except for that setPreferred() and setMinimum/Maximum() are better to be avoided.
My question in response to learning this is, what are my options for restricting the JSplitPane divider outside of using these methods? MadProgrammer mentioned listening for the componentResized event, but I need just a little more clarification as to why. Am I calling setDividerLocation() in response to this event?
I apologize in advance if the appended question is meant as a separate StackOverflow question entirely, I can post another and link here if necessary.
Update 2:
Is simply not regulating how the user chooses to size the window and having the right panel in a JScrollPane a viable option? This looks to me like somewhat of a standard practice.
Firstly, the method setMinimumSize is a suggestion to the LayoutManager API. A suggestion that may be ignored.
In order to be able to even come close to supporting this, you will need to use something like a ComponentListener and monitor the componentResized event.
The best solution I can think of is to use a LayoutManager that actually uses the minimum and maximum size constraints, something like GridBagLayout.
Use this on a "content" pane and place you're JSplitPane onto this (setting it's minimum and maximum size accordingly) then add the "content" pane to frame.
UPDATE
Sorry, I'm probably missing something really obvious, but I put this little test together, I hope it has some ideas that help :P
public class TestFrameSize extends JFrame {
public TestFrameSize() throws HeadlessException {
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(600, 600);
setLocationRelativeTo(null);
setMinimumSize(new Dimension(250, 250));
JLabel left = new JLabel("Left");
JLabel right = new JLabel("Right");
Dimension pSize = new Dimension(100, 100);
Dimension mSize = new Dimension(25, 100);
left.setPreferredSize(pSize);
left.setMinimumSize(mSize);
right.setPreferredSize(pSize);
right.setMinimumSize(mSize);
JSplitPane pane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, right);
JPanel content = new JPanel(new GridBagLayout());
content.add(pane);
setLayout(new BorderLayout());
add(content);
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
new TestFrameSize().setVisible(true);
}
}
In this example, the preferred size of the content of a JSplitPane is initially set to a small multiple of the component's preferred size in getPreferredSize(). Note that the JSplitPane will become no smaller than the panel's minimum size, managed by the enclosed Jlabel.