Is there any way to correct the dimensions for a rotated component?
In my swing application I’d like to be able to rotate a single panel, and have it respond correctly to resize events. The rotation is straight-forward, but on resizing, the height and width dimensions are reversed. It could almost would work if a call to setSize was called from an overridden paint() call to swap the dimensions, but that doesn’t work, because setSize causes another call to paint to occur, and recursion ensues... I’ve also tried adding a ComponentAdapter to handle the swapping on resize events, but got the same result.
So, here’s a simplified example of what I’m working with. The components here are buttons, but the logic applies to a JComponent like a JPanel too. Button c is rotated with a JXTransformer, but this doesn’t resize (it’s commented out in the code, but you can add the JXTransformer class to the classpath if you wish). If you compile the sample, try resizing the window and see how the rotated button behaves. Screenshot:
(It said I can't post screenshots, but these links appear to be live..)
http://i.stack.imgur.com/S3qmb.png
If I add in a scale transformation, the resizing is correct, but the component is distorted beyond usability. Screenshot:
http://i.stack.imgur.com/K4l9e.png
I’ve seen lots of questions on here that discuss the rotating part, but nothing about the resizing issue. For instance, A rotated square panel in Java GUI
Thanks!
Code:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class RotatingTest {
public static void main(String[] args) {
JFrame frame = new JFrame();
JPanel panel = new JPanel(new BorderLayout());
// The button rotates, but the height/width dimensions are incorrect
RotatedButton a = new RotatedButton("ROTATED CONTENTS!");
JButton b = new JButton("Normal Contents");
JButton c = new JButton("Transformer Contents");
// JXTransformer t = new JXTransformer(c);
// t.rotate(Math.toRadians(90));
panel.add(a, BorderLayout.CENTER);
panel.add(b, BorderLayout.WEST);
// panel.add(t, BorderLayout.EAST);
panel.setBorder(BorderFactory.createEmptyBorder(30, 30, 30, 30));
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}
class RotatedButton extends JButton{
private static final long serialVersionUID = -3355833063753337573L;
RotatedButton(String string){
super(string);
}
#Override
protected void paintComponent(Graphics g) {
int width = getWidth();
int height = getHeight();
Graphics2D graphics = (Graphics2D) g;
AffineTransform txOrig = graphics.getTransform();
AffineTransform transformer = new AffineTransform(txOrig);
transformer.translate(width/2, height/2);
transformer.rotate(Math.toRadians(90));
transformer.translate(-height/2, -width/2);
// this scaling fits the button to the window, but distorts the contents
// double coef1 = (double)width / (double)height;
// double coef2 = (double)height / (double)width;
// transformer.scale(coef2, coef1);
// this line sets the rotation, comment out to disable
graphics.setTransform(transformer);
super.paintComponent(graphics);
graphics.setTransform(txOrig);
graphics.dispose();
System.out.println("Parent size: "+getRootPane().getParent().getSize());
System.out.println("this size: "+getSize());
}
}
So, the problem here is that JComponents do not care what is actually on them when setting their size. When you modify something in paintComponent() it only affects superficial portions of the component, and not anything that will actually be returned with from the component. What you are going to want to do, is remodel your paintComponent() method, or change how you are resizing the component.
Think of it like when you place an image on a button that is too large. The button will simply display what it can, and the rest does not matter as far as the button is concerned. Rotating the graphics on a JComponent is the exact same way. You can create the graphics ahead of time, and set your size to that (this is the complicated way), or if you are only rotating 90 degrees, simply change what you put int for setSize(). I have always found the second to be very simple, as you can constantly change the size of it with no extra code, you just have to remember to switch all your dimensions.
If, however, you wish to put it at an angle that is not a multiple of 90 degrees, you will have to make a larger square for the component to sit in. (using the Pythagorean theory) This may seem silly, but think about how all coordinates are made, from the top left corner. that corner defines that object's location, but if it is not at an absolute (the highest leftmost point) then anything working with that component would have to manually look at every part of the component instead of just calculating it.
Hope this helps, late as it may be.
Related
Although in this example, the X-Y values are hard-coded, lets assume the user entered the values dynamically and clicked a button to view the results on the screen.
It wouldn't make sense to calculate the frame based on the largest size as the Frame would be too large for the monitor.
What is required to take the X-Y values entered (not matter how large or small) and have the image appear centered within the frame?
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ZoomToXY extends JPanel
{
int x = 0;
public void paint(Graphics g)
{
//Can't see this.
int[] xs2 = {5308, 5306, 4143, 4143, 4120, 4119, 4118, 4117, 4116, 4114, 4112};
int[] ys2 = {4474, 5329, 5306, 5171, 5171, 5173, 5175, 5177, 5179, 5181, 5182};
BasicStroke traceStroke = new BasicStroke (1); //Line thickness
Graphics2D gc = (Graphics2D) g.create();
gc.setColor(Color.RED);
gc.drawPolyline(xs2, ys2, 11);
gc.setStroke(traceStroke);
x++;
}
public static void main(String[] args)
{
JFrame frame = new JFrame();
frame.add(new ZoomToXY());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(20,20, 250,250);
frame.setVisible(true);
}
}
The reason we can't see the polygon or whatever you're making, is because it's outside the frame's bounds. Let's take a look.
frame.setBounds(20,20, 250,250);
This line indicates we will only see what's inside these bounds, though everything outside will also be drawn but not shown. Try drawing a rectangle inside the bounds and see.
g.fillRect(20, 20, 100, 100);
You will see a rectangle. But how can I solve this issue? Since having a frame being 5000px by 5000px isn't going to work on most monitors, either you work with smaller resolutions and therefore smaller coordinates, or you implement a camera. Having a camera you can have as big world as you want and being able to move around in it. But if your frame can only show 100 pixels and your polygon is 1000px, we will only see 10% of it, this problem can easily be solved with zooming. Here is a topic how to implement a gamecamera. With the gameCamera you can simply calculate the center of your image, then translate it, quite simple. If you need assistance just ask.
A frame that is 250x250 is quite small, consider it being a little bigger. Also why set the coordinates as (20, 20)? If you want to center the JFrame to the current monitor just call:
frame.setLocationRelativeTo(null);
I am attempting to make a Connect Four game to improve my ability with Java Graphics and as a school project. The background for the game will be a blue JPanel and the game board will be a separate JPanel that will be placed on top of the background. See my classes below:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class gameBoard extends JPanel {
private Board bored;
public gameBoard(){
setLayout(new BorderLayout());
bored = new Board();//does not appear in Center Region of gameBoard
add(bored, BorderLayout.CENTER);
}
public void paint(Graphics g){//This line is the one that is acting weird.
//blue rectangle board is here, but when repaint called
//JFrame turns blue and does not add new JPanel called above
g.setColor(Color.BLUE);
g.fillRect(0, 0, 1456, 916);
}
}
AND
import java.awt.BasicStroke;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Graphics2D;
import javax.swing.JPanel;
public class Board extends JPanel {
/*
* 2d array will represent board and take 1's(red) and 2's(black) the nums
* represent pieces, with each redraw of the board, a check will be done to
* compare a sum against blackWin and redWin. Sum will be created by
* summing a continuous line of 1's or 2's going left -> right
*/
public int[][] boardEvalMatrix = new int[6][7];
private final int blackWin = 8, redWin = 4;
public Board() {//1200 x 764
BoardMethods a = new BoardMethods();
a.printBoard(getBoard());
JPanel panelLORDY = new JPanel(new FlowLayout());
repaint();
}
public int[][] getBoard(){
return boardEvalMatrix;
}
public void paint(Graphics g){
g.setColor(Color.BLUE);//Drawing background with actual board as a Test
g.fillRect(0, 0, 1456, 916);//will not remain like this
Graphics2D newG = (Graphics2D) g;
newG.setStroke(new BasicStroke(15));
g.setColor(Color.YELLOW);
for(int a = 0; a < 6; a++)//rows for board --- rowHeight is 127
g.drawRect(128, 68+ (a*127), 1200, 127);
//g.setColor(Color.BLACK);
//newG.setStroke(new BasicStroke(8));
//for(int a = 0; a < 7; a++)//columns for board --- columnWidth is 171
// g.drawRect(208, 152, 70, 10);
//g.drawLine(50,0, 1456, 916); //width 1456 length 916 - school computer monitors
}
}
So what happened is this:
PROBLEM 1:
When I include the public void paint(Graphics g) line in the gameBoard class, the display that appears when I run the driver is just a gray JFrame, even though there is no call to repaint() and the paint() method is empty. However, when I deleted the line creating the paint method, the problem disappeared and the proper display appeared.
PROBLEM 2:
Even when I placed the code to draw a blue rectangle in the paint method in the gameBoard class and called repaint() the JFrame was blue, which is partly right. I know that Java executes commands from top to bottom so I made sure that the code adding the actual game board to the gameBoard JPanelcame after drawing a blue rectangle, but it didnt work.
QUESTION:
What did I do wrong and how do I fix it?
To change the background color of a panel you just use:
setBackground( Color.BLUE );
on the panel. Then is no need for custom painting.
When you override paint() and forget the super.paint(), then you really mess up the painting process. The paint() method is responsible for painting the child components on the panel. Since you don't invoke super.paint() the children never get painted.
If you do need custom painting for some reason then you should override the paintComponent() method and don't forget to invoke super.paintComponent(). Don't override paint().
Read the Swing tutorial on 'Custom Painting`, especially the section on A Closer Look at the Paint Mechanism for more information and examples.
Hey :) So I'm making buttons for a game I'm making. The graphics work, at least to the extent that I don't have to fix them yet. However, click detection is a bit iffy. For example pressing where the black line is in the first picture below, triggers a response. Now obviously that point is not on the button. I have tested the buttons bounding box by drawing a rectangle around it, using it's getBounds() method (which is also used for click detection) and it draws a perfect rectangle around it. So then I tested the mouse click points and it turns out that even though the button is placed at y = 100, at the black line, the mouse point is also equal to 100... Now I have no idea why that is happening, especially because, if I place the button in the top left corner, the mouse detection correctly detects the top pixels and there is no offset...
This is rather interesting, and during my times in have had similar problems. This all really depends on why the Mouse Listener is attached to. Many people attach the listener to the frame, but draw on a panel. This can have the effects you are describing so it is usually better to either draw directly onto the frame, or attach the listener to the panel. In 99.99% of cases, I would always choose the latter. Really, no one should ever choose the latter UNLESS it's something very small.
Panels are exactly that; they're boxes which hold things, hence 'panel'. In my experiences it has always been more effective to use a panel. Frames are just the container to hold multiple panels.
Hope I could help, report your findings in a comment and/or post update.
Jarod.
Got bored so I whipped up an example of what I think is going on.
In essence, I do full rendering to a buffer (BufferedImage here). And then draw the render to the canvas. This may or may not be what you do, but I did it merely for example.
Seeing as you did say that it works fine in the top-left corner, I came to the hypothesis that scaling is the issue, since the x,y-values near the top left approach 0, and 0 * scale = 0, even a scaling of 1000 won't have any offset. The issue is when those components are not at the top-left corner, which you demonstrated for us.
Hopefully this answers your question. As for solving it, you can either accommodate for scaling, or use a letterboxing technique. Beyond those two, there are certainly many other ways to deal with this (such as fixing the screen size).
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* #author Obicere
*/
public class GraphicScale {
public GraphicScale(){
final JFrame frame = new JFrame("Graphic Scale Example");
final MyPanel panel = new MyPanel();
final Timer repaintTimer = new Timer(50, e -> frame.repaint());
frame.add(panel);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
repaintTimer.start();
}
public static void main(final String[] args){
SwingUtilities.invokeLater(GraphicScale::new);
}
public class MyPanel extends JPanel {
private final Rectangle box = new Rectangle(100, 100, 100, 50);
private final Dimension size = new Dimension(500, 500);
private final BufferedImage render = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB);
#Override
public void paintComponent(final Graphics g){
super.paintComponent(g);
render.flush();
render();
g.drawImage(render, 0, 0, getWidth(), getHeight(), this); // Trick is that this gets rescaled!
}
public void render(){
final Graphics2D g = (Graphics2D) render.getGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, render.getWidth(), render.getHeight());
final Point mouse = getMousePosition();
if(mouse != null){
if(box.contains(mouse)) {
g.setColor(Color.GREEN);
g.fill(box);
}
g.setColor(Color.DARK_GRAY);
g.drawOval(mouse.x - 3, mouse.y - 3, 6, 6);
}
g.setColor(Color.BLACK);
g.draw(box);
}
#Override
public Dimension getPreferredSize(){
return size;
}
}
}
Ok, so it turns out that there was some scaling going on with the frame, however I have no idea where it came from. I prepped the game to be scalable so I did all the painting to the optimal size BufferedImage and then I scale that image to the frame. However, even when I removed that the mouse location was still offset. In the end I overcame it by finishing the scaling optimization which required finding the scale of the frame by dividing the current width and height by the optimal width and height. And then dividing the mouse location by that value.I just figured this out. Setting the size of a component and packing the frame after adding the component, results in the actual frame being that size (counting the border), yet when you retrieve the size of the frame, it disregards the border... Why does this happen?
Solved
When I did the game screen scaling, I used the actual frame's height and width to scale the screen, instead of the canvas's height and width. I changed that and now it works perfectly!
I am having trouble doing a rather simple task of taking in the diameter of a circle and then drawing it. Here is my code so far.
import javax.swing.*;
import java.awt.Graphics;
public class Shapes extends JFrame
{
double diameter;
public Shapes()
{
setSize(600,600);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public void getDiameter()
{
String input = JOptionPane.showInputDialog("What is the diameter of the circle?");
diameter = Double.parseDouble(input);
Shapes gui = new Shapes();
gui.setVisible(true);
}
public static void main(String[] args)
{
Shapes app = new Shapes();
app.getDiameter();
}
public void paint(Graphics canvas)
{
canvas.drawOval(50, 50, (int)diameter, (int)diameter);
}
}
When I run it, it brings up the Jframe window, but nothing is drawn, so I'm guessing the value of diameter is never passed to the paint method. Can someone help me get this to work? Thanks.
Your program is creating two Shapes objects actually, one of which has the diameter field set correctly but is not being displayed, and the other, which retains diameter's default value of 0 and which is displayed.
Suggestions:
Don't draw directly in a JFrame, but rather in the paintComponent(Graphics g) method override of a JPanel that is held by and displayed in the JFrame. There are many reasons for this, but for one, since the paint(...) method is not only responsible for painting a component but also its borders and children, this will prevent you from causing problems when paint(...) tries to paint a GUI's children and borders. It also will help your animations (which surely you will be doing soon) to be smooth given Swing component's default use of double buffering.
Always call the super.paintComponent(g) method within your JPanel's paintComponent override. This will allow Swing to erase images that need to be erased.
Don't create two Shapes objects, but rather only one. This will simplify things greatly and will allow you to set the diameter value of the one and only object of importance.
After changing the diameter field's value, call repaint on your GUI so that the displayed JPanel's paintComponent will be called.
While playing around with the Java font class and Swing, I set the font size to a negative value.
I discovered that this makes the text be drawn upside down. Is this a bug or a feature? Can anyone explain why this behavior happens?
Try it out:
import java.awt.Font;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class UpsideDown extends JFrame{
public UpsideDown(){
setSize(500,500);
setContentPane(new Panel());
setVisible(true);
}
public class Panel extends JPanel{
public void paintComponent(Graphics g){
Font f = new Font("Sans-Serif", Font.PLAIN, -50);
g.setFont(f);
g.drawString("Upside Down", 400, 100);
}
}
public static void main(String args[]){
new UpsideDown();
}
}
Seems like this is happening:
Swing draws your font's height downwards, because it multiplies the font size with the glyph height of the font. -50 * glyph_height is negative -> drawing downwards instead of upwards.
It also draws the glyph's (the letter's) width to the left, again because it multiplies your font size with the glyph width specified by the font.
It is a feature.
In Swing there are very few, if any, absolute quantities. Virtually all quantities are algebraic, meaning that they can be negative, and they can participate in algebraic calculations which may change their sign, as the case is when you multiply by -1. For example, a rectangle can have a negative width, and that's perfectly fine. A font is not an exception to this rule.
By all means, do try this at home:
Take any piece of code that you might have lying around which draws 2D graphics in a Graphics2D graphics context. (That would be a component that overrides paintComponent( Graphics g ) and begins with Graphics2D g2 = (Graphics2D)g;) For example, you might have a component that draws a graph, like so:
Before the drawing operations, insert the following two lines:
g2.scale( -1.0, -1.0 );
g2.translate( -getWidth(), -getHeight() );
Now check the result. It will all be perfectly up-side down, like this:
Needless to say, if you double the x scale without doubling the y scale, everything will be elongated, including the characters of the text, like so:
This demonstrates how everything, all the way down to the individual coordinates of character glyphs, is an algebraic quantity in Swing.
This gives a great degree of freedom. If you want to invert the graph but keep the text upright, one way to achieve it is to make the font size negative.