Show multiple JPane images on a single JFrame - java

First off, I am new to JFrame and all the associated classes so I am still learning how to do this.
My current goal is to draw multiple images on a single JFrame. So far, I can get test2.png to draw, but not test1.png. Any suggestions or help understanding JFrame is appreciated. This is my main class:
package com.osj.oneshotjava;
import java.awt.Dimension;
import javax.swing.JFrame;
/**
*
* #author BCG04
*/
public class actorTest {
public static void main(String []args){
JFrame jFrame = new JFrame("OSJ actor test");
jFrame.setPreferredSize(new Dimension(640, 480)); // sets window size
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Actor2 Background = new Actor2(jFrame, "test1.png");
Actor2 testActor = new Actor2(jFrame, "test2.png");
jFrame.pack(); // automatically adjusts window size (also sets window size based on the maximum and minimum sizes)
jFrame.setVisible(true);
}
}
And this is Actor2:
package com.osj.oneshotjava;
import java.awt.BorderLayout;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
/**
*
* #author BCG04
*/
public class Actor2 { //Purpose: make it easer to add multiple images to a single JFrame using only a single call to Actor2's constuctor rather than repeating the same section of code for each image.
private BufferedImage image = null;
private JLabel jLabel = null;
public Actor2(JFrame jFrame, String filename){
try
{ // try to load a image 'filename' into 'image'
image = ImageIO.read(new File(filename));
}
catch (Exception e)
{
e.printStackTrace(); // if loading fails, print the error
System.exit(1); // then exit with an error code 1 'unsuccessful exit'
}
ImageIcon imageIcon = new ImageIcon(image); // create a new ImageIcon that contains 'image'
JPanel jPanel = new JPanel();
jLabel = new JLabel();
jLabel.setIcon(imageIcon); // set JLabel 'jLabel' to contain 'imageIcon'
jPanel.add(jLabel);
jFrame.getContentPane().add(jPanel, BorderLayout.CENTER); // makes window visible?
}
public JLabel getJLabel(){
return jLabel;
}
}
Edit:
-removed Thread.sleep(1000); and setLocation(90, 90); since they were not relevant to the question or the problem and I originally had them in to test whether I could move images.
-removed jLabel.setBounds as it did not seem to do anything.
+added a comment clarifying Actor2's goal.
I'd like to clarify my end goal, I would like to create a simple 2d game that uses Java.

