Overriding paintComponent() in JButton produces unexpected results - java

I've never written a program with anything more than a bare-bones GUI, so I've undertaken a personal project to write a chess application.
One of my goals with this project was to make the board rescale to fit the window, and I managed to do this without too much trouble. However, in the process I ran into the issue that my pieces (which were represented as Icons on JButtons) did not rescale with the rest of the board.
I decided to represent them with the Image class instead, and made a custom class called ScalingJButton which overrode paintComponent. This actually worked quite well... for the last piece to be drawn. The rest of the pieces are not drawn, and as a result the program is broken. Here is my ScalingJButton class:
public class ScalingJButton extends JButton{
private Image image;
ScalingJButton (){
this.image = null;
}
ScalingJButton (Image image){
this.image = image;
}
public void setImage(Image image){
this.image = image;
}
public Image getImage(){
return image;
}
#Override
public void paintComponent(Graphics g){
//super.paintComponent(g);
int x = getX();
int y = getY();
int width = getWidth();
int height = getHeight();
if (image != null) {
g.drawImage(image, x, y, width, height, this);
}
}}
Additionally, here is the code responsible for instantiating the ScalingJButtons (VisualBoard is a class extending JPanel and this is its constructor).
public VisualBoard (){
white = Color.WHITE;
black = Color.GRAY;
loadPieceImages();
setLayout(new GridLayout(8, 8));
for (int i = 0; i < 8; i++){
for (int j = 0; j < 8; j++){
squares[i][j] = new ScalingJButton();
if ((i + j) % 2 != 0) {
squares[i][j].setBackground(white);
}
else{
squares[i][j].setBackground(black);
}
add(squares[i][j]);
}
}
initializeStandardBoard();
}
Finally, since the layout may be relevant, here is the code that makes the board autoscale:
public class Chess {
public static void main (String[] args){
final VisualBoard board = new VisualBoard();
final JPanel container = new JPanel(new GridBagLayout());
container.add(board);
container.addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
drawResizedBoard(board, container);
}
});
JFrame frame = new JFrame("Chess");
frame.setSize(1000,1000);
frame.add(container);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private static void drawResizedBoard(JPanel innerPanel, JPanel container) {
int w = (int)Math.round(container.getWidth()*0.9);
int h = (int)Math.round(container.getHeight()*0.9);
int size = Math.min(w, h);
innerPanel.setPreferredSize(new Dimension(size, size));
container.revalidate();
}}
I have done extensive debugging, but most of it has lead nowhere. One thing to note is that the "image" variable in ScalingJButton holds the correct picture when drawImage is called, so that's not the issue. Another interesting point is that the square with the piece in it is the the last square to be drawn, and depending on what order I add squares to the Board, different pieces will be drawn (so there is no issue loading pieces).
Strangely, if I do away with super in paintComponent, rolling my mouse over the drawn pieces causes the other squares to fill with that piece when I roll my mouse over them.
I'm completely lost and don't know what to do, so any help would be greatly appreciated!

First, you need to keep the super.paintComponent(g); line. If you don’t, artifacts will appear at seemingly random times.
Second, getX() and getY() return to the component’s position in its parent. When you paint, you are given a coordinate system where 0,0 is the top left corner of the component. So you should ignore getX() and getY().
The simplest alternative is to use the upper-left corner of the button:
int x = 0;
int y = 0;
You may want to account for the button’s border:
if (image != null) {
Rectangle inner = SwingUtilities.calculateInnerArea(this, null);
g.drawImage(image, inner.x, inner.y, inner.width, inner.height, this);
}
And if you want the button to really look like a normal button, you can also account for its margin:
if (image != null) {
Rectangle inner = SwingUtilities.calculateInnerArea(this, null);
Insets margin = getMargin();
inner.x += margin.left;
inner.y += margin.top;
inner.width -= (margin.left + margin.right);
inner.height -= (margin.top + margin.bottom);
g.drawImage(image, inner.x, inner.y, inner.width, inner.height, this);
}

However, in the process I ran into the issue that my pieces (which were represented as Icons on JButtons) did not rescale
The Stretch Icon may be a simpler solution. You can use this Icon on any component that can display an Icon. The image will dynamically change size to fill the space available to the Icon.

