1

Frame -> MainContentPane -> MapPanel -> Map -> Tile[]

JFrame -> JPanel(new GridBagLayout) -> JPanel -> JPanel(new GridLayout(31,30) -> JComponent

I'm trying to paint 32x32 pix tiles on a JPanel, but no matter where I call repaint(); it will only paint the tiles if I call validate(); on the JFrame. If I bypass Panels and paint directly (use of the draw() methods) onto the MapPanel the images will only paint if I resize or move the JFrame so that the Frame has to be repainted by the repaintmanager. However calling repaint(), validate() or both on my ContentPanel will not paint the images.

If I understand how Java.swing paints thing right, if I call a repaint on the highest level Component it should repaint all child Components that the repaintmanager thinks should be repainted. Since I am adding components after the frame has been set to visible I need to call validate() on the highest level Component to tell the repaintmanager that there are new things. Am I right with this understanding?

Things that will not work:

telling me to add all the components before setting the frame to visible. The Map and Tile[] are going to be changing quite regularly, and it would be very impractical to reset the Frame every time I add/remove something.

public class NetScore {

    public static void main(String[] args) {
        MapPanel mapPanel = new MapPanel();
        InfoPanel infoPanel = new InfoPanel();
        ImageLoader imageLoader = new ImageLoader();

        Player player = new Player("Tester", imageLoader);

        JPanel contentPane = new MainContentPane((JPanel)mapPanel, (JPanel)infoPanel);

        System.out.println(mapPanel.getHeight());
        System.out.println(mapPanel.getWidth());

        MapBuilder mapBuilder = new MapBuilder(player, imageLoader);

        Map map = new Map(mapBuilder, mapPanel, player);
        map.drawMap();

        System.out.println();
    }

    }
public class MapPanel extends JPanel implements ActionListener{

    Map map;
    Timer clock;
    public MapPanel(){
        clock = new Timer(500, this);
        clock.start();

    }

    public void addMap(Map map){
        this.map = map;
        this.add(map);
        this.validate();
    }

    public void paintComponent(Graphics g){
        super.paintComponent(g);
        System.out.println(map == null);
        System.out.println("paint mapPanel");
        Graphics2D g2 = (Graphics2D) g;
        if(map == null){
            //map.draw(g2);
        }
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        //repaint();

    }
    }
public class MainContentPane extends JPanel implements ActionListener{

    public JFrame frame;
    Timer clock;

    public MainContentPane(JPanel mapPanel ,JPanel infoPanel){
        clock = new Timer(500, this);
        clock.start();
        frame = new Frame();
        JPanel contentPane = new JPanel();
        frame.setContentPane(contentPane);
        contentPane.setLayout(new GridBagLayout());
        GridBagConstraints c = new GridBagConstraints();

        c.weightx = 2;
        c.gridx = 0;
        c.gridy = 0;
        c.fill = GridBagConstraints.BOTH;
        contentPane.add(mapPanel, c);

        c.weightx = 1;
        c.weighty = 1;
        c.gridx = 1;
        c.gridy = 0;
        c.fill = GridBagConstraints.BOTH;


        contentPane.add(infoPanel, c);
        frame.setVisible(true);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        repaint();

    }

    class Frame extends JFrame{

        public Frame(){
            this.setTitle("netScore");
            this.setDefaultCloseOperation(EXIT_ON_CLOSE);
            this.setBounds(100, 10, 1500, 1000);
            this.setResizable(false);
        }

    }
    }
public class Map extends JPanel{


    private Tile[][] tiles;
    private MapBuilder mapBuilder;
    private Player player;


    public Map(MapBuilder mapBuilder, MapPanel mapPanel, Player player){
        this.player = player;
        this.mapBuilder = mapBuilder;
        this.setLayout(new GridLayout(31,30));
        mapPanel.addMap(this);
    }

    public void loadMap(){
        tiles = mapBuilder.buildMap();
    }

    public void drawMap(){
        loadMap();
        this.removeAll();
        for(int i = 0; i < tiles.length; i++){
            for(int p = 0; p < tiles[i].length; p++){
                this.add(tiles[i][p]);
            }
        }
        validate();
    }
    public void draw(Graphics2D g2){
        if(tiles != null){
            for(int i = 0; i < tiles.length; i++){
                for(int p = 0; p <tiles[i].length; p++){
                    tiles[i][p].draw(g2, i*32, p*32);
                }
            }
        }
    }

      //    private class GlassPanel extends JComponent{
      //        
      //        
      //        @Override
      //        protected void paintComponent(Graphics g) {
      //            super.paintComponent(g);
      //            Graphics2D g2 = (Graphics2D) g;
      //            g2.drawImage(player.getImage(), player.getX(), player.getY(), null);
      //            
      //        }
      //        
      //    }
      }
public class Tile extends JComponent{

    private int id;
    private boolean collision;
    private BufferedImage image;

    public Tile(char diCo, int id, ImageLoader imageLoader){
        this.id = id;

        collision = (Character.isUpperCase(diCo));
        image = imageLoader.getTileImage(id, diCo); 
        setPreferredSize(new Dimension(32, 32));
    }

     // public Dimension getPreferredSize(){
     //     return new Dimension(32,32);
     // }

    public boolean isCollision() {
        return collision;
    }

    public void draw(Graphics2D g2, int x, int y){
        System.out.println("paint tile, id "+ id);
        g2.drawImage(image, null, x, y);
    }

    public void paintComponent(Graphics g){
        System.out.println("paint tile, id "+ id);
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.drawImage(image, null, 0, 0);
    }
}

Edit: Added minimal code. This will work if I replace validate(); with revalidate(); But I don't want to use revalidate(); if nothing on the Panel needs to be invalided. Am I wright with that thinking?

public class test {
    public static void main(String[] args) throws Exception {

    MapPanel mapPanel = new MapPanel();
    ContentPanel contentPanel = new ContentPanel((JPanel)mapPanel);
    Map map = new Map();
    mapPanel.add(map);
    map.loadMap();

    }
}
class MapPanel extends JPanel{
    public MapPanel(){
    //this.setBackground(Color.BLACK);

    }
}

class Map extends JPanel{
   BufferedImage image;
   public Map(){
        try {
            image = ImageIO.read(new File("graphics//brick_brown0.png"));
        } catch (IOException e) {
            System.err.println("can't find file.");
        }
    setLayout(new GridLayout(31,30));
    setPreferredSize(new Dimension(962,992));
}
public void loadMap(){
    for(int i = 0; i < 30; i++){
        for(int p = 0; p < 31; p++){
            add(new Tile(image));
        }
    }
    validate();
}
}

class Tile extends JComponent{

BufferedImage image;
public Tile(BufferedImage image){
    this.image = image;
    setPreferredSize(new Dimension(32,32));
}

public void paintComponent(Graphics g){
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g;
    g2.drawImage(image, null, null);
}
}


class ContentPanel extends JPanel implements ActionListener{
Timer clock = new Timer(100, this);

public ContentPanel(JPanel mapPanel){
    clock.start();
    setLayout(new BorderLayout());
    JFrame frame = new Frame();
    frame.setContentPane(this);
    add(mapPanel);
    frame.setVisible(true);
}

@Override
public void actionPerformed(ActionEvent e) {
    repaint();
}

private class Frame extends JFrame{ 
    public Frame(){
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setBounds(100, 100, 1000, 1000);
    }
}
}
Spik330
  • 502
  • 4
  • 6
  • 19
  • 1
    You can stomp up and down about adding things after the window is shown, but sorry swing just won't work that way. If you want to modify the dialog after it is shown you MUST do it on the event dispatch thread, which means you must using SwingUtilities.invokeLater, or use SwingWorker. – MeBigFatGuy Apr 03 '17 at 02:51
  • 1
    1) For better help sooner, post a [MCVE] or [Short, Self Contained, Correct Example](http://www.sscce.org/). 2) One way to get image(s) for an example is to hot link to images seen in [this Q&A](http://stackoverflow.com/q/19209650/418556). – Andrew Thompson Apr 03 '17 at 02:55
  • @Andrew Thompson This is a Minimal version, and I'm not having problems with the images, but the painting of the JComponents. – Spik330 Apr 03 '17 at 15:42
  • @MeBigFatGuy can you be less ambiguous with your reply. – Spik330 Apr 03 '17 at 16:06
  • the event dispatch thread is the thread that renders the gui, and processes events. So you are allowed to change the gui in response to events that occur. But if you want to change the gui from just normal code, (like main) you MUST execute that code on the event dispatch thread. The way you ask swing to do that is to use SwingUtiltities.invokeLater – MeBigFatGuy Apr 03 '17 at 17:44
  • @MeBigFatGuy I have looked up a few thing on invokeLater and tried this `SwingUtilities.invokeLater(new Runnable() { @Override public void run() { contentPanel.repaint(); } });` which didn't work. Can you point me in the direction of good outline of who, what, when, where, and how to use invokeLater. I'm also not familiar with threads at all. – Spik330 Apr 03 '17 at 18:16
  • *"This is a Minimal version,"* The version I tried to work with had 5 public classes. When I reduced them to default access to allow compilation in a single source file, and added imports, there were still 6 compilation errors referring to classes like `ImageLoader`, `MapBuilder`, `Player`, `InfoPanel`.. *"I'm not having problems with the images, but the painting of the JComponents."* When other people are trying to test the code, what your not having problems with becomes less important. In fact, if they're not part of the problem, they should not be part of the MCVE / SSCCE. – Andrew Thompson Apr 03 '17 at 19:00
  • Please ignore previous comment. When I made it, I did not notice the edit that included (what is almost, barring the image) an MCVE. :P Having a look at it now. – Andrew Thompson Apr 03 '17 at 21:07
  • It is incredible to me that Java owners do not offer a prime time tutorial called: "HOW TO ENSURE THAT DYNAMIC CHANGES IN A GUI BECOMES VISIBLE USING SWINGUTILITIES.INVOKELATER, and that this tutorial's link is always injected in the start of any tutorial involving Swing and its brothers and sister. Amazing timethief abyss that we fall into as we try to fight this system of strange rules. (Beyond that Java is super) – carl Oct 18 '20 at 14:27

1 Answers1

1

The basic problem with the code as posted was that the JFrame was being set visible prior to the components being added. While there are ways to add components and make them visible after the top-level container becomes visible, they seem unnecessary in this case.

Here is a working version that uses an image generated at run-time, with a little space in the GridLayout to show that the grid is 31 x 30 components.

enter image description here

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;

public class TestRepaint {

    public static void main(String[] args) throws Exception {

        MapPanel mapPanel = new MapPanel();
        Map map = new Map();
        mapPanel.add(map);
        map.loadMap();
        new ContentPanel((JPanel) mapPanel);
    }
}

class MapPanel extends JPanel {

    public MapPanel() {
        this.setBackground(Color.RED);
    }
}

class Map extends JPanel {

    BufferedImage image;

    public Map() {
        image = new BufferedImage(10, 10, BufferedImage.TYPE_INT_BGR);
        setLayout(new GridLayout(31, 30,2,2));
        //setPreferredSize(new Dimension(962, 992));
    }

    public void loadMap() {
        for (int i = 0; i < 30; i++) {
            for (int p = 0; p < 31; p++) {
                add(new Tile(image));
            }
        }
        validate();
    }
}

class Tile extends JComponent {

    BufferedImage image;

    public Tile(BufferedImage image) {
        this.image = image;
        setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.drawImage(image, null, null);
    }
}

class ContentPanel extends JPanel implements ActionListener {

    Timer clock = new Timer(100, this);

    public ContentPanel(JPanel mapPanel) {
        clock.start();
        setLayout(new BorderLayout());
        JFrame frame = new Frame();
        frame.setContentPane(this);
        add(mapPanel);
        frame.pack();
        frame.setVisible(true);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        repaint();
    }

    private class Frame extends JFrame {

        public Frame() {
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            //setBounds(100, 100, 1000, 1000);
        }
    }
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • 1
    Thanks for the input. I really don't want to be that guy after you tried to help, but please read the "Things that will not work:" section of my post. This is why I had the less minimal version of code on the post, so you could see that I'm going to be doing dynamic updates to the components, which I can't do before the setting the frame visible. Have it be or not (<--I have no idea what that means but it sounds cool), the main block of code is skeletonised. – Spik330 Apr 03 '17 at 23:28
  • OK.. my bad for not reading the original post more carefully. *"The Map and Tile[] are going to be changing quite regularly"* How. exactly? The thing is, it's typically better to maintain a game **model**, change the model, then update the *look of the GUI or the components the GUI contains* based on the changed model, than to actually change components for other components. When it is actually necessary to change components, (e.g. to flip from a 'menu panel' to a 'game panel') - I'd tend to use a `CardLayout` (which still allows adding all components before the frame is set visible). So .. – Andrew Thompson Apr 04 '17 at 01:07
  • .. can you go into significantly more detail about what it is that actually changes? – Andrew Thompson Apr 04 '17 at 01:08
  • I realised *look of the GUI or the components the GUI contains* was a bit confusing. By *..or the components the GUI contains* I actually mean changing the appearance of them, rather than changing them for different components. :P I hope what I'm trying to communicate is clear. – Andrew Thompson Apr 04 '17 at 01:10