sh code
sh code

Reputation: 333

Custom JComponent(s) refuse to paint (if added to an ArrayList)

here's the relevant code from the form where JComponents should be drawn - this code creates and adds to the form a JComponent and also adds it to an ArrayList, as I need to be able to destroy the components later.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JOptionPane;

public class frmMain extends javax.swing.JFrame implements ActionListener {
  ArrayList<Pocitadlo> poc;
  Random rnd;
  /**
   * Creates new form frmMain
   */
  public frmMain() {
    initComponents();

    poc = new ArrayList<Pocitadlo>();
    rnd = new Random();

//    Pocitadlo tmp = new Pocitadlo(100, 40, 40, getFont());
//    tmp.addActionListener(this);
//    this.add(tmp);
//    tmp.start();
  }

private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
    int val, x, y;

    val = 20;
    x = (this.getWidth() / 2) - 10 + rnd.nextInt(20);
    y = (this.getHeight() / 2) - 10 + rnd.nextInt(20);

    try{
      val = Integer.parseInt(txtCounterValue.getText());

      Pocitadlo tmp = new Pocitadlo(val, x, y, getFont());
      tmp.addActionListener(this);
      poc.add(tmp);
      tmp.setVisible(true);
      tmp.start();
      this.add(tmp);
      this.setTitle("Počet počítadiel: " + this.getComponentCount());
      repaint();
      tmp.repaint();
    } catch(Exception e){
      JOptionPane.showMessageDialog(this, "Nesprávne zadaná alebo prázdna hodnota počítadla. Hodnota musí byť     celé číslo.", "Chyba", JOptionPane.ERROR_MESSAGE);
    }
  }

  @Override
  public void actionPerformed(ActionEvent e){
    if(e.getActionCommand().equals("counterFinished")){
      Pocitadlo tmp = (Pocitadlo)e.getSource();

      poc.remove(tmp);
      this.setTitle("Počet počítadiel: " + poc.size());
      this.remove(tmp);
    }
  }

and here is the code of the JComponents i'm adding and naiively expect to be drawn:

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JComponent;
import javax.swing.Timer;

public class Pocitadlo extends JComponent implements MouseListener, ActionListener {
  private int maxValue, value;
  private Boolean isRunning, isDisplayed;
  private Timer valueTimer, blinkTimer;
  private ActionListener actionListener;
  private Font font;

  public Pocitadlo(int timeout, int x, int y, Font f){
    isRunning = false;
    isDisplayed = true;
    maxValue = value = timeout;

    valueTimer = new Timer(1000, this);
    valueTimer.setActionCommand("valueTimer");

    blinkTimer = new Timer(200, this);
    blinkTimer.setActionCommand("blinkTimer");

    this.setBounds(x, y, 100, 50);
  }

  public void start(){
    isRunning = true;
    valueTimer.start();
  }

  public void stop(){
    isRunning = false;
    valueTimer.stop();
  }

  @Override
  public void actionPerformed(ActionEvent e){
    if(e.getActionCommand().equals("valueTimer")){
      value--;

      if(actionListener != null){
        repaint();
        actionListener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "counterTick"));
      }