Here is a complete, self contained example that is close to what you're after. It is to demonstrate the use of a layout manager.
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
public class DuelingJLabels{
public static void startGui(){
JFrame frame = new JFrame();
JPanel scene = new JPanel();
Actor red = new Actor(Color.RED);
Actor blue = new Actor(Color.BLUE);
//scene.setLayout( null );
scene.add(red.image);
scene.add(blue.image);
//scene.setPreferredSize( new Dimension(512, 512) );
frame.add(scene, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
static class Actor{
int x, y;
JLabel image;
public Actor(Color c){
BufferedImage a = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB);
Graphics g = a.getGraphics();
g.setColor(c);
g.fillOval(0, 0, 64, 64);
image = new JLabel();
image.setIcon(new ImageIcon(a));
image.setLocation( x, y );
image.setSize( 64, 64);
image.addMouseListener( new MouseAdapter(){
#Override
public void mouseClicked(MouseEvent evt){
x = x+64;
if(x>=448){
x = 0;
y += 64;
}
image.setLocation(x, y);
}
});
}
}
public static void main(String[] args){
EventQueue.invokeLater( DuelingJLabels::startGui );
}
}
Take note of the line scene.setLayout(null); if you run the example with that line commented out, then you will see two circles side by side. That is because we are letting swing handle the layout. scene is a JPanel with a FlowLayout by default.
Now when you click the circles, nothing happens* because we tell the new position but the layout manager resets the position.
*Actually they move sometimes, but if you trigger a re-validation then they get moved by the layout manager.
So now remove the comment on scene.setLayout(null); and notice the difference.
The frame is tiny, and we have to manually resize it to see our scene.
There is only one circle.
If you click on the circle, it moves.
That's because we have told swing to not use a layout manager for the JPanel scene. That means it will not reposition the components in the scene for us, and it will not adjust the sizes for us either.
The other line that is commented setPreferredSize makes scene tell the parent component a size it would like to be at. If you uncomment that line then the JFrame will not start out incredibly small. You should only use that with custom components, otherwise you can end up conflicting with the layout manager.
Another tool, which I have found usefull is the JLayeredPane because it gives you some depth. I also think the example is good.
Finally, another technique for putting custom graphics arbitrarily is to #Override paintComponent. That way you can draw whatever, where-ever on your component.

Related

How do I get both my image and button to display?

Currently, the problem I am trying to solve is how I get both my image and button to show up.
When I have the following line in the code the image shows up but when I remove it my image doesn't display but the button does:
setLayout (new FlowLayout()) ;
without the line of code
with the line of code
Images for example ^
import java.awt.*;
public class Panel extends JFrame {
private ImageIcon FirstPageImage;
private JLabel FirstPageLabel;
private JLayeredPane SignupButtonLayer;
private JButton Button;
public Panel(){
setLayout (new FlowLayout()) ;
FirstPageImage = new ImageIcon(getClass().getResource("FirstPageAnimationUsing.gif"));
FirstPageLabel = new JLabel(FirstPageImage);
FirstPageImage.setImage(FirstPageImage.getImage().getScaledInstance(343,820,Image.SCALE_DEFAULT));
add(FirstPageLabel);
Button = new JButton();
SignupButtonLayer = new JLayeredPane();
Button.setOpaque(true);
Button.setBackground(Color.cyan);
Button.setBounds(94,617,159,82);
SignupButtonLayer.add(Button, JLayeredPane.DEFAULT_LAYER);
add(SignupButtonLayer);
}
public static void main(String[] args) {
Panel gui = new Panel();
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gui.setVisible(true);
gui.pack();
gui.setTitle("Reminder App");
gui.setSize(360,850);
}
}
Refer to How to Use Layered Panes.
You need to give the JLayeredPane a preferred size. Since your JLayeredPane contains only a single JButton, that size should be big enough to display the entire JButton.
The arguments to method setBounds – that you call on Button – are relative to its container, i.e. SignupButtonLayer. Setting the x to 94 and the y to 617 means that Button is placed outside of the bounds of SignupButtonLayer. Hence you don't see it. In the below code, I set x and y both to 0 (zero) so that the top, left corner of Button aligns with the top, left corner of SignupButtonLayer.
No need to explicitly call method setOpaque(true) for Button since that is the default, anyway.
Either call pack() – which is usually preferred – or setSize() but don't call both.
setVisible(true) should be called only once your GUI is completely built. In the below code I call it after calling pack() and setTitle().
I suggest that you try to adhere to Java naming conventions.
I also suggest that try not to name your classes the same as classes in the JDK. I am referring to Panel.
The below code simply resolves your problem, i.e. displaying both the image and the button together – while using FlowLayout for the [content pane of the] JFrame. Notice that the preferred size of SignupButtonLayer is slightly larger than the size arguments in method setBounds.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Image;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
public class Panel extends JFrame {
private ImageIcon FirstPageImage;
private JLabel FirstPageLabel;
private JLayeredPane SignupButtonLayer;
private JButton Button;
public Panel() {
setLayout(new FlowLayout());
FirstPageImage = new ImageIcon(getClass().getResource("FirstPageAnimationUsing.gif"));
FirstPageLabel = new JLabel(FirstPageImage);
FirstPageImage.setImage(FirstPageImage.getImage().getScaledInstance(343, 820, Image.SCALE_DEFAULT));
add(FirstPageLabel);
Button = new JButton();
SignupButtonLayer = new JLayeredPane();
SignupButtonLayer.setPreferredSize(new Dimension(160, 90));
// Button.setOpaque(true);
Button.setBackground(Color.cyan);
Button.setBounds(0, 0, 159, 82);
SignupButtonLayer.add(Button, JLayeredPane.DEFAULT_LAYER);
add(SignupButtonLayer);
}
public static void main(String[] args) {
Panel gui = new Panel();
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gui.pack();
gui.setTitle("Reminder App");
// gui.setSize(360, 850);
gui.setVisible(true);
}
}

JButtons are painted over by an image

I'm trying to make a main menu for the board game risk. There's a custom background image, and ideally I would like the buttons to appear over the image. However when I run my code, only the button called "New Game" appears, and the other buttons will appear if you hover the mouse over them. I have tried just about everything (there are similar problems on here) but can't seem to fix the problem. Maybe it has something to do with my code? I appreciate any help/suggestions!
package View;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.SpringLayout;
/**
* These classes set up the GUI of the Risk program.
* The main menu, dialog for setting player count, dialog for name/color settings for each
* player, the Risk game board, and a menu used during a Risk game session are included.
**/
public class Menu extends JFrame {
private JPanel mainPanel;
private JButton newGameButton;
private JButton loadGameButton;
private JButton quitButton;
private JButton ruleButton;
private String newGameButtonName = "newGameBtn";
private String loadGameButtonName = "loadGameBtn";
private String quitButtonName = "quitBtn";
private String ruleButtonName = "rulebtn";
//private SpringLayout mainLayout;
private static BufferedImage img;
/**
* Constructs the main menu.
**/
public Menu()
{
add( mainMenu( ) );
//setTitle("Risk: UConn Edition");
setPreferredSize(new Dimension(640, 700));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setResizable(false);
toFront();
pack();
setVisible(true);
}
/**
* creates the buttons for the jPanel
*
* #return
*/
private JPanel mainMenu()
{
// Creates the panel
mainPanel = new JPanel();
// Sets Layout
//mainLayout = new SpringLayout();
mainPanel.setLayout(null);
// Creates buttons
newGameButton = new JButton("New Game");
newGameButton.setBounds(20,300,150,50);
newGameButton.setOpaque(false);
newGameButton.setContentAreaFilled(false);
newGameButton.setForeground(Color.RED);
newGameButton.setBackground(Color.BLUE);
loadGameButton = new JButton("Load Game");
loadGameButton.setBounds(20,400,150,50);
//loadGameButton.setOpaque(false);
//loadGameButton.setContentAreaFilled(false);
loadGameButton.setForeground(Color.RED);
quitButton = new JButton("Quit");
quitButton.setBounds(490,400,150,50);
quitButton.setOpaque(false);
quitButton.setContentAreaFilled(false);
quitButton.setForeground(Color.RED);
ruleButton = new JButton("Rules");
ruleButton.setBounds(490,300,150,50);
ruleButton.setOpaque(false);
ruleButton.setContentAreaFilled(false);
ruleButton.setForeground(Color.RED);
// Sets button commands
newGameButton.setActionCommand(newGameButtonName);
loadGameButton.setActionCommand(loadGameButtonName);
quitButton.setActionCommand(quitButtonName);
// Adds buttons to mainPanel
mainPanel.add(newGameButton);
mainPanel.add(loadGameButton);
mainPanel.add(quitButton);
mainPanel.add(ruleButton);
// add(mainPanel);
return mainPanel;
}
private Image createImage(){
try {
img = ImageIO.read(
Menu.class.getResource("../resource/riskUconn.jpg"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return img;
}
/**
* paint method
*/
#Override
public void paint (Graphics g) {
Image img = createImage();
g.drawImage(img, 20,20,this);
super.paint(g);
}
// Action listeners for Menu
protected void riskViewActionListeners(ActionListener evt)
{
newGameButton.addActionListener(evt);
loadGameButton.addActionListener(evt);
quitButton.addActionListener(evt);
}
public static void main(String [] args){
Menu m = new Menu();
}
}
Painting components doesn't always notify the parent or child components. Instead of overriding paint, try overriding paintComponent and paint your background there, this is what paintComponent is really meant for, painting the background.
You should avoid overriding paint of top level containers.
Most top level containers have a series of layers, including the JRootPane, contentPane and even a glassPane, all of which will paint over the top of the a frame.
Instead, create a custom component, which extends from something like JPanel and use it as you base component. You can override it's paintComponent and paint the background within it. Then add this component to your frame, maybe even making it the content pane.
Remember, if a component is opaque, it will cover any child components
You should also avoid loading resources from within any paint method (especially continuously reloading them) as this can have a serve affect on the performance of your program. Painting should paint as fast as it can.
For example and example

drawing a line in Java - line is not visible

I am learning Java on my own. Trying to create a frame with a line in it. It seemed pretty basic, but I can't see the line. The code compiles and I can't seem to understand why I can't see the line. I see other components in the frame.
I am using 2 java files. One file is container file and the other file has the draw line code. They are part of package a1.
Here is my code (please help):
Container File:
package a1;
import javax.swing.*;
import java.awt.*;
public class gameContainer {
public static void addComponentsToPane(Container pane) {
pane.setLayout(null);
//add button to the pane
JButton b3 = new JButton("B1");
pane.add(b3);
//add line to the pane
drawingLine line1 = new drawingLine();
pane.add(line1);
//size and position all relatively
Insets insets = pane.getInsets();
Dimension size;
size = b3.getPreferredSize();
b3.setBounds(350+insets.left,15+insets.top,size.width+50,size.height+20);
size = line1.getPreferredSize();
line1.setBounds(350+insets.left,75+insets.top,size.width+50,size.height+20);
}
private static void createAndShowGUI() {
int l = 200, w = 80;
//Create and set up the window.
JFrame frame = new JFrame("Frame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Set up content pane
addComponentsToPane(frame.getContentPane());
//add menu bar
JMenuBar menuBar = new JMenuBar();
JMenu menu = new JMenu("Menu");
menu.add(new JMenuItem("Do nothing"));
menuBar.add(menu);
frame.setJMenuBar(menuBar);
// size and display root pane/window
Insets insets = frame.getInsets();
frame.setSize(500+insets.left+insets.right,300+insets.top+insets.bottom);
frame.setLocation(w,l);
frame.setVisible(true);
}
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
Draw line File:
package a1;
import javax.swing.*;
import java.awt.geom.Line2D;
import java.awt.Graphics2D;
import java.awt.Graphics;
public class drawingLine extends JPanel{
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
Line2D line = new Line2D.Double(200, 300, 1000, 1000);
//g2d.setColor(Color.black);
g2d.draw(line);
//g.drawLine(200, 300, 1000, 1000);
//g.setColor(color.BLACK);
}
}
Why am I not able to see the line?
Your main problem is that you use null/Absolute layout.
You should have a read here:
Laying Out Components Within a Container
1) You should use an appropriate LayoutManager (or nest together multiple LayoutManagers) and/or override getPreferredSize() of JComponent to return correct dimensions which fit the drawings.
2) Dont call setSize on JFrame rather call pack() before setting JFrame visible (with above in mind)
3) No need for adding to contentPane via getContentPane().add(..) as add(..) setLayout(..) and remove(..) have been forwarded to the contentPane.
4) Watch class names, stick to the java convention a class name begins with a capital letter and each new word thereafter the first letter should be capitilized i.e gameContainer should be GameContainer, drawingLine should be DrawingLine
Here is your code with above fixes implemented (not the most well layed out, but its only an example):
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
public class GameContainer {
public static void addComponentsToPane(JFrame frame) {
JPanel buttonPanel=new JPanel();//create panel to hold button
//add button to the pane
JButton b3 = new JButton("B1");
buttonPanel.add(b3);
frame.add(buttonPanel, BorderLayout.EAST);//use contentPane default BorderLayout
//add line to the pane
DrawingLine line1 = new DrawingLine();
frame.add(line1);
}
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("Frame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Set up content pane
addComponentsToPane(frame);
//add menu bar
JMenuBar menuBar = new JMenuBar();
JMenu menu = new JMenu("Menu");
menu.add(new JMenuItem("Do nothing"));
menuBar.add(menu);
frame.setJMenuBar(menuBar);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
class DrawingLine extends JPanel {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
Line2D line = new Line2D.Double(10, 10, 100, 100);
g2d.draw(line);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
}
The proper (better to say, easier, much less error prone) way to do it has been shown by David Kroukamp. As for your original question, modify the original code like this:
line1.setBounds(350+insets.left,75+insets.top,size.width+50,size.height+20);
line1.setBorder(BorderFactory.createEtchedBorder()); // add this line
Your line starts at point 200,300 but the panel you're drawing it in has width of 50+10 and height of 20+10 - on my computer at least - which means that the line is outside of the drawingLine panel and that's why it doesn't get drawn. To verify that modify the line in your original drawingLine.paintComponent like this:
Line2D line = new Line2D.Double(0, 0, 1000, 1000);
and you will see the following result:
This line in your original code:
pane.setLayout(null);
is pretty clear - you are using no layout manager, or in other words you chose to use absolute positioning. This means that the coordinates, widths and heights of components must be precalculated and set by you. But if you make a mistake somewhere (as your example nicely shows) it'll be hard to detect. Not to mention, for example if you want to handle window resizing. That's why LayoutManagers exist.
I recommend reading also:
Using Layout Managers
Solving Common Layout Problems

Why is paintComponent never called?

I have the following code. Basically I have a frame which has a background image. I also have three panels within the frame: panels 1, 2 and 3. 2 & 3 work fine as I haven't subclassed them. However, panel 1 as soon as I subclassed it i.e. put the logic inside the paintComponent method of JPanel stopped working as that method is never called and foo is never printed. I'm not able to figure out why. Would appreciate your help. I've tried a few suggestions from other similar threads and they haven't helped.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Test {
public static void main(String[] args) throws IOException {
JFrame.setDefaultLookAndFeelDecorated(true);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int fooPanelX = 5;
int fooPanelY = 160;
int fooPanelWidth = 470;
int fooPanelHeight = 305;
int bar0PanelX = 5;
int bar0PanelY = 550;
int bar0PanelWidth = 230;
int bar0PanelHeight = 210;
int bar1PanelX = bar0PanelX * 2 + bar0PanelWidth + bar0PanelX;
int bar1PanelY = bar0PanelY;
int bar1PanelWidth = bar0PanelWidth;
int bar1PanelHeight = bar0PanelHeight;
JPanel panel1 = new Panel1(fooPanelX, fooPanelY, fooPanelWidth, fooPanelHeight);
JPanel panel2 = new JPanel();
panel2.setBackground(Color.WHITE);
panel2.setLocation(bar0PanelX, bar0PanelY);
panel2.setSize(bar0PanelWidth, bar0PanelHeight);
panel2.setOpaque(false);
panel2.setBorder(BorderFactory.createLineBorder(Color.WHITE));
panel2.setBounds(bar0PanelX, bar0PanelY, bar0PanelWidth, bar0PanelHeight);
JPanel panel3 = new JPanel();
panel3.setBackground(Color.WHITE);
panel3.setLocation(bar1PanelX, bar1PanelX);
panel3.setSize(bar1PanelWidth, bar1PanelHeight);
panel3.setOpaque(false);
panel3.setBorder(BorderFactory.createLineBorder(Color.WHITE));
panel3.setBounds(bar1PanelX, bar1PanelY, bar1PanelWidth, bar1PanelHeight);
JLabel imagePanel = new JLabel(new ImageIcon(ImageIO.read(new File("image.png"))));
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == 27) {
System.exit(0);
}
}
});
frame.setContentPane(imagePanel);
frame.getContentPane().add(panel1);
frame.getContentPane().add(panel2);
frame.getContentPane().add(panel3);
frame.setLocation((int) (screenSize.getWidth() * 0.75),
(int) (screenSize.getHeight() * 0.25));
frame.pack();
frame.setVisible(true);
}
#SuppressWarnings("serial")
static class Panel1 extends JPanel {
int x, y, w, h;
public Panel1(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
#Override
public void paintComponent(Graphics graphics) {
System.out.println("foo");
super.paintComponents(graphics);
setBackground(Color.WHITE);
setLocation(x, y);
setSize(w, h);
setOpaque(true);
setBorder(BorderFactory.createLineBorder(Color.WHITE));
}
}
}
Update: If you aren't able to find the issue then please could you provide me with an alternative way of doing the following. I need a frame with a background image and three panels on top of the background image. The three panels have to have pixel perfect locations and sizes on the background image to look right. That's it pretty much. I'll be repainting the three panels but the background image will remain the same.
Well, the problem is that you are not using an appropriate LayoutManager.
JLabel does not come with any LayoutManager by default. So when you add your Panel1, it has a size of 0x0 and is located in (0,0) and since no LayoutManager will change that, it will keep that size and location. With empty bounds, your component is never painted, hence your paintComponent method is never called.
Now, you should NEVER do this in paintComponent:
setBackground(Color.WHITE);
setLocation(x, y);
setSize(w, h);
setOpaque(true);
setBorder(BorderFactory.createLineBorder(Color.WHITE));
Do that in the constructor or some other method. paintComponent is meant for "painting a component", not changing its properties.
I decided to tackle the problem in a very different way. This is how I did it. I startedc completely from scratch with my code. I created a JFrame instance and a Canvas instance (the canvas was subclassed). In the canvas I used drawImage() to apply the background image. Then for each of the three areas that I wanted to animate on the background image, instead of creating three JPanels, I simply used fillRect() within the canvas to fill the right areas on the image. That's it. Nice and simple. The repaint() every second does flickr on the three areas and that's the next challenge. I'm guessing I have to use double buffering but it's not something I've used before so I'll look into that next. Anyway, using a single canvas in place of three JPanels proved a heck of a lot simpler and the reason I was able to do that was because the background image provided everything else visually. All I had to do was drawImage() and fillRect(). Thanks for all contributions.
Update: I have now completed this task. There was one thing I changed about the above. While attempting to double buffer with Canvas I had a few issues: the usual "component must have valid peer" exception. While looking into that I learnt that one should not use Canvas in Swing and that the practice of using it was mixing AWT and Swing. So I swapped it out for JComponent (as I didn't need anything that JPanel offered). And as Swing is double buffered by default my work was complete. No flicker and simplified code.

Making image scrollable in JFrame contentpane

I am trying to display a large image inside a JFrame's contentpane. I would like to make the image or contentpane scrollable as the image is large. I tried to do it using Jscrollpane and add it into the contentpane but it didn't work. Did some searching for solution but end up failed to find one. Can someone guide me? My code are below
FinalEnvironment.java
package environment;
import java.awt.*;
import java.net.URL;
import javax.swing.*;
public class FinalEnvironment{
public FinalEnvironment(){
Image Eastlake;
URL EastlakeURL = null;
EastlakeURL = FinalEnvironment.class.getResource("/image1/eastlake_night.png");
Eastlake = Toolkit.getDefaultToolkit().getImage(EastlakeURL);
JFrame frame = new JFrame("UniCat World");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
JMenuBar yellowMenuBar = new JMenuBar();
Map map = new Map(800, 550, Eastlake);
yellowMenuBar.setOpaque(true);
yellowMenuBar.setBackground(Color.YELLOW);
yellowMenuBar.setPreferredSize(new Dimension(800, 50));
frame.setJMenuBar(yellowMenuBar);
JScrollPane scroller = new JScrollPane(map);
scroller.setAutoscrolls(true);
scroller.setPreferredSize(new Dimension(800, 550));
frame.getContentPane().add(scroller, BorderLayout.CENTER);
frame.setSize(800, 600);
frame.setVisible(true);
}
public static void main(String[] args){
FinalEnvironment fe = new FinalEnvironment();
}
}
Here is my map.java
package environment;
import java.awt.*;
import javax.swing.*;
public class Map extends JPanel{
private int width;
private int height;
private Image img;
public Map(int width, int height, Image img){
this.width = width;
this.height = height;
this.img = img;
}
protected void paintComponent(Graphics g)
{
super.paintComponents(g);
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(img,0,0,2624,1696,null);
}
}
Lastly, I would like to place Jbuttons on top of this image. Should I call a Rectangle and place it on top the image in the contentpane which then I use Point to position my buttons or should I straight away use the image or the component itself to do it? I need the button to be able to synchronize with the image when it is scrolled instead of static in the contentpane.
Thanks
What I would do here:
1.Have a panel (canvas) which only responsibility is to paint a given image independent of the real image size in overridden method paintComponent()
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
2.Make sure the canvas preferred size equals to image real size.
3.Have a second panel which will serve as content pane of a frame.
4.In it you will set a JScrollPane as its centre.
5.In the scroll pane viewport will be the component from step 1.
6.Add your button to canvas panel from step 1. It will be scrolled together with the image.
7.Add the content pane, the panel from step 3, to a frame, and run the application.
EDIT:
Code sample with button added to canvas, which stays always in its place, independent of scroll position or frame size.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
public class ScrollImageTest extends JPanel {
private static final long serialVersionUID = 1L;
private BufferedImage image;
private JPanel canvas;
public ScrollImageTest() {
try {
this.image = ImageIO.read(new URL("http://interviewpenguin.com/wp-content/uploads/2011/06/java-programmers-brain.jpg"));
}catch(IOException ex) {
Logger.getLogger(ScrollImageTest.class.getName()).log(Level.SEVERE, null, ex);
}
this.canvas = new JPanel() {
private static final long serialVersionUID = 1L;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
};
canvas.add(new JButton("Currently I do nothing"));
canvas.setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
JScrollPane sp = new JScrollPane(canvas);
setLayout(new BorderLayout());
add(sp, BorderLayout.CENTER);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JPanel p = new ScrollImageTest();
JFrame f = new JFrame();
f.setContentPane(p);
f.setSize(400, 300);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
});
}
}
What if you use your dimensions to set the Map's preferred size. For instance, give Map this method:
// method in the Map class
#Override
public Dimension getPreferredSize() {
return new Dimension(width, height);
}
This way the Map JPanel will take up the necessary room to show the entire image. Also, why does your drawImage method in the paintComponent method have the large magic numbers? Why not use the width and height there as well? Edit 1: or don't even specify the image size as Boro suggests in his answer (1+ to him).
Why is everybody reinventing the wheel??? There is no need for a custom panel to paint the image!!!
All you need to do is create a JLabel and add an ImageIcon to the label and you won't have a problem. The label will:
paint the image at (0, 0) at its original size (which is exactly what the custom code is doing).
determine the preferred size of the image based on the image size. Now scrolling will happen automatically.
Also there is rarely any reason to use the setPreferredSize() method since all components have a default preferred size. So you should not set the default size of the menu bar. The only time I set a preferred size would be on the JScrollPane. This will allow the frame to be packed at a reasonable size and then scrollbars will appear automatically based on the size of the image in the label.
In addition to other helpful answers, you might like studying this example that uses mouse gestures to scroll arbitrary content.

Categories