There are two suspicious lines in your code. First one is
frame.add(container);
This line adds your Swing element to an AWT container. Instead your should replace it with
frame.setContentPane(container);
The second line is the method
public void paintComponent(Graphics g) {
Try replacing it with
public void paint(Graphics g) {
Hope this helps.
EDIT:
Also change this
g.drawImage(image, x, y, width, height, this);
to
public void paint(Graphics g) {
super.paint(g);
int width = getWidth();
int height = getHeight();
if (image != null) {
g.drawImage(image, 0, 0, width, height, this);
}
}
Your images were simply outside of the grid.

Related

How to extend JComponent and use it to write a customized game board?

I am trying to teach myself more about graphics in Java. To do this I'm trying to build a chess game. I've hit my first roadblock at making the board. My thought here is that I would have a extension of JComponent called "Square" that would be my container for both the color of the board square and the piece on that square (if any). To start with I haven't attempted to include any representation of the piece yet, just the square colors. Later on I hope to have an abstract "Pieces" class that is extended by multiple subclasses representing all the different types of pieces, and add those to each Square as applicable.
When I execute the following, I only get one black square in the upper left hand corner.
ChessBoardTest.java
public class ChessBoardTest {
public static void main(String[] args) {
ChessBoard Board = new ChessBoard();
Board.Display();
}
}
ChessBoard.java
public class ChessBoard extends JFrame {
public static final int FRAME_WIDTH = 500;
public static final int FRAME_HEIGHT = 500;
// Declare instance variables
private Square[][] square = new Square[rows][cols];
private final static int rows = 8;
private final static int cols = 8;
public ChessBoard() {
}
public void Display() {
JPanel Board_Layout = new JPanel();
Board_Layout.setLayout(new GridLayout(8,8));
for(int i=0;i<8;i++)
{
for(int j=0;j<8;j++)
{
if((i+j) % 2 == 0) {
square[i][j] = new Square(1);
Board_Layout.add(square[i][j]);
} else {
square[i][j] = new Square(0);
Board_Layout.add(square[i][j]);
}
}
}
setTitle("Chess Mod");
setSize(FRAME_WIDTH, FRAME_HEIGHT);
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(Board_Layout);
setVisible(true);
}
public void messageBox(String pMessage) {
JOptionPane.showMessageDialog(this, pMessage, "Message", JOptionPane.PLAIN_MESSAGE);
}
}
Square.java
public class Square extends JComponent {
private int color;
public Square(int c) {
this.color=c;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (this.color == 1) {
g.setColor(new Color(0,0,0));
} else {
g.setColor(new Color(255,255,255));
}
g.fillRect(this.getX(), this.getY(), this.getWidth(), this.getHeight());
}
}
I only get one black square in the upper left hand corner.
That's mainly because of the following call:
g.fillRect(this.getX(), this.getY(), this.getWidth(), this.getHeight());
getX() returns the horizontal pixel offset/location of the Component which is invoked upon, relative to the Container that contains that Component. getY() accordingly returns the vertical pixel offset/location of the Component which is invoked upon, relative to the Container that contains the Component.
getWidth() and getHeight() return the size of the Component.
So imagine that the Component at row with index 2 and column with index 3, will have its coordinates at about x == 3 * w / 8 and y == 2 * h / 8 where w and h is the size (width and height respectively) of the parent Container (ie the Board_Layout panel). Let's assume that Board_Layout has a size of 300x300 when you show the graphical user interface... This means that the Square at the location I mentioned will only paint the region which starts at x == 112 and y == 75 and expands at one 8th of the width (and height) of Board_Layout (because there are 8 rows and 8 columns in the grid). But the size of the Square itself is also at one 8th of the width (and height) of Board_Layout, ie about 37x37. So the painted region which starts and expands from the location 112,75 will not be shown at all (because it lies completely outside the Square's size).
Only the top left Square will have some paint on it because its bounds in the parent happen to intersect the drawn region.
To fix this, the location given at the Graphics object should be relative to each Square and not its parent Board_Layout. For example:
g.fillRect(0, 0, getWidth(), getHeight());

Swift: Double Buffering like in Java

In Java, I can write pixel data to an image that is then printed to the screen by overridden methods (paint and paintComponent). I can refresh the screen easily by updating the image and calling refresh(), which calls the paint/paintComponent cycle.
I'm wanting to do this in swift (3) with a UIImage. I can update the image, but I can't figure out how to repeatedly project the image onto the screen, print the screen, and repeat as I need to refresh the screen.
Firstly, what type of swift project (Single view app., Game, etc.) is best for repeatedly refreshing the screen?
And secondly how do I go about creating a (regulated) loop in Swift that refreshes the screen every so often?
In java this would look like (in a class that extends Frame):
static int width = 1440;
static int height = 900;
private BufferedImage canvas;
static Color[][] RGBMap = new Color[width][height];
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setBackground(Color.WHITE);
g2.clearRect(0, 0, width, height);
g2.drawImage(this.canvas, null, null);
paint();
}
public void paint() {
int i = 0;
while (i < width) {
int t = 0;
while (t < height) {
this.canvas.setRGB(i, t, RGBMap[i][t].getRGB());
t++;
}
i++;
}
//Refreshes RGBMap
iterate();
repaint();
}

