JScrollPane & Graphics2D - java

I am trying to draw graphics that is bigger than the JFrame and use JScrollPane to scroll the entire graphics. I created a simple example with two lines. The scroll bars appear but the graphics do not show.
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
public class Test extends JPanel{
public static void main(String... args) {
Test test = new Test();
JFrame frame = new JFrame();
JPanel panel = new JPanel();
panel.add(test);
JScrollPane scrollPane = new JScrollPane(panel);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setBounds(0, 0, 1350, 700);
JPanel contentPane = new JPanel(null);
contentPane.setPreferredSize(new Dimension(1400, 700));
contentPane.add(scrollPane);
frame.setContentPane(contentPane);
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
#Override
protected void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D)g;
g2.drawLine(30,30,30,3000);
g2.drawLine(30, 400, 500, 3000);
}
}

Welcome to a wonderful example of why null layouts suck...
Avoid using null layouts, pixel perfect layouts are an illusion within modern ui design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify
Also see Why is it frowned upon to use a null layout in SWING? for more details...
The basic problem is, the JScrollPane, has a JViewport, which actually contains your component. The JViewport uses your components sizing hints to make determinations about how big it should be and the JScrollPane uses the decisions the JViewport makes to make determinations about whether it needs to display the scrollbars or not.
The JViewport is taking a look at your component and has decided, because you've not told it otherwise, that it should be 0x0 in size.
You can prove this by adding a LineBorder to your component, setBorder(new LineBorder(Color.RED));, you won't see it either (or if you do, it will be a little red square)
Start by overriding the getPrefferedSize method of the Test panel and return some appropriate size
Next, call super.paintComponent before you perform any custom painting, otherwise you'll end up with some awesome, but annoying, paint artifacts...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test extends JPanel {
public static void main(String... args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
Test test = new Test();
JFrame frame = new JFrame();
JScrollPane scrollPane = new JScrollPane(test);
frame.add(scrollPane);
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(3000, 3000);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.drawLine(30, 30, 30, 3000);
g2.drawLine(30, 400, 500, 3000);
}
}
You'll probably want to take a look at the Scrollable interface next, so you can control the default size of the JViewport, so it won't try and fill the entire screen.
Take a look at Implementing a Scrolling-Savvy Client for more details

The problem comes from the lines
JPanel panel = new JPanel();
panel.add(test);
JScrollPane scrollPane = new JScrollPane(panel);
You are adding test to panel which uses FlowLayout by default. This layout does not strech the components in it, so test on which you draw has dimensions 0x0 and what you see in the scroll pane is the empty panel.
To fix this you can set panel to use BorderLayout which stretches the center component:
JPanel panel = new JPanel(new BorderLayout());
panel.add(test);
JScrollPane scrollPane = new JScrollPane(panel);
or add test directly to the scroll pane:
JScrollPane scrollPane = new JScrollPane(test);
Additionally:
Always call super.paintComponent(g) as the first line when overriding paintComponent.
Don't use null layouts (and consequently don't set bounds on components).
When you use setPreferredSize remember that if the dimensions are too large they will "flow off" the screen.

Related

Nothing happens when setting the background of JScrollPane.getViewPort()

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.

Trouble with borders

