Java: Using paint() doesn't make lines appear

Discussion in 'Mac Programming' started by jsmwoolf, Sep 14, 2011.

  1. jsmwoolf macrumors regular

    Joined:
    Aug 17, 2011
    #1
    I decided to stop making my calculator application for and decided to stick with Java for now. I'm trying to draw graphics for a Connect 4 game. I did a Tic-Tac-Toe game and actually implemented an A.I. without help so I wanted to tackle on something easier than checkers, but harder than Tic-Tac-Toe. The problem is that when I try to invoke the paint() method, nothing seems to appear.

    Code:
    import javax.swing.*;
    import java.awt.*;
    import javax.swing.JPanel;
    
    public class Connect4 {
    	
    	Control control;
    	Drawings drawings = new Drawings();
    	
    	JFrame window = new JFrame("Connect 4");
    	//Row 1
    	JPanel row1 = new JPanel();
    	JCheckBox AI = new JCheckBox("AI");
    	JButton start = new JButton("Start");
    	
    	//Row 2
    	JPanel row2 = new JPanel();
    	JButton[] rowButton = new JButton[7];
    	Dimension rowButtonSize = new Dimension(32,32);
    	
    	public Connect4(Control control)
    	{
    		this.control = control;
    		window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		window.setSize(300,365);
    		GridLayout layout = new GridLayout(9,1,1,1);
    		window.setLayout(layout);
    		
    		FlowLayout layout1 = new FlowLayout();
    		row1.setLayout(layout1);
    		row1.add(AI);
    		row1.add(start);
    		window.add(row1);
    		
    		FlowLayout layout2 = new FlowLayout();
    		row2.setLayout(layout2);
    		for(int x = 0; x<7;x++)
    		{
    			rowButton[x] = new JButton(Integer.toString(x+1));
    			row2.add(rowButton[x]);
    			rowButton[x].setPreferredSize(rowButtonSize);
    			rowButton[x].setEnabled(false);
    		}
    		window.add(row2);
    		
    		window.add(drawings);
    		drawings.repaint();
    		
    		window.setVisible(true);
    	}
    	
    }
    
    class Drawings extends JPanel
    {
    	//Paint
    	int changingX = 20;
    	int changingY = 100;
    	
    	public Drawings()
    	{
    
    	}
    	
    	public void paint(Graphics g)
    	{
    		super.paint(g);
    		g.setColor(Color.black);
    		g.drawLine(changingX, 100, changingX, 350);
    	}
    	
    	public void paintComponent(Graphics graphic)
    	{
    		Graphics2D graphic2d = (Graphics2D) graphic;
    		graphic2d.setColor(Color.black);
    		graphic2d.drawLine(changingX, 100, changingX, 350);
    	}
    }
    Note: You need the Control class because the Control has the main event and will not executes without it.

    Code:
    public class Control {
    
    	Connect4 game = null;
    	
    	public Control(Connect4 game)
    	{
    		game = new Connect4(this);
    		this.game=game;
    		
    	}
    	
    	public static void main(String[] args) {
    		Control control = new Control(null);
    	}
    }
    Why isn't this working? Did I forget to declare something?
     
  2. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #2
    Don't override paint() at all. Only override paintComponent().

    The standard JComponent.paint() method already does what it needs to do. If you override it, then it doesn't call paintComponent() or any of the other things needed by a lightweight component.

    Did you follow the custom JComponent tutorial I pointed you to before? Did they override paint()?


    I'll try compiling and running this later.
     
  3. jsmwoolf thread starter macrumors regular

    Joined:
    Aug 17, 2011
    #3
    Are you referring to "super.paint(g)" or the "drawings.repaint()"?
     
  4. dylanryan macrumors member

    Joined:
    Aug 21, 2011
    #4
    He meant that you redefined the paint method inside of Drawings, when you should only be defining paintComponent.

    Anyway, I just tried out the code, and it IS drawing the lines. The problem is, the component isn't tall enough to show the line. Try adding the following right after casting graphic to graphics2d inside of repaintComponent, and you will see part of it white... it just isn't tall enough to reach the black line. Vertically resizing the window allows you to eventually see the top part of a line.

    Code:
    graphic2d.setColor(Color.white);
    graphic2d.fillRect(0,0,500,500);
    
    I haven't worked in Java for a long time, so I actually forget how to force that panel to have a set size inside a GridLayout, but I'm sure someone else (or even google) can help you there.
     
  5. jsmwoolf thread starter macrumors regular

    Joined:
    Aug 17, 2011
    #5
    I believe you're referring to GridBagLayout in which I just checked?
     
  6. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #6
    It's not possible to force a component in a GridLayout to have a certain size. The whole point of GridLayout is that it sizes components equally across all its rows and columns. The GridLayout itself is in charge. Components within the GridLayout are not.

    I don't really see how the 9-row 1-column GridLayout of the original code is intended to be used. Maybe each row is supposed to hold a custom component that draws one "line" of the Connect-4 "board". If so, then the posted code is missing a lot, such as 6 of the 7 custom board-drawing components.


    An inner panel with 7x7 GridLayout within an outer panel with a BorderLayout might be easier to implement. Each "cell" in the GridLayout is a custom component. It draws one of 3 things:
    1. Nothing.
    2. Red circle.
    3. Yellow circle.

    Each cell is then connected to a corresponding cell in the game-logic (model), and when a turn is played, only the cell that changes state needs to be repaint()'ed.

    Any lines between cells can be done by giving the JPanel with the 7x7 GridLayout a background color, and create the GridLayout with 1 or more pixel gap between elements.

    The custom drawing code doesn't have to be complex. It can simply draw a filled ellipse of the proper color inscribed within its bounding rectangle (x, y, width, height). Keeping the shape circular can be left to standard component methods that set the preferred, max, min sizes, etc.

    Frankly, I'd start with a 2x2 cell game-board and get that working properly. The AI doesn't even have to play well, it just has to make a choice when it's its turn, and the whole thing should draw properly. Starting with something simple that works is a common strategy. It's a lot easier to figure out when it doesn't work, and it still has the same kind of parts that the real thing will have, just not as many.

    This is assuming the basic Connect Four game described on Wikipedia:
    http://en.wikipedia.org/wiki/Connect_Four
     
  7. jsmwoolf, Sep 15, 2011
    Last edited: Sep 15, 2011

    jsmwoolf thread starter macrumors regular

    Joined:
    Aug 17, 2011
    #7
    Well, now I realized my mistake on the 9,1,1,1 part just a while ago.

    I did a Tic-Tac-Toe game(3x3) using JButtons. Beside I would start small first anyway by drawing it to one square and then when that works, to another one.

    EDIT: I instead use FlowLayouts. I set a size for the panel that deals with the drawing, but I can't access the paintComponent at all even when using repaint().
    Code:
    import javax.swing.*;
    import java.awt.*;
    import javax.swing.JPanel;
    
    public class Connect4 {
    	
    	Control control;
    	Drawings drawings = new Drawings();
    	
    	JFrame window = new JFrame("Connect 4");
    	//Row 1
    	JPanel row1 = new JPanel();
    	JCheckBox AI = new JCheckBox("AI");
    	JButton start = new JButton("Start");
    	
    	//Row 2
    	JPanel row2 = new JPanel();
    	JButton[] rowButton = new JButton[7];
    	Dimension rowButtonSize = new Dimension(32,32);
    	
    	
    	public Connect4(Control control)
    	{
    		this.control = control;
    		window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		window.setSize(300,365);
    		window.setResizable(false);
    		FlowLayout layout = new FlowLayout();
    		window.setLayout(layout);
    		
    		FlowLayout layout1 = new FlowLayout();
    		row1.setLayout(layout1);
    		row1.add(AI);
    		row1.add(start);
    		window.add(row1);
    		
    		FlowLayout layout2 = new FlowLayout();
    		row2.setLayout(layout2);
    		for(int x =0; x < 7; x++)
    		{
    		rowButton[x]= new JButton(Integer.toString(x+1));
    		rowButton[x].setPreferredSize(rowButtonSize);
    		row2.add(rowButton[x]);
    		}
    		window.add(row2);
    		window.add(drawings);
    		drawings.setPreferredSize(new Dimension(300,250));
    window.setVisible(true);
    	}
    	
    }
    
    class Drawings extends JPanel
    {
    	//Paint
    	int changingX = 40;
    	int changingY = 0;
    	int drawX;
    	int drawY;
    	
    	public Drawings()
    	{
    
    	}
    	
    	public void paint(Graphics g)
    	{
    		System.out.println("Invoked");
    		g.setColor(Color.black);
    		for(int x = 0; x < 8; x++)
    		{
    		System.out.println(changingX);
    		g.drawLine(changingX, 0, changingX, 192);
    		changingX+=32;
    		}
    		for(int x = 0; x < 7; x++)
    		{
    			System.out.println(changingY);
    			g.drawLine(40, changingY, 264, changingY);
    			changingY+=32;
    		}
    		changingX = 40;
    		changingY = 0;
    	}
    	
    	public void paintComponent(Graphics g)
    	{
    		super.paintComponents(g);
    		Graphics2D g2D = (Graphics2D)g;
    		drawCircle(g2D, false, drawX, drawY);
    		
    	}
    	
    	void drawCircle(Graphics g, boolean isPlayer, int drawX, int drawY)
    	{
    		System.out.println("Drawing a circle!");
    		g.drawOval(drawX, drawY, 32, 32);
    	}
    }

    Code:
    public class Control {
    
    	Connect4 game = null;
    	int[][] theBoard = new int[7][6];
    	
    	public Control(Connect4 game)
    	{
    		game = new Connect4(this);
    		this.game=game;
    		game.drawings.drawX = 40;
    		game.drawings.drawY = 192;
    		game.drawings.repaint();
    	}
    	
    	public static void main(String[] args) {
    		Control control = new Control(null);
    	}
    }
    What else did I do wrong?
     
  8. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #8
    I've mentioned this at least twice now, but you're still overriding paint().
    Do not override paint() in your JComponent subclass.

    When you override paint(), and don't call super.paint(), none of the normal JComponent methods (paintChildren, paintComponent, paintBorder) will be called. You may think overriding paint() is unimportant, but I assure you it's very important. You're doing it completely wrong.

    I'm also pretty sure you haven't gone through the tutorial I previously linked to about creating a custom-drawing JComponent. If you had, you wouldn't be overriding paint().

    Exactly which book or tutorial on custom drawing are you following? Because you really need to learn how to do custom drawing correctly before you can proceed.


    Your paintComponent() method has this:
    Code:
    		super.paintComponents(g);
    
    Unfortunately, that is the completely wrong method to call. The spelling is wrong: you have the plural, when it should be the singular.

    Because you overrode paint(), your paintComponent() is never called, so this error has no effect.

    If your paintComponent() were called, it would still be completely wrong.

    As designed, your Drawings class draws the entire game board. Therefore, it must draw all the lines and all the pieces. Your paint() method draws the lines of the board, but it does nothing to draw circles. Your paintComponent() only draws one circle, therefore it's not drawing all the pieces. If you think you only need to draw one circle, then you don't understand the difference between a container class (like JPanel) and a component class. You need to revisit how Swing containers and components work.

    If you don't want to draw all the lines and all the pieces in a single Drawings instance, then you need to redesign your classes. Make a single Cell class, that subclasses JComponent. Have it draw a single circle in paintComponent(). Make a single JPanel with a 7x7 GridLayout. Create and add a total of 49 Cell components to the grid panel. The JPanel with the GridLayout is a standard class, not a subclass.

    None of your classes or subclasses should override paint(). That means none of your subclasses should contain a paint(Graphics) method.


    This code in your Control class is wrong:
    Code:
    		game.drawings.drawX = 40;
    		game.drawings.drawY = 192;
    
    All that does is offset any drawing of circles so anything drawn is outside the bounds of the component doing the drawing. You won't see it.

    The drawing won't happen anyway, because paintComponent() isn't being called, but if it were, and if drawCircle() were being called, it would draw outside the component's clip region, so nothing would appear.

    Remember every component has its own top left coordinate of 0,0. So if you draw a circle from that point to 32,32, then it will be within the component's bounds, assuming the bounds are at least 32x32.
     
  9. jsmwoolf thread starter macrumors regular

    Joined:
    Aug 17, 2011
    #9
    Okay, now I understand your intention. Now it works and I got what I want. However, I apologize for pissing you off 3-4 times in trying to help me. I didn't entirely grasp your point until now. I did read it, but I didn't entirely understand the tutorial.
     

Share This Page