Setting the length of the window to the correct length using Icon

Using this class and this main method, I am trying to make it so that the window that is created when running the main class is the right size to hold all of the icons that are passed into it without having to resize the window, and without hardcoding a value into what I want the window size to be when I initialize it.
Right now when it runs, the window starts extremely tiny, and as I resize it the layout of all of the icons that are painted onto it are messed up.
I know how to determine the proper size it should be, but I am not sure how I I know using the coordinates ArrayList is how I would determine the size, but I am not sure how I would change the size of the window after it has already been initialized.
public class CompositeIcon implements Icon
{
ArrayList<Icon> iList;
static int width;
static int height;
ArrayList<Point> coordinates;
public CompositeIcon()
{
iList = new ArrayList<Icon>();
coordinates = new ArrayList<Point>();
}
public int getIconHeight()
{
return height;
}
public void addIcon(Icon icon, int x, int y)
{
iList.add(icon);
coordinates.add(new Point(x, y));
}
public int getIconWidth()
{
return width;
}
public void paintIcon(Component c, Graphics g, int x, int y)
{
int i = 0;
for (Icon s : iList)
{
Point offset = coordinates.get(i++);
s.paintIcon(c, g, x + offset.x, y + offset.y);
}
}
This is the main to test it
public static void main (String args[]) {
JFrame frame = new JFrame();
Container panel = frame.getContentPane();
panel.setLayout(new BorderLayout());
CompositeIcon icon = new CompositeIcon();
try {
icon.addIcon(new ImageIcon(new URL("http://th02.deviantart.net/fs71/150/f/2013/103/2/7/java_dock_icon_by_excurse-d61mi0t.png")), 10, 10);
icon.addIcon(new ImageIcon(new URL("http://www.bravegnu.org/blog/icons/java.png")), 5, 370);
icon.addIcon(new ImageIcon(new URL("http://fc03.deviantart.net/fs20/f/2007/274/9/8/3D_Java_icon_by_BrightKnight.png")), 200, 200);
}
catch (MalformedURLException e) {
System.err.println("Apparently, somebody cannot type a URL");
}
panel.add(new JLabel(icon));
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Basically, the width and height of the CompositeIcon should represent the combined width and height's of the icons you add (allowing for the x/y offsets)
Something like...
public void addIcon(Icon icon, int x, int y) {
iList.add(icon);
width = Math.max(width, x + icon.getIconWidth());
height = Math.max(height, y + icon.getIconHeight());
coordinates.add(new Point(x, y));
}
You will need to remove the static declearations for width and height as each instance of CompositeIcon should have it's own width and height
Your approach allows for random positioning of Icons, but it means you are responsible for knowing the size of each Icon and positioning them so the don't overlap one another.
For a more structured approach, you can check out Compound Icon. This class supports vertical, horizontal and stacked Icon alignment and does all the location/size calculations for you.

Graphics of JPanel flickering on left side [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I have a java program that's should be doing something very simple. It contains a JPanel, on which repaint() is called 30 times each second. This JPanel overrides the paintComponent() method, and in this overwritten method, I take a BufferedImage and draw it to the JPanel.
This BufferedImage consists of a black image with a somewhat smaller blue rectangle inside of it. This displays, but the problem is that the left side, 50-80 pixels or so, of the screen flickers. On the leftmost part of what should be the blue rectangle, some of the pixels will sometimes appear black instead, as though there's some black overlay extending from the left side of the screen covering it, that flickers a bit each frame.
I wouldn't think just drawing a rectangle would be so consuming that it would cause graphical bugs with something like this; is it? I can't figure out why this would be happening, so do any of you have any idea what would cause a black "flicker" on the left of either a BufferedImage or a Graphics2D?
'Runnable example(please add the imports)':
public class Panel extends JPanel{
public int width, height;
public long lastTime;
public BufferedImage canvas;
public Panel(int a, int b){
width = a;
height = b;
canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
lastTime = System.currentTimeMillis();
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g.drawImage(canvas, 0, 0, this);
}
public void drawRect(int startX, int startY, int w, int h, int color){
for(int i=0; i<w; i++){
for(int j=0; j<h; j++){
canvas.setRGB(i + startX, j + startY, color);
}
}
}
public void render(){
drawRect(0, 0, width, height, 0x000000);
drawRect(10, 10, width - 20, height - 20, 0x0000ff);
}
public void update(){
int delta = (int)(System.currentTimeMillis() - lastTime);
if(delta >= 1000 / 30){
render();
lastTime = System.currentTimeMillis();
}
}
//in a different class, contains main()
public class Main{
public static Panel pan;
public static void main(String[] args){
JFrame frame = new JFrame();
Container c = frame.getContentPane();
c.setPreferredSize(500, 500);
pan = new Panel(500, 500);
frame.add(pan);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
new runThread().run();
}
class runThread extends Thread{
public void run(){
while(true){
pan.update();
}
}
}
}
Because your program is incorrectly synchronized in several ways, the result is indeterminate. Swing GUI objects must be constructed and manipulated only on the event dispatch thread; this is required on all supported platforms. As discussed in How to Use Swing Timers, this example runs at 50 Hz without flicker.
I have a 15-class program…
In larger programs, you can search for EDT violations using one of the approaches cited here. Also review the animation techniques suggested here.

Java JPanel Scaling

Hey Guys I have succesfully made a GUI in java that will scale polygons and circles using a slider. Everything works but I was wondering if there is a way to change the Origin point(Where it scales from). Right now it scales from the corner and I would like it to scale from the middle so I can start it in the middle and it scales out evenly. Also, If anyone could tell me an easy way to replace the Rectangle I have with an Image of some kind so you can scale the Picture up and down would be great! Thank you! Here is my code:
import javax.swing.*;
public class Fred
{
public static void main(String[] args)
{
TheWindow w = new TheWindow();
w.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //X wont close the window with out this line
w.setSize(375,375);
w.setVisible(true);
}
}
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
public class TheWindow extends JFrame
{
private JSlider slider; //declare slider
private drawRect myPanel; //declare/ create panel
public TheWindow()
{
super("Slider Example"); //make title
myPanel = new drawRect();
myPanel.setBackground(Color.green); //change background color
slider = new JSlider(SwingConstants.VERTICAL, 0, 315, 10);// restrains the slider from scaling square to 0-300 pixels
slider.setMajorTickSpacing(20); //will set tick marks every 10 pixels
slider.setPaintTicks(true); //this actually paints the ticks on the screen
slider.addChangeListener
(
new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
myPanel.setD(slider.getValue()); //Wherever you set the slider, it will pass that value and that will paint on the screen
}
}
);
add(slider, BorderLayout.WEST); //similar to init method, adds slider and panel to GUI
add(myPanel, BorderLayout.CENTER);
}
}
import java.awt.*;
import javax.swing.*;
public class drawRect extends JPanel
{
private int d = 25; //this determines the beginning length of the rect.
public void paintComponent(Graphics g)//paints circle on the screen
{
super.paintComponent(g); //prepares graphic object for drawing
g.fillRect(15,15, d, d); //paints rectangle on screen
//x , y, width, height
}
public void setD(int newD)
{
d = (newD >= 0 ? newD : 10); //if number is less than zero it will use 10 for diameter(compressed if statement)
repaint();
}
public Dimension getPrefferedSize()
{
return new Dimension(200, 200);
}
public Dimension getMinimumSize()
{
return getPrefferedSize();
}
}
Changing the "origin point" so it becomes the center of the "zoom" is basically just the process of subtract half of d from the center point.
So, assuming the the center point is 28 ((25 / 2) + 15), you would simply then subtract d / 2 (25 / 2) from this point, 28 - (25 / 2) = 15 or near enough...
I modified the paintComponent method for testing, so the rectangle is always at the center of the panel, but you can supply arbitrary values in place of the originX and originY
#Override
public void paintComponent(Graphics g)//paints circle on the screen
{
super.paintComponent(g); //prepares graphic object for drawing
int originX = getWidth() / 2;
int originY = getHeight() / 2;
int x = originX - (d / 2);
int y = originY - (d / 2);
System.out.println(x + "x" + y);
g.fillRect(x, y, d, d); //paints rectangle on screen
//x , y, width, height
}
As for scaling an image, you should look at Graphics#drawImage(Image img, int x, int y, int width, int height, ImageObserver observer), beware though, this will scaling the image to the absolute size, it won't keep the image ratio.
A better solution might be to use a double value of between 0 and 1 and multiple the various elements by this value to get the absolute values you want

Categories

Resources