I try to layer triangles (JPanel) on top of a map (JPanel). I tried it with a JLayeredPane but the triangle and the map is not layered (see attached picture). In the end, I would like to set a position for a triangle and place it onto the map. I tried to set absolute positions for the triangle and the map with setBounds() but it did not work for me. Same result. This is my code:
import javax.swing.*;
import java.awt.*;
public class MainFrame extends JFrame {
Container mainContainer;
JLayeredPane mapLayer;
BackgroundMap bgMap;
Mesocyclone meso1;
public MainFrame() {
initUI();
}
private void initUI() {
mainContainer = getContentPane();
bgMap = new BackgroundMap();
mapLayer = new JLayeredPane();
mapLayer.setLayout(new FlowLayout());
mainContainer.setLayout(new FlowLayout(FlowLayout.LEFT));
mainContainer.add(mapLayer);
meso1 = new Mesocyclone();
mapLayer.add(bgMap, JLayeredPane.DEFAULT_LAYER);
mapLayer.add(meso1, JLayeredPane.POPUP_LAYER);
setSize(900, 1000);
setTitle("Mesodetect | v1.0 \u00A9 ");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Path2D;
public class Mesocyclone extends JPanel {
private static final int PREF_W = 100;
private static final int PREF_H = PREF_W;
private static final Color COLOR = Color.RED;
private Path2D myPath = new Path2D.Double();
public Mesocyclone() {
double firstX = (PREF_W / 2.0) * (1 - 1 / Math.sqrt(3));
double firstY = 3.0 * PREF_H / 4.0;
myPath.moveTo(firstX, firstY);
myPath.lineTo(PREF_W - firstX, firstY);
myPath.lineTo(PREF_W / 2.0, PREF_H / 4.0);
myPath.closePath();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// to smooth out the jaggies
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(COLOR); // just for fun!
g2.fill(myPath); // fill my triangle
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
}
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class BackgroundMap extends JLabel {
private Image image;
private BevelBorder border;
private int WIDTH = 620;
private int HEIGHT = 850;
public BackgroundMap() {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
try {
image = ImageIO.read(new File("res/map_bg.png"));
this.setIcon(new ImageIcon(image));
} catch (IOException e) {
e.printStackTrace();
}
border = new BevelBorder(1);
setBorder(border);
}
public Image getImage() {
return this.image;
}
}
import java.awt.*;
public class Application {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new MainFrame().setVisible(true);
}
});
}
}
mapLayer = new JLayeredPane();
mapLayer.setLayout(new FlowLayout());
mainContainer.setLayout(new FlowLayout(FlowLayout.LEFT));
mainContainer.add(mapLayer);
meso1 = new Mesocyclone();
mapLayer.add(bgMap, JLayeredPane.DEFAULT_LAYER);
mapLayer.add(meso1, JLayeredPane.POPUP_LAYER);
you should not be setting a layout manager for the layered pane. The will cause the components to be displayed in a FlowLayout instead of being layered because the location of each component will be set based on the rules of the FlowLayout. However, if you don't't set the layout manager you are now responsible for setting the size/location of each component you add to the layered pane.
Don't use "DEFAULT_LAYER" AND "POPUP_LAYER", those are special layers used by the layered panel. You are just using "regular" layers when you add your own components. So you should be using "new Integer(1)" and "new Integer(2)".
Read the section from the Swing tutorial on How to Use LayeredPanes for a working example of the above two suggestions.
The other option is to not use a layered pane and just use Swings default parent/child relationship between components.
So your image label would become the parent and the triangle component becomes the child. Then your logic would become:
JPanel child = new ...
child.setSize( child.getPreferredSize() );
child.setLocation(10, 10);
JLabel parent = new JLabel( new ImageIcon(...) );
parent.add( child );
Note you would also need to use setOpaque(false) on the child component.
As long as the child component will fit in the bounds of the labels image, this is the easier solution.
Related
I'm making an application for creating convex
polygon.
I imagined it to be so that I first set the vertices of the polygon and then create it.
I was able to make the addition of points (vertices). Now I need help connecting the dots with a line.
This is what it looks like:
It looks like this
And I would like when I click the Draw Polygon button that these points connect and it looks like a polygon, like this:
and it should look like this when I click the button
Here's the code so you can run it yourself:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;
public class MyPaint{
public static void main(String[] args){
final PadDraw drawPad = new PadDraw();
JFrame frame = new JFrame("Draw Polygon");
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(100, 100, 450, 300);
Container contentPane = frame.getContentPane();
((JComponent) contentPane).setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
frame.setContentPane(contentPane);
JPanel buttonPanel = new JPanel();
buttonPanel.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));
contentPane.add(buttonPanel, BorderLayout.SOUTH);
JButton buttonDrawPolygon = new JButton("Draw Polygon");
buttonPanel.add(buttonDrawPolygon);
JButton buttonReset = new JButton("Reset");
buttonPanel.add(buttonReset);
contentPane.add(drawPad, BorderLayout.CENTER);
}
}
class PadDraw extends JComponent{
private Image image;
private Graphics2D graphics2D;
private int currentX , currentY , oldX , oldY ;
public PadDraw(){
addMouseListener(new MouseAdapter(){
public void mousePressed(MouseEvent e){
oldX = e.getX();
oldY = e.getY();
}
});
addMouseListener(new MouseAdapter(){
public void mousePressed(MouseEvent e){
currentX = e.getX();
currentY = e.getY();
if(graphics2D != null) {
graphics2D.drawLine(oldX, oldY, currentX, currentY);
repaint();
oldX = currentX;
oldY = currentY;
}
System.out.println(oldX + " " + oldY);
}
});
}
public void paintComponent(Graphics g){
if(image == null){
image = createImage(getSize().width, getSize().height);
graphics2D = (Graphics2D)image.getGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics2D.setStroke(new BasicStroke(5));
}
g.drawImage(image, 0, 0, null);
}
}
Introduction
Oracle has a helpful tutorial, Creating a GUI With Swing. Skip the Learning Swing with the NetBeans IDE section.
Here's your revised GUI
Here's the GUI with four points
Here's the GUI with a polygon
I'm not showing it, but the Reset button clears the drawing area.
Explanation
When I create a Swing GUI, I use the model-view-controller pattern. This pattern allows me to separate my concerns and focus on one part of the Swing application at a time.
A Swing application model consists of one or more plain Java getter/setter classes.
A Swing view consists of a JFrame and one or more JPanels.
Swing controllers are the listeners that are attached to JButtons and drawing JPanels.
Model
For this application, I created a PolygonModel class. The class holds a boolean that tells me whether or not to draw the polygon and a java.util.List of java.awt.Point instances. The Point class holds an X and Y int value.
View
All Swing applications must start with a call to the SwingUtilities invokeLater method. This method ensures that the Swing components are created and executed on the Event Dispatch Thread.
I broke up your main method into a couple of methods. I separate the creation of the JFrame from the creation of the JPanels. This allows me to separate my concerns and focus on one part of the GUI at a time.
The JFrame methods must be called in a specific order. This is the order I use for most of my Swing applications.
I changed your PadDraw class to extend a JPanel. I moved the MouseAdapter code to its own class. Your drawing panel should draw. Period. Nothing else.
The paintComponent method always starts with a call to the super.paintComponent method. This maintains the Swing paint chain and helps to eliminate unwanted drawing artifacts.
The drawing JPanel is cleared before every repaint. Therefore, you have to completely redraw your image each time. That's why we store the List of Point instances in the model.
Controller
I created three controller classes.
The PointListener class extends MouseAdapter. Notice how simple the mousePressed method becomes with an application model.
The two JButtons each have their own ActionListener. Since they are so simple, I made each of them lambdas.
Code
Here's the complete runnable code. I made all the additional classes inner classes so I could make them public and post the code as one block.
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.EtchedBorder;
public class PolygonImage implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new PolygonImage());
}
private final PolygonModel model;
private final PadDraw drawPad;
public PolygonImage() {
this.model = new PolygonModel();
this.drawPad = new PadDraw(this, model);
}
#Override
public void run() {
JFrame frame = new JFrame("Draw Polygon");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(drawPad, BorderLayout.CENTER);
frame.add(createButtonPanel(), BorderLayout.SOUTH);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createButtonPanel() {
JPanel buttonPanel = new JPanel();
buttonPanel
.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));
JButton buttonDrawPolygon = new JButton("Draw Polygon");
buttonDrawPolygon.addActionListener(event -> {
model.setConnectPoints(true);
repaint();
});
buttonPanel.add(buttonDrawPolygon);
JButton buttonReset = new JButton("Reset");
buttonReset.addActionListener(event -> {
model.setConnectPoints(false);
model.clearList();
repaint();
});
buttonPanel.add(buttonReset);
return buttonPanel;
}
public void repaint() {
drawPad.repaint();
}
public class PadDraw extends JPanel {
private static final long serialVersionUID = 1L;
private final PolygonModel model;
public PadDraw(PolygonImage view, PolygonModel model) {
this.model = model;
this.addMouseListener(new PointListener(view, model));
this.setPreferredSize(new Dimension(450, 300));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.black);
// Draw points
for (Point p : model.getPoints()) {
int radius = 6;
int diameter = radius + radius;
g2d.fillOval(p.x - radius, p.y - radius, diameter, diameter);
}
// Draw polygon
if (model.isConnectPoints()) {
g2d.setStroke(new BasicStroke(5));
List<Point> points = model.getPoints();
if (points.size() >= 1) {
Point old = points.get(0);
for (int index = 1; index < points.size(); index++) {
Point p = points.get(index);
g2d.drawLine(old.x, old.y, p.x, p.y);
old = p;
}
Point p = points.get(0);
g2d.drawLine(p.x, p.y, old.x, old.y);
}
}
}
}
public class PointListener extends MouseAdapter {
private final PolygonImage view;
private final PolygonModel model;
public PointListener(PolygonImage view, PolygonModel model) {
this.view = view;
this.model = model;
}
#Override
public void mousePressed(MouseEvent event) {
model.addPoint(event.getPoint());
view.repaint();
}
}
public class PolygonModel {
private boolean connectPoints;
private final List<Point> points;
public PolygonModel() {
this.points = new ArrayList<>();
this.connectPoints = false;
}
public void setConnectPoints(boolean connectPoints) {
this.connectPoints = connectPoints;
}
public boolean isConnectPoints() {
return connectPoints;
}
public void clearList() {
this.points.clear();
}
public void addPoint(Point point) {
this.points.add(point);
}
public List<Point> getPoints() {
return points;
}
}
}
I started programming in SWING class recently and I try to set Image (like Space) and on it image(like spaceShip) like background. I
would love for you to help me,
Here is my code
public class SpaceWar {
static JFrame frame = new JFrame("Space War");
static JPanel panel = new JPanel();
public static void main(String[] args) {
new SpaceWar();
//frame.setResizable(false);
frame.setVisible(true);
}
public SpaceWar() {
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
Dimension size
= Toolkit.getDefaultToolkit().getScreenSize();
frame.setPreferredSize(size);
frame.setLayout(null);
panel.setLayout(null);
panel.setBounds(frame.getPreferredSize().width/4,0,
frame.getPreferredSize().width/2,frame.getPreferredSize().height);
frame.add(panel);
frame.add(new Background());
spaceShip sp1 = new spaceShip();
panel.setBackground(Color.black);
panel.add(sp1);
System.out.println(panel.getPreferredSize().width);
}
}
class spaceShip extends JLabel{
static ImageIcon img = new ImageIcon("spaceShip.png");
public spaceShip(){
sizeIcon(100,100,img);
setIcon(img);
}
public static ImageIcon sizeIcon(int w,int h,ImageIcon image1){
Image image = image1.getImage(); // transform it
Image newimg = image.getScaledInstance(w,h, java.awt.Image.SCALE_SMOOTH); // scale it the smooth way
ImageIcon img1 = new ImageIcon(newimg); // transform it back
return img1;
}
}
class Background extends JPanel{
public void paint(Graphics g) { // paint() method
super.paint(g);
ImageIcon image = new ImageIcon("space.jpg");
Image bg = image.getImage();
g.drawImage(bg,0,0,null);
}
}
So, your "core" problem is the use of null layouts and a lack of understand of how components are sized and positioned. Having said that, if your aim is to make a game, this probably isn't the best approach anyway.
Instead, I'd focus on creating a "surface", onto which you can paint all your assets directly, this will give you much greater control.
Start by taking a look at Painting in AWT and Swing and Performing Custom Painting to get a better understanding how the paint system works and how you can work with it to perform custom painting.
I'd also avoid ImageIcon, it's not the best way to handle images, instead, take a look at ImageIO (Reading/Loading an Image), it will generate an IOException if the image can't be loaded and will return a fully realised image (unlike ImageIcon which off loads the image loading to a background thread).
For example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new MainPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class MainPane extends JPanel {
private BufferedImage ufo;
private BufferedImage background;
private int horizontalPosition = 106;
public MainPane() throws IOException {
ufo = ImageIO.read(getClass().getResource("/images/ufo.png"));
background = ImageIO.read(getClass().getResource("/images/starfield.png"));
System.out.println("background = " + background);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
paintBackground(g2d);
paintUFO(g2d);
g2d.dispose();
}
protected void paintBackground(Graphics2D g2d) {
int x = (getWidth() - background.getWidth()) / 2;
int y = (getHeight() - background.getHeight()) / 2;
g2d.drawImage(background, x, y, this);
}
protected void paintUFO(Graphics2D g2d) {
int x = (getWidth() - ufo.getWidth()) / 2;
int y = (getHeight() - ufo.getHeight()) / 2;
g2d.drawImage(ufo, x, y, this);
}
}
}
The example makes use of embedded resources (something to read up on). Managing your assets this way will save you countless hours of wondering why they aren't loading/working. How you achieve this will come down to your IDE and build system, for example, Netbeans and Eclipse will allow you to add resources directly to the src directory, when you're not using maven.
At some point, you're going to want to learn about How to Use Swing Timers and How to Use Key Bindings
I want to draw lines and more on a JPanel, add that to a JFrame and using .pack() afterwards. My problem is that I dont get how to use a Layout Manager in that particular case. Usually I add a button or something to the panel by using a gridBagLayout and I totally understand that. But with graphics 2D I kind of just draw directly to the panel. Therfore I cant use .pack() properly. Does somebody know how to pack() that jPanel the right way? My code looks like that:
public class NetworkViewPanel extends JPanel implements KeyListener, ActionListener {
public NetworkViewPanel(NetworkAI network) {
this.network = network;
this.netList = network.getLayerList();
addKeyListener(this);
setFocusable(true);
this.setLayout(new GridLayout(2, 2, 2, 2)); // does that even make sense ?
}
public void paint(Graphics g) {
super.paint(g);
g2 = (Graphics2D) g;
if (showStandardView) {
drawRectangles();
drawLines();
} else {
drawRectangles();
drawLinesSpecial(listIndex, xIndex);
}
}
Greetings :)
You can layout a JPanel with a layout manager, and do custom painting on top of it.
This does not prevent you from using pack()1.
The following mre 2 demonstrates painting a line on a JPanel using a GridLayout:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class NetworkViewPanel extends JPanel{
private final List<JLabel> labels;
public NetworkViewPanel() {
this.setLayout(new GridLayout(2, 2, 2, 2));
this.setPreferredSize(new Dimension(400,300));//used by pack()
labels = new ArrayList<>();
addLabels(new String[]{ "A", "B" , "C" , "D"});
}
private void addLabels(String[] text){
for(String t: text){
JLabel label = new JLabel(t);
label.setBorder(BorderFactory.createLineBorder(Color.BLUE));
label.setHorizontalAlignment(JLabel.CENTER);
add(label);
labels.add(label);
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g); //draw panel as layed out by layout manager
drawLines(g);
}
private void drawLines(Graphics g) {
//draw line between centers of first and last components
int x1 = labels.get(0).getBounds().x + labels.get(0).getBounds().width /2;
int y1 = labels.get(0).getBounds().y + labels.get(0).getBounds().height /2;
int x2 = labels.get(labels.size()-1).getBounds().x + labels.get(labels.size()-1).getBounds().width/2;
int y2 = labels.get(labels.size()-1).getBounds().y + labels.get(labels.size()-1).getBounds().height/2;
g.setColor(Color.RED);
g.drawLine(x1, y1, x2, y2);
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationRelativeTo(null);
f.add(new NetworkViewPanel());
f.pack();
f.setVisible(true);
}
}
1 See: What does .pack() do?
2 Consider posting mre when asking or answering
I was mixing two JPanels in one frame and it gives me this output!
Here's my code in which I add the two JPanels:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.*;
import java.util.*;
public class Board extends JFrame{
private int width=500;
private int height=450;
Obstacles asd= new Obstacles();
Human human;
private Dimension mindim= new Dimension(width+10,height+10);
Board(){
human = new Human();
this.setTitle("Athwart");
//setLayout(null);
human.add(asd); //!!!we see here, I add asd (which is inherited from a JPanel)
// to another existing JPanel
this.setMinimumSize(mindim); //
this.getContentPane().setLayout(new BorderLayout());
this.getContentPane().add(human);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //
this.setLocationRelativeTo(null); //
this.setResizable(true); //
pack(); //
setVisible(true);
human.requestFocus(); //
}
}
This is how my Obstacles class looks like.
import javax.swing.*;
import java.awt.*;
public class Obstacles extends JPanel {
private int width=500;
private int height=450;
private Dimension mindim= new Dimension(width+10,height+10);
Obstacles()
{
this.setBackground(Color.white);
// this.addKeyListener(this);
// this.setFocusable(true);
// this.setRequestFocusEnabled(true);
setSize(mindim);
this.setVisible(true);
}
public void paintComponent(Graphics g)
{
super.paintComponent(g); //
g.fillRect(0, 0, 60, 30);
g.setColor(Color.black);
g.draw3DRect(0, 0, 60, 30, true);
g.setColor(Color.black);
}
}
So as you can see the height of the component is 30 and the width is 60 but the Image above shows not even a half of it!
Is there anything I can do to make that two JPanels mixed together? By the way,
I tried using BoxLayout earlier but it didn't worked. Is there something wrong or it's just my IDE is not working properly? Cheers and thanks for an awesome reply. I'm just a starting gui programmer and I really don't know how to handle things. And yeah, if you would ask the complete code, I'll edit this if it would matter. :)
Finally, you put in a requirement:
I was just trying to put an image of a rectangle inside a jframe together with another Jpanel of an image of a circle. to see if how i would be that far in mixing two Jpanels together without overlapping.
Yes, this can be done, say by using a JLayeredPane, you could layer one JPanel over another, but you need to make sure that the upper JPanel is not opaque (setOpaque(false)).
But having said that, I still stand by my comment, that you look to be going about this wrong. You should not create a JPanel for drawing one thing and try to combine multiple JPanels because this can lead to an unholy mess. Instead you should consider creatomg one drawing JPanel, and give it logical objects, say non-GUI Obstacle objects, put them in a collection such as an ArrayList, and then in the drawing JPanel, iterate through all the Obstacles in the drawing JPanel's paintComponent method, drawing each Obstacle as it directs.
Edit
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
public class ObstacleDrawer extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = PREF_W;
private List<Obstacle> obstacleList = new ArrayList<>();
public ObstacleDrawer() {
}
public void addObstacle(Obstacle obstacle) {
obstacleList.add(obstacle);
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// smooth out the drawing
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// iterate through the obstacle list, drawing each obstacle
for (Obstacle obstacle : obstacleList) {
obstacle.draw(g2);
}
}
private static void createAndShowGui() {
ObstacleDrawer mainPanel = new ObstacleDrawer();
mainPanel.addObstacle(new CircleObstacle(new Point(200, 200), 100, Color.red));
mainPanel.addObstacle(new CircleObstacle(new Point(400, 300), 150, Color.blue));
JFrame frame = new JFrame("ObstacleDrawer");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
interface Obstacle {
public Point getCenter();
public void setCenter(Point center);
public int getWidth();
public void setWidth(int width);
public Color getColor();
public void setColor(Color color);
public void draw(Graphics2D g2);
}
class CircleObstacle implements Obstacle {
private Point center;
private int width;
private Color color;
public CircleObstacle(Point center, int width, Color color) {
this.center = center;
this.width = width;
this.color = color;
}
#Override
public Point getCenter() {
return center;
}
#Override
public void setCenter(Point center) {
this.center = center;
}
#Override
public int getWidth() {
return width;
}
#Override
public void setWidth(int width) {
this.width = width;
}
#Override
public Color getColor() {
return color;
}
#Override
public void setColor(Color color) {
this.color = color;
}
#Override
public void draw(Graphics2D g2) {
Color oldColor = g2.getColor();
g2.setColor(color);
int x = center.x - width / 2;
int y = center.y - width / 2;
int height = width;
g2.fillOval(x, y, width, height);
g2.setColor(oldColor);
}
}
When you want to mix JPanels, the best way to do it is to nest them inside another JPanel. For nesting, the best layouts (in my experience) are BoxLayout and GridLayout. The example below tries to replicate the drawing in your JFrame.
JPanel outsidePanel = new JPanel();
Obstacle obstacle1 = new Obstacle();
Human human1 = new Human();
outsidePanel.setLayout(new BoxLayout(BoxLayout.Y_AXIS));
outsidePanel.add(human1);
outsidePanel.add(obstacle1);
Furthermore, I recommend you to actually use BorderLayout in your JFrame, and then add this Panel to the CENTER location of the BorderLayout. Following your example:
this.setLayout(new BorderLayout());
this.add(outsidePanel(), BorderLayout.CENTER);
Usually this will give you the best results. More about nesting panels in the BoxLayout documentation (http://docs.oracle.com/javase/7/docs/api/javax/swing/BoxLayout.html). Hope this helps.
I'd like to make a Java panel that creates objects where the user clicks. Since my actual application uses a MVC approach I'd like also for these objects to be able to repaint themselves when a model is changed, and provide menus to change their properties.
I think that the best way to control their x and y locations would be to take a canvas based approach whereby the JPanel calls a draw method on these objects from the paintComponent method. This however will only draw the shape on the canvas and does not add the object itself loosing all abilities to control object properties. I'd be very grateful if someone could tell me the best approach for what I want to do.
I've created some sample code which can be seen below. When clicked I'd like the circle to change colour, which is implemented using a MouseListener (it basically represents changing the models properties in this small example). Also I'd just like to make sure that zooming in/out still works with any sample code/advice can provide so I've added buttons to zoom the objects in and out as a quick test.
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import java.awt.geom.Ellipse2D;
public class Main {
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ExamplePanel panel = new ExamplePanel();
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
});
}
//I could not get this to with when it extended JLayeredPane
private static class ExamplePanel extends JPanel {
private static final int maxX = 500;
private static final int maxY = 500;
private static double zoom = 1;
private static final Circle circle = new Circle(100, 100);
public ExamplePanel() {
this.setPreferredSize(new Dimension(maxX, maxY));
this.setFocusable(true);
Button zoomIn = new Button("Zoom In");
zoomIn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
zoom += 0.1;
repaint();
}
});
add(zoomIn);
Button zoomOut = new Button("Zoom Out");
zoomOut.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
zoom -= 0.1;
repaint();
}
});
add(zoomOut);
// add(circle); // Comment back in if using JLayeredPane
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.scale(zoom, zoom);
super.paintComponent(g);
circle.paint(g); // Comment out if using JLayeredPane
}
}
static class Circle extends JPanel {
private Color color = Color.RED;
private final int x;
private final int y;
private static final int DIMENSION = 100;
public Circle(int x, int y) {
// setBounds(x, y, DIMENSION, DIMENSION);
this.x = x;
this.y = y;
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
color = Color.BLUE;
}
#Override
public void mouseReleased(MouseEvent e) {
}
});
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(color);
g2.fillOval(x, y, DIMENSION, DIMENSION);
}
// I had some trouble getting this to work with JLayeredPane even when setting the bounds
// In the constructor
// #Override
// public void paintComponent(Graphics g) {
// Graphics2D g2 = (Graphics2D) g;
// g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// g2.setPaint(color);
// g2.fillOval(x, y, DIMENSION, DIMENSION);
// }
#Override
public Dimension getPreferredSize(){
return new Dimension(DIMENSION, DIMENSION);
}
}
}
As an aside I did try using a JLayeredPane(useful because I'd also like to layer my objects) but could not get my objects to even render. I know it has no default layout manager so tried calling setBounds in the circle in the constructor, but sadly it did not work. I know it's better to use a layout manager but can't seem to find one suitable for my needs!
Thanks in advance.
Don't override paint components, use paintComponent and don't forget to call super.paintComponent
A component already has a concept of "location", so when painting, the top left position of your component is actually 0x0
What you are doing is actually painting beyond the boundaries of you component
For example, if you place your Circle at 100x100 and then did...
g2.fillOval(x, y, DIMENSION, DIMENSION);
You would actually start painting at 200x200 (100 for the actual location of the component and 100 for you additional positioning).
Instead use
g2.fillOval(x, y, DIMENSION, DIMENSION);
And go back and try using JLayeredPane.
You could actually write your own layout manager that takes the location of the component and it's preferred size and updates the components bounds and then apply this to a JLayeredPane. This gives you the "benefits" of an absolute layout, but keeps you within how Swing works to update its components when things change.
You should also be careful with doing anything like...
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
The Graphics context is a shared resource. That means, anything you apply to, will still be in effect when the next component is painted. This may produce some strange results.
Instead try using...
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//...
g2.dispose();
Updated
For zooming I would take a closer look at JXLayer (or JLayer in Java 7)
The JXLayer (and excellent PBar extensions) have gone quite on the net, so you can grab a copy from here
(I tried finding a better example, but this is the best I could do with the limited time I have available)
Updated with working zooming example
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.RenderingHints;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.jdesktop.jxlayer.JXLayer;
import org.pbjar.jxlayer.demo.TransformUtils;
import org.pbjar.jxlayer.plaf.ext.transform.DefaultTransformModel;
public class TestJLayerZoom {
public static void main(String[] args) {
new TestJLayerZoom();
}
public TestJLayerZoom() {
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.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JXLayer<JComponent> layer;
private DefaultTransformModel transformModel;
private JPanel content;
public TestPane() {
content = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridy = 0;
JLabel label = new JLabel("Hello");
JTextField field = new JTextField("World", 20);
content.add(label, gbc);
content.add(field, gbc);
gbc.gridy++;
gbc.gridwidth = GridBagConstraints.REMAINDER;
final JSlider slider = new JSlider(50, 200);
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
int value = slider.getValue();
double scale = value / 100d;
transformModel.setScale(scale);
}
});
content.add(slider, gbc);
transformModel = new DefaultTransformModel();
transformModel.setScaleToPreferredSize(true);
Map<RenderingHints.Key, Object> hints = new HashMap<>();
//hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//hints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
//hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
layer = TransformUtils.createTransformJXLayer(content, transformModel, hints);
setLayout(new BorderLayout());
add(layer);
}
}
}
I've left the rendering hints in to demonstrate their use, but I found that they screwed with the positing of the cursor within the text field, but you might like to have a play
I'd just like to add that I fixed the zooming issue not in the way suggested by the answer, but just by keeping the line that applied a scaled transform call in the ExamplePanel paintComponent method:
g2.scale(zoom, zoom);
I thought that this was the nicest implementation since none of the components require any knowledge about zooming and it seemed far simpler than JLayer since I only required basic zooming functionalities.