      if(value == 0 && actionListener != null){
        isRunning = false;
        valueTimer.stop();
        actionListener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "counterFinished"));
      }
      if(value < maxValue / 2 && !blinkTimer.isRunning()) blinkTimer.start();
    }

    if(e.getActionCommand().equals("blinkTimer")){
      isDisplayed = !isDisplayed;
      repaint();
    }
  }

  public void addActionListener(ActionListener listener){
    actionListener = listener;
  }

  @Override
  public void mouseClicked(MouseEvent e) {
    value += 5000;
    if(value > maxValue) value = maxValue;
    repaint();
  }

  @Override
  public void paintComponent(Graphics g){
    g.setColor(Color.red);
    g.fillRect(getX(), getY(), getWidth(), getHeight());
    if(isDisplayed){
      g.setColor(Color.green);
      g.fillRect(getX(), getY(), getWidth(), getHeight());
      g.setColor(Color.black);
      //g.setFont(font);
      g.drawString(value + "/" + maxValue, 0, 0);
    }

    //super.paintComponent(g);
  }
}
  1. this results in nothing - the JComponents are created and correctly added at least to the ArrayList, and its paintComponent method run, as well as the timer events, components are also correctly removed at least from the ArrayList when the countdown reaches 0, but doesn't draw anything and I have no idea why.

  2. please note that the commented code in frmMain constructor kind of works, and if uncommented, it draws the JComponent... well... just the rectangles, doesn't draw any text, but I suppose that's another problem, however, if you're willing to help with that one too, I'd be glad.

  3. also, if I'm using action events and listeners incorrectly, please ignore that, I'm a beginner at Java and would like to know how to do it correctly, but later, right now I just need to get this code to work so please focus on the issue, thank you.

  4. yes, there's some dead things, like the font passed to the component, those are remains of my attempts to get the drawString to work.

Upvotes: 0

Views: 314

Answers (1)

MadProgrammer
MadProgrammer

Reputation: 347214

You project is suffering from a number of really basic misconceptions about how painting in Swing works.

Because (at the time I tested it) you hadn't provided the full code, I had to make some assumptions.

Your expection is that the content pane of the JFrame is using a null layout. I'd find it difficult to see how you would mange this and have other components on the frame. In any case, I would isolate a separate container to act as the "main" view for your Pocitadlo. This allows you to continue using layout managers to manage the other components as well.

I'm a little concerned that you trap the entire Pocitadlo generation code in a try-catch simply to catch a numeric conversion. Since you've already applied a default value, it would be reasonable to trap the conversion in its own try-catch and ignore it if it fails (displaying a message as required). Now I'm coming in from the outside, so you may see a need for this, it just caught my eye...

Your valueTimer actionPerformed code is slightly erronous, as it's possible for the blinkTimer to continue running even after the valueTimer has completed. I corrected this by merging two of you if statements into a if-else statement instead...

if (value == 0 && actionListener != null) {
    isRunning = false;
    valueTimer.stop();
    actionListener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "counterFinished"));
    blinkTimer.stop();
} else if (value < maxValue / 2 && !blinkTimer.isRunning()) {
    blinkTimer.start();
}

I, personally, would use a JPanel over a JComponent. JPanel is opaque to start with.

The main problem is in you paintComponent method.

  1. You should always call super.paintComponent, and on opaque components it should be called first.
  2. You seem to think you have to correct for the components position when painting, for example g.fillRect(getX(), getY(), getWidth(), getHeight());. This is actually wrong. The graphics context has already been translated so that the x/y position of the component/graphics is now equal to 0x0. Have a read through Painting in AWT and Swing for a better explanation, but basically, this means, the top/left corner of you paint area is now 0x0.
  3. You seem to think that text is painted from the top/left corner of the first character. This is actually wrong. Text is painted along it's base line. You need to compenstate for this by adjusting the y-position by the Fonts ascent value.