I'm trying to get clarification as to how borders work, specifically the insets, and in searching through the Java docs and numerous websites I can't seem to find a clear explanation. Looking at this code:
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
public class ShadowWindow {
public static void main(String[] args) {
new ShadowWindow();
}
public ShadowWindow() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setUndecorated(true);
frame.setBackground(new Color(0, 0, 0, 0));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(new ShadowPane());
JPanel panel = new JPanel(new GridBagLayout());
panel.add(new JLabel("Look ma, no hands"));
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ShadowPane extends JPanel {
public ShadowPane() {
setLayout(new BorderLayout());
setOpaque(false);
setBackground(Color.BLACK);
setBorder(new EmptyBorder(0, 0, 10, 10));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive(0.5f));
g2d.fillRect(10, 10, getWidth(), getHeight());
g2d.dispose();
}
}
}
The way I interpret it this is what is happening:
JFrame frame (200 x 200) is created
JPanel shadowPane is created (also 200 x 200) with an empty border of 10 pixels created on the inside bottom and inside right of the JPanel
A second JPanel is created (200 x 200) and added on top of shadowPane
A rectangle is drawn (200 x 200) starting at x = 10 and y = 10
So my question in how is the shadowPane going past the range of the JFrame? Does the border go 10 pixels outside the JFrame or does it exist inside the JFrame. From everything I've found it should be inside, but that doesn't make sense based on how this code generates a shadow behind the frame. can anybody walk me through this? Thanks.
So my question in how is the shadowPane going past the range of the JFrame?
It's not. pack determines the preferred layout size of the content and makes the window big enough to accommodate it, because the frame is undercoated AND it's background is transparent, it "appears" as if the shadow hangs past the frame, it's an illusion.
The empty border is making sure that content added to the ShadowPane is "forced" into a small space.
Lets change the code slightly...
JFrame frame = new JFrame("Testing");
JPanel content = new JPanel(new BorderLayout());
content.setBackground(Color.RED);
//frame.setUndecorated(true);
//frame.setBackground(new Color(0, 0, 0, 0));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(content);
//frame.setContentPane(new ShadowPane());
JPanel panel = new JPanel(new GridBagLayout());
panel.add(new JLabel("Look ma, no hands"));
ShadowPane shadowPane = new ShadowPane();
shadowPane.add(panel);
frame.add(shadowPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
What this does, is creates a new background panel, filled with the color red. It also adds the window decoration back in.
As you can see, the shadow pane and the label are are all rendered within the confines of the window.
If we once again remove the window decoration...
You can see that it's still the same...
So what's going on?
getPreferredSize is providing the core information about how the component would like to be size (in this case 200x200)
The EmptyBorder is defining a usable space within the ShadowPane which defines an area within which content can be displayed, it's leaving 10 pixels to the right and bottom of the component, in which components can't be displayed. This is take care of automatically by the layout manager. This means that the ShadowPane can actually paint here itself, but components added to it will never be displayed here, hence the shadow board.
Basically, it's smoke and mirrors and used to generate the illusion of a drop shadow behind the content added to the frame (or the ShadowPane in this case)

Java - Set size of decorated JFrame

I've got a problem with the size of a JFrame:
I want to show content in the Jframe. The content will have the size 640 x 480. But I can not use the method JFrame.setSize(640, 480); because I also want to show the decoration of the window.
My first thought was: Add a panel with the preferred dimension and than use pack();. But this also doesn't work - I only get a really small window.
I think the solution to my problem could be similar to this:
String JFrame Size
How do I make a JFrame a certain size not including the border
Here is my code:
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Fenster extends JFrame {
JPanel panel;
Dimension dim;
Fenster(){
dim = new Dimension(640, 480);
panel = new JPanel();
panel.setSize(dim);
panel.setMinimumSize(dim);
panel.setMaximumSize(dim);
panel.setPreferredSize(dim);
panel.setBounds(0, 0, 640, 480);
panel.setDoubleBuffered(true);
JLabel label = new JLabel("bla");
panel.add(label);
this.setLayout(null);
this.getContentPane().add(panel);
this.setLocationRelativeTo(null);
this.setResizable(false);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.pack();
this.setVisible(true);
}
}
this.setLayout(null); is your problem. Let the layout manager do it's job
From the JavaDocs
public void pack() Causes this Window to be sized to fit the
preferred size and layouts of its subcomponents. The resulting width
and height of the window are automatically enlarged if either of
dimensions is less than the minimum size as specified by the previous
call to the setMinimumSize method. If the window and/or its
owner are not displayable yet, both of them are made displayable
before calculating the preferred size. The Window is validated after
its size is being calculated.
Most containers (JComponent, JPanel) have a default, preferred size of 0x0, the layout manager provides this information based on the requirements of the layout manager itself and the contents of the container, but using a null, you effectivly make the container 0x0...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setUndecorated(true);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
add(new JLabel("Look ma, no null layouts!"));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(640, 480);
}
}
}
Avoid using null layouts, pixel perfect layouts are an illusion within modern ui design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify
See Why is it frowned upon to use a null layout in SWING? for more details...

How do I draw over top of a JScrollPane?

