Here are two instances of the exact same code that draws a bunch of rectangles superimposed down a diagonal, except in the second instance there are parentheses around "i^2" for both the x and y values. I believe that the rectangles should begin at (0,0) in both cases, but in the first case they do not begin at the origin. Instead, they begin slightly off from the origin. Why is there a difference when the rectangles are added to the JFrame?
using
javax.swing.JComponent
javax.swing.JFrame
and
java.awt.Rectangle
for(int i=0;i<600;i++){
Rectangle rect1 = new Rectangle(20*i^2, 20*i^2, 50, 100);
g2.draw(rect1);
}
for(int i=0;i<600;i++){
Rectangle rect1 = new Rectangle(20*(i^2), 20*(i^2), 50, 100);
g2.draw(rect1);
}
My code is in two classes. Here is the main class:
import javax.swing.JFrame;
public class MainClass {
public static void main(String[] args){
JFrame window = new JFrame();
window.setSize(600,600);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
DrawingComponent drawing = new DrawingComponent();
window.add(drawing);
window.setVisible(true);
}
}
And here is the code for the DrawingComponent class that makes Rectangles to be added to the JFrame:
import javax.swing.JComponent;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
public class DrawingComponent extends JComponent{
public void paintComponent(Graphics g){
Graphics2D g2 = (Graphics2D) g;
for(int i=0;i<600;i++){
Rectangle rect1 = new Rectangle(20*i^2, 20*i^2, 50, 100);
g2.draw(rect1);
}
}
}
The expression 20*i^2 is not the same as 20*(i^2).
Bitwise XOR operator ^ has lower precedence than multiplication * and so the two expressions will produce different results.
Related
The Situation
I am currently trying to build a 2D game with Java's Swing. For this, I have my main class Puzzle which is subclassing JFrame. To my frame I add my main JPanel which consists of several JPanels added together (each of them being a new piece).
EDIT 2: PlayingField is my model which will store the current location of each piece.
One can select a piece with the mouse (the plan is to highlight it) and move it with the arrow keys as long as the next step (a full cell, so approx. 100 pixel) isn't the location of one of the other pieces. As of right now, PlayingFielddoes not store any data since the pieces are missing.
private void createAndShowGui() {
// The playing-field with a 4x6 grid.
PlayingField field = new PlayingField(4, 6);
JPanel mainPanel = new ComputerView(field);
setTitle("Puzzle");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400, 600);
add(mainPanel);
setVisible(true);
}
The method above will create my frame and adds the main panel. The following method is my main panel which adds several JPanels to itself.
public ComputerView(PlayingField field) {
this.field = field;
this.setLayout(null);
JPanel topLeft = new GamingPiece(PlayingField.TOP_LEFT);
add(topLeft);
JPanel topRight = new GamingPiece(PlayingField.TOP_RIGHT);
add(topRight);
JPanel bottomLeft = new GamingPiece(PlayingField.BOTTOM_LEFT);
add(bottomLeft);
}
Each GamingPiece or rather my sub-JPanels are drawing a basic piece (I only drawing one and rotating the others, since all consists of the same arbitrary shape). The GamingPiece class also subclasses JPanel and invokes the JPanel#paintComponent() method to draw the piece.
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.YELLOW);
g2.fillPolygon(pieceX, pieceY, pieceX.length);
}
The Problem And My Questions
Since I am pretty new to Java I really do not know how to do it properly. If I add my pieces by creating a new object and adding it to the main panel, it won't show all of them, only the last one added. Some don't even seem to work, even if they're the only ones added (to explain my situation: I have for pieces which are the same arbitrary shape just rotated differently but using Graphics2D#rotate() doesn't seem to work fine).
I hope I explained my situation and my problem well enough fo you guys to help me. Thanks in advance!
EDIT:
My Codes
Puzzle.java
package programming.schimmler;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import programming.schimmler.model.PlayingField;
import programming.schimmler.view.ComputerView;
public class Puzzle extends JFrame {
...
Invoking the createAndShowGui()
...
private void createAndShowGui() {
// The playing-field with a 4x6 grid.
PlayingField field = new PlayingField(4, 6);
JPanel mainPanel = new ComputerView(field);
setTitle("Puzzle");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400, 600);
add(mainPanel);
setVisible(true);
}
}
ComputerView.java
package programming.schimmler.view;
import java.util.HashSet;
import java.util.Set;
import javax.swing.JPanel;
import programming.schimmler.model.PlayingField;
public class ComputerView extends JPanel {
...
Instance variables
....
public ComputerView(PlayingField field) {
this.field = field;
this.setLayout(null);
JPanel topLeft = new GamingPiece(PlayingField.TOP_LEFT);
add(topLeft);
JPanel topRight = new GamingPiece(PlayingField.TOP_RIGHT);
add(topRight);
JPanel bottomLeft = new GamingPiece(PlayingField.BOTTOM_LEFT);
add(bottomLeft);
}
}
GamingPiece.java
package programming.schimmler.view;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
import programming.schimmler.model.PlayingField;
/**
*
*/
public class GamingPiece extends JPanel {
...
public GamingPiece(int type) {
switch (type) {
// Need to draw each polygon from different coordinates since rotating did not work yet.
case PlayingField.TOP_LEFT:
pieceX = new int[] { 100, 100, 300, 300, 200, 200, 100 };
pieceY = new int[] { 100, 100, 100, 200, 200, 300, 300 };
break;
case PlayingField.TOP_RIGHT:
pieceX = new int[] { 400, 400, 300, 300, 200, 200 };
pieceY = new int[] { 0, 200, 200, 100, 100, 0 };
break;
case PlayingField.BOTTOM_LEFT:
pieceX = new int[] { 0, 200, 200, 100, 100, 0 };
pieceY = new int[] { 400, 400, 300, 300, 200, 200 };
break;
case PlayingField.BOTTOM_RIGHT:
pieceX = new int[] { 400, 400, 300, 300, 200, 200 };
pieceY = new int[] { 400, 200, 200, 300, 300, 400 };
break;
case PlayingField.SQUARE:
pieceX = new int[] { 100, 300, 300, 100 };
pieceY = new int[] { 100, 100, 300, 300 };
break;
}
setLayout(null);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.YELLOW);
g2.fillPolygon(pieceX, pieceY, pieceX.length);
}
}
These classes above are the only classes interacting with my GUI, no other classes take part.
You are overcomplicating things by trying to add JPanel puzzle pieces to your JPanel puzzle board. Problems will rapidly become apparent with your non-rectangular puzzle pieces, like the TOP_LEFT shape, which has a cut-out from the lower right side of the piece. When it comes time to overlap the puzzle pieces, to fit the cut-away portions together, the piece added later will occlude pieces drawn earlier.
For instance, if you try to nest a TOP_LEFT piece (A) and a BOTTOM_RIGHT piece (B) together.
AAAAAA BBB
AAAAAA BBB
AAA BBBBBB
AAA BBBBBB
You will only see:
+---+------+
|AAA| BBB|
|AAA| BBB|
|AAA|BBBBBB|
|AAA|BBBBBB|
+---+------+
The overlapping area will be drawn by only one of the panels. The entire area of the B piece, including the blank space, will be drawn in the area of the second piece, on top of whatever the A piece hoped to display.
You might be able to get around this by setting the JPanel to be transparent, and not calling super.paintComponent() which paints the entire component the background colour. But then you still have to draw the shape on the JPanel, and position the JPanel properly on the parent JPanel.
Forget panels within panels! Just draw the pieces on the parent JPanel.
Example:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Puzzle {
public static void main(String[] args) {
SwingUtilities.invokeLater(Puzzle::new);
}
private final static int shape_x[] = { -100, 100, 100, 0, 0, -100 };
private final static int shape_y[] = { -100, -100, 0, 0, 100, 100 };
public Puzzle() {
JFrame frame = new JFrame("Puzzle");
frame.setSize(600, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
View view = new View();
frame.setContentPane(view);
Shape l_shape = new Polygon(shape_x, shape_y, shape_x.length);
view.pieces.add(new Piece(Color.YELLOW, l_shape, 0, 100, 100));
view.pieces.add(new Piece(Color.GREEN, l_shape, 180, 300, 300));
view.pieces.add(new Piece(Color.RED, l_shape, 65, 450, 145));
frame.setVisible(true);
}
}
#SuppressWarnings("serial")
class View extends JPanel {
final List<Piece> pieces = new ArrayList<>();
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D gc = (Graphics2D) g;
for (Piece piece : pieces)
piece.draw(gc);
}
}
class Piece {
final Color color;
final Shape shape;
final int angle;
int x, y; // Move pieces by by changing these
Piece(Color color, Shape shape, int angle, int x, int y) {
this.color = color;
this.shape = shape;
this.angle = angle;
this.x = x;
this.y = y;
}
void draw(Graphics2D gc) {
AffineTransform tf = gc.getTransform();
gc.translate(x, y);
gc.rotate(Math.toRadians(angle));
gc.setColor(color);
gc.fill(shape);
gc.setTransform(tf);
}
}
Note that this is also a Minimal, Complete and Verifiable example. Minimal: No package statement; No extra HashSet and Set imports which aren't used; doesn't try to do anything except show 3 game pieces. It is complete: a single file you can compile, and run. It is verifiable: you can run it and see it shows 3 game pieces.
As a bonus, it shows Graphics2D.rotate() working, with one piece rotated 180°, and another rotated at an angle that is not appropriate for your puzzle but useful in demonstrating the that rotate works just fine.
I am trying to add a border to a Rectangle element and for some reason it will not work, is it not compatible with JFrame? I can set my entire JFrame to having a border, but it can't find setBorder with my rectangles. Here is my code:
package trivia;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.border.Border;
#SuppressWarnings("serial")
public class Main extends JFrame{
boolean mainMenu = true;
static Color tan = Color.decode("#F4EBC3");
static Color darkGreen = Color.decode("#668284");
static Color buttonColor = Color.decode("#A2896B");
Rectangle header = new Rectangle(0, 0, 500, 100);
Rectangle body = new Rectangle(0, 100, 500, 400);
Rectangle start = new Rectangle(150, 150, 200, 40);
public Main() {
setTitle("Trivia Game!");
setSize(500, 500);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
#Override
public void paint(Graphics g) {
Dimension d = this.getSize();
Border blackline;
blackline = BorderFactory.createLineBorder(Color.black);
if(mainMenu = true){
g.setColor(darkGreen);
g.fillRect(header.x, header.y, header.width, header.height);
g.setFont(new Font("Courier", Font.BOLD, 24));
g.setColor(Color.BLACK);
drawCenteredString("Trivia Game!", d.width, 125, g);
g.setColor(tan);
g.fillRect(body.x, body.y, body.width, body.height);
g.setColor(buttonColor);
g.fillRect(start.x, start.y, start.width, start.height);
}
}
public void drawCenteredString(String s, int w, int h, Graphics g) {
FontMetrics fm = g.getFontMetrics();
int x = (w - fm.stringWidth(s)) / 2;
int y = (fm.getAscent() + (h- (fm.getAscent() + fm.getDescent())) / 2);
g.drawString(s, x, y);
}
public static void main(String[] args) {
#SuppressWarnings("unused")
Main m = new Main();
}
}
And when I add this in my paint function:
start.setBorder(blackline);
It gives me the error:
The method setBorder(Border) is undefined for the type Rectangle
I am not sure how I can make it recognize the setBorder function, can anyone help? All help is much appreciated!
Rectangle does not have a setBorder method, instead, set the color of the Graphics context using Graphics#setColor(Color) and either use Graphics#drawRect(int, int, int, int) or Graphics2D#draw(Shape)
You're breaking the paint chain. Painting is made up of a series of chained method calls, which when called correctly, paint the current component and its child components. By not calling super.paint you're preventing from doing this and could cause any number of nasty side effects, none of which you really want...
You should avoid overriding paint of top level containers, like JFrame, for a number of reasons; they're not double buffered; there a bunch of other components sitting on top of the frame which may paint over it; etc. Instead, create a custom component, extending from something like JPanel and override it's paintComponent method instead (ensuring that you call super.paintComponent first)...
See Painting in AWT and Swing,
Performing Custom Painting and 2D Graphics for more details
Sounds like you're trying to draw the rectangle referenced by start. In that case, you want to be invoking a method on a Graphics, not on a Rectangle. So:
g.drawRect(start.x, start.y, start.width, start.height);
I do not have much familiar with graphics in Java, sorry. However, this is what I'm trying to do. I want to be able draw a couple of points on a canvas (JPanel here), and be able to redraw the points everytime the method (drawPoints) is invoked with a new set of parameters: double[]xs, double[]ys. Any chance I could do this without 'redrawing' the canvas? I can't even get the points to plot in the current state of the code.
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Graphics2D;
import javax.swing.JPanel;
import javax.swing.JFrame;
public class PlotPoints extends JPanel {
double[] x;
double[] y;
public void paintComponent (Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.black);
for (int i=0; i<x.length; i++){
g2d.fillOval((int)this.x[i],(int)this.y[i], 10, 10);
}
}
public void drawPoints(double[]xs, double[]ys){
JFrame frame = new JFrame("Points");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.x=xs.clone();
this.y=ys.clone();
frame.add(new PlotPoints());
frame.setSize(100, 100);//laptop display size
frame.setVisible(true);
}
}
Here is the other class that invokes the 'drawPoints' method from the PlotPoints Class. I got this code snippet from some StackOverflow Q&As, and tried to improvise on it to suit my needs. If a different structure is more suited, I'd be grateful for your sharing.
import java.lang.*;
public class MainClass {
double[] xcoords;
double[] ycoords;
public static void main(String[] args){
//create instances of classes
PlotPoints myPlots=new PlotPoints();
MainClass myMain=new MainClass();
//initialize coordinates
myMain.xcoords=new double[5];
myMain.ycoords=new double[5];
//put values into coordinates
for (int i=0; i<5; i++){
myMain.xcoords[i]=Math.random()*1000; //Random number
myMain.ycoords[i]=Math.random()*1000;
}
//Create a plotter. Plot
//to draw points defined by: (xcoords[i],ycoords[i])
myPlots.drawPoints(myMain.xcoords, myMain.ycoords);
//Please do this!
}
}
Any chance I could do this without 'redrawing' the canvas?
Sure. Draw them to a BufferedImage that is itself displayed in a JLabel. E.G. as seen in this answer.
But don't be too quick to go this way. Java-2D can animate thousands of graphics elements in a paint() method.
Write a program that fills the window with a larrge ellipse. The ellipse shoud touch the window boundaries, even if the window is resized.
I have the following code:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import javax.swing.JComponent;
public class EllipseComponent extends JComponent {
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
Ellipse2D.Double ellipse = new Ellipse2D.Double(0,0,150,200);
g2.draw(ellipse);
g2.setColor(Color.red);
g2.fill(ellipse);
}
}
And the main class:
import javax.swing.JFrame;
public class EllipseViewer {
public static void main(String[] args)
{
JFrame frame = new JFrame();
frame.setSize(150, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
EllipseComponent component = new EllipseComponent();
frame.add(component);
frame.setVisible(true);
}
}
in your EllipseComponent you do:
Ellipse2D.Double ellipse = new Ellipse2D.Double(0,0,getWidth(),getHeight());
I'd also recommend the changes given by Hovercraft Full Of Eels. In this simple case it might not be an issue but as the paintComponent method grows in complexity you realy want as little as possible to be computed in the paintComponent method.
Do not resize components within paintComponent. In fact, do not create objects or do any program logic within this method. The method needs to be lean, fast as possible, do drawing, and that's it. You must understand that you do not have complete control over when or even if this method is called, and you certainly don't want to add code to it unnecessarily that may slow it down.
You should create your ellipse in the class's constructor. To resize it according to the JComponent's size and on change of size, use a ComponentListener.:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import javax.swing.JComponent;
public class EllipseComponent extends JComponent {
Ellipse2D ellipse = null;
public EllipseComponent {
ellipse = new Ellipse2D.Double(0,0,150,200);
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
// set the size of your ellipse here
// based on the component's width and height
}
});
}
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
g2.draw(ellipse);
g2.setColor(Color.red);
g2.fill(ellipse);
}
}
Caveat: code not run nor tested
Could any body diagnose the problem I am facing?
As you run the demo you can see the middle part left blank, I need to fill the entire area..
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class FillDemo
{
public static void main(String aths[])
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel pnl = new PolygonDemo();
pnl.setSize(100, 200);
f.getContentPane().add(pnl);
f.setSize(400,280);
f.setLocation(200,200);
f.setVisible(true);
}
}
class PolygonDemo extends JPanel
{
public PolygonDemo()
{
setBackground(Color.white);
}
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
Polygon p=new Polygon();
p.addPoint(100,0);
p.addPoint(100,100);
p.addPoint(0,100);
p.addPoint(0,0);
p.addPoint(80,0);
p.addPoint(80,20);
p.addPoint(40,20);
p.addPoint(40,40);
p.addPoint(80,40);
p.addPoint(80,100);
p.addPoint(20,100);
p.addPoint(20,80);
p.addPoint(60,80);
p.addPoint(60,60);
p.addPoint(20,60);
p.addPoint(20,0);
p.addPoint(0,0);
g2.setColor(Color.BLACK);
g2.draw(p);
g2.setColor(new Color(120,250,100));
g2.fillPolygon(p);
//g2.fillPolygon(p.xpoints,p.ypoints,p.npoints);
}
}
Many thanks in advance
Your polygon intersects with itself. The fillPolygon method can not clearly decide which point is in and which is out. From the fillPolygon javadoc:
The area inside the polygon is defined using an even-odd fill rule, also known as the alternating rule.
Perhaps you can split your polygon into three single ones.
Draw Rectangle and Fill Color.....
public void paint(Graphics g)
{
int[] xPoints = {100,50,150};
int[] yPoints = {100,200,200};
g.setColor(Color.black);
g.drawPolygon(xPoints, yPoints, 3);
g.setColor(Color.red);
g.fillPolygon(xPoints, yPoints, 3);
}