.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestRepaint01 {

    public static void main(String[] args) {
        new TestRepaint01();
    }

    public TestRepaint01() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (UnsupportedLookAndFeelException ex) {
                }

                MainFrame frame = new MainFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setSize(200, 200);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }

        });
    }

    public class MainFrame extends javax.swing.JFrame implements ActionListener {

        ArrayList<Pocitadlo> poc;
        Random rnd;
        private JPanel body;

        /**
         * Creates new form frmMain
         */
        public MainFrame() {

            setLayout(new BorderLayout());

            poc = new ArrayList<Pocitadlo>();
            rnd = new Random();

            body = new JPanel(null);
            add(body);
            JButton btn = new JButton("Add");
            btn.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    int val, x, y;

                    val = 20;
                    x = (getWidth() / 2) - 10 + rnd.nextInt(20);
                    y = (getHeight() / 2) - 10 + rnd.nextInt(20);

//                    try {
//                        val = Integer.parseInt(txtCounterValue.getText());

                        Pocitadlo tmp = new Pocitadlo(val, x, y, getFont());
                        tmp.addActionListener(MainFrame.this);
                        GridBagConstraints gbc = new GridBagConstraints();
                        gbc.gridwidth = GridBagConstraints.REMAINDER;
                        gbc.weightx = 1;
                        gbc.fill = GridBagConstraints.HORIZONTAL;
                        poc.add(tmp);
                        tmp.start();
                        body.add(tmp);
                        body.repaint();
                        setTitle("Počet počítadiel: " + getComponentCount());
//                    } catch (Exception e) {
//                        JOptionPane.showMessageDialog(this, "Nesprávne zadaná alebo prázdna hodnota počítadla. Hodnota musí byť     celé číslo.", "Chyba", JOptionPane.ERROR_MESSAGE);
//                    }
                }
            });
            add(btn, BorderLayout.SOUTH);

        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (e.getActionCommand().equals("counterFinished")) {
                System.out.println("Counter finished");
                Pocitadlo tmp = (Pocitadlo) e.getSource();

                poc.remove(tmp);
                this.setTitle("Počet počítadiel: " + poc.size());
                body.remove(tmp);
//                body.revalidate();
                body.repaint();
            }
        }

    }

    public class Pocitadlo extends JPanel implements MouseListener, ActionListener {

        private int maxValue, value;
        private Boolean isRunning, isDisplayed;
        private Timer valueTimer, blinkTimer;
        private ActionListener actionListener;
        private Font font;

        public Pocitadlo(int timeout, int x, int y, Font f) {
            isRunning = false;
            isDisplayed = true;
            maxValue = value = timeout;

            valueTimer = new Timer(1000, this);
            valueTimer.setActionCommand("valueTimer");

            blinkTimer = new Timer(200, this);
            blinkTimer.setActionCommand("blinkTimer");

            this.setBounds(x, y, 100, 50);
        }

        @Override
        public void removeNotify() {
            super.removeNotify();
            stop();
            blinkTimer.stop();
        }

        public void start() {
            isRunning = true;
            valueTimer.start();
        }

        public void stop() {
            isRunning = false;
            valueTimer.stop();
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (e.getActionCommand().equals("valueTimer")) {
                value--;
                System.out.println("value = " + value);

                if (actionListener != null) {
                    repaint();
                    actionListener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "counterTick"));
                }

                if (value == 0 && actionListener != null) {
                    isRunning = false;
                    valueTimer.stop();
                    actionListener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "counterFinished"));
                    blinkTimer.stop();
                } else if (value < maxValue / 2 && !blinkTimer.isRunning()) {
                    blinkTimer.start();
                }
            }

            if (e.getActionCommand().equals("blinkTimer")) {
                System.out.println("Blink");
                isDisplayed = !isDisplayed;
                repaint();
            }
        }

        public void addActionListener(ActionListener listener) {
            actionListener = listener;
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            value += 5000;
            if (value > maxValue) {
                value = maxValue;
            }
            repaint();
        }

        @Override
        public void mouseExited(MouseEvent e) {
        }        

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);

            g.setColor(Color.red);
            // location bad
            g.fillRect(0, 0, getWidth(), getHeight());
            if (isDisplayed) {
                g.setColor(Color.green);
                g.fillRect(0, 0, getWidth(), getHeight());
                g.setColor(Color.black);
                //g.setFont(font);
                FontMetrics fm = g.getFontMetrics();
                g.drawString(value + "/" + maxValue, 0, fm.getAscent());
            }

            g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);

        }

        @Override
        public void mousePressed(MouseEvent e) {
        }

        @Override
        public void mouseReleased(MouseEvent e) {
        }

        @Override
        public void mouseEntered(MouseEvent e) {
        }

    }

}

None of the problems you've encountered are super critical (sure, you program doesn't do what you want) and suggest that you've reached a pinnacle moment in your code abilities. I say this, because I made the exact same mistakes... ;)

Take a closer look at 2D Graphics and Custom Painting

I'd also take a look at Code Conventions for the Java Programming Language as you simply annoy people if you don't follow them ;)

Upvotes: 3

Related Questions