I have a JScrollPane that fills a JPanel (which is the content pane for my JFrame). The JPanel performs custom drawing - however, it doesn't appear over top of the JScrollPane. Should I override something other than paintComponent?
Here is a demo:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.LayoutManager;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
// Create the frame.
JFrame frame = new JFrame();
frame.setPreferredSize(new Dimension(1024, 768));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel custom = new CustomPanel(new BorderLayout());
// Add the scroll pane.
JScrollPane scroll = new JScrollPane();
scroll.setBorder(BorderFactory.createLineBorder(Color.blue));
custom.add(scroll, BorderLayout.CENTER);
// Display the frame.
frame.setContentPane(custom);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
#SuppressWarnings("serial")
class CustomPanel extends JPanel {
public CustomPanel(LayoutManager lm) {
super(lm);
}
#Override
public void paintComponent(Graphics g) {
g.setColor(Color.red);
g.drawRect(200, 200, 200, 200);
}
}
I'd like for the paint on the JPanel to go over top of the paint on
the JScrollPane
you can to paint to the
JViewport as you can to see here and here
use JLayer(Java7) based on JXlayer(Java6)
very similair (as todays JLayer) is painting to GlassPane, notice GlassPane to consume()(by default) MouseEvent in the case there is(are) added some JComponent(s), GlassPane can to covers whole RootPane or only part of available Rectangle, depends of used LayoutManager and Dimension returns from layed JComponent(s)
Just a simple problem. You are adding the scrollpane to the custompanel which is hiding what your are drawing. Instead, consider intializing your scrollpane with the cutsompanel as its content.
Example:
JScrollPane scrlPane = new JScrollPane(customPanel);
when you add a single component to a BorderLayout and specify BorderLayout.CENTER, the component will expand to completely fill its parent. If the component is opaque, you won't be able to see any custom painting you are doing in the parent.
The way that worked (as #mKorbel suggested) is to play with the JViewport:
scroll.setViewport(new CustomViewPort());
where CustomViewPort is a class extending JViewport that overrides the paintComponent method.

JScrollPane scrollbars not scrollable

I have a class that draw some very simple graphics like lines, circles and rectangles. The lines are dynamically expandable and sometimes when they expand beyond the resolution, it is impossible to see without a scroll bar. Therefore, I've added JScrollPane to my JFrame but unfortunately, the scroll bar is not scrollable despite calling the Layout Manager already.
Here's what I have:
- A class that draws components (lines, rectangles, circles)
- A class that sets up the JFrame/JScrollPane
Here's an excerpt code of my GUI class:
JFrame frame = new JFrame("GUIFrame");
frame.setLayout(new BorderLayout()); // Layout already set
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
DrawComponent comp = new DrawComponent(); // Reference to class that draw components
JScrollPane sp = new JScrollPane(comp, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
sp.setPreferredSize(new Dimension(1000, 1000));
frame.add(sp, BorderLayout.CENTER);
frame.setSize(500,500);
frame.setVisible(true);
With the code above, I've got Java to show me a JFrame with scrollpane containing my jcomponents. I have set the scrollbars to always appear as shown above but they are not scrollable, gray-ed out.
As suggested by Andrew, I took sometime to create a SSCCE to reflect what I'm trying to do:
import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.util.Random;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
public class DrawTest {
public static void main(String[] args){
JFrame frame = new JFrame("SSCCE");
frame.setLayout(new BorderLayout());
frame.setSize(1000, 1000);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
DrawComp d = new DrawComp();
JScrollPane sp = new JScrollPane(d, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
frame.add(sp);
frame.setVisible(true);
}
}
class DrawComp extends JComponent {
public void paintComponent(Graphics g){
Graphics2D g2 = (Graphics2D)g;
Random ran = new Random();
int ranNum = ran.nextInt(10);
System.out.println(ranNum);
double length = 100 * ranNum;
g2.draw(new Line2D.Double(10, 10, length, length));
}
}
The code above draws a diagonal line based on a random input. What I intend to do is that when the line gets so long that it goes out of the frame size, I hope that I'll be able to scroll and have a view of the full line. Again I have added the line component to JScrollPane but it's not scroll-able.
My apologies. You are using JScrollPane the right way. I ran your code and I think I got the reason why JScrollPane is not working. Picture this, when your set the jframe's background to all red (by paiting a red dot everywhere) should the jscrollpane be scrollable? No, because painting the background color is in the background. The actual VIEW isnt changing and it is not bigger then the display size so the scrollpane does not see the point of scrolling. Your paint component method is doing something similar. It is just drawing something in the background. The actual VIEW didnt change so scrollpane wont work.
public class DrawTest {
public static void main(String[] args){
JFrame frame = new JFrame("SSCCE");
frame.setLayout(new BorderLayout());
frame.setSize(500, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final DrawComp d = new DrawComp();
final JScrollBar hbar,vbar;
hbar = new JScrollBar(JScrollBar.HORIZONTAL, 0, 1, 0, 500);
vbar = new JScrollBar(JScrollBar.VERTICAL, 0, 1, 0, 500);
frame.setLayout(null);
frame.add(d);
frame.add(hbar);
frame.add(vbar);
d.setBounds(0, 0, 300, 300);
vbar.setBounds(460, 0, 20, 480);
frame.setVisible(true);
vbar.addAdjustmentListener(new AdjustmentListener()
{
public void adjustmentValueChanged(AdjustmentEvent e)
{
d.setLocation(d.getX(), -vbar.getValue());
}
});
}
}
Here is the code for sliding a component vertically. I made some changes to your existing code. The DrawComp is still the same
I have found a makeshift solution to my problem. Given how simple the drawing is, I have decided to use the end point of the line to override preferredSize.
Pertaining to the SSCCE I posted above, I added a setPreferredSize(new Dimension(length, length) in paintComponent() so that the preferredSize will always be the end point of the line when it is called. This makes sure that I have got the whole area of the painting covered and yet be able to scroll, should there be a need to.

Categories

Resources