Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling setVisible(true) on an already visible frame

Question:

What does calling setVisible(true) on a JFrame which is already visible do? I was digging through the source code of JFrame, and ultimately, it boils down to this function in Component which does nothing to a frame if it is already visible. Why does it act like revalidate(); repaint();? (See SSCCE below)


Motivation:

I am working on a java app, for which I wrote a class JImagePanel which extends JPanel and allows the user to set an image as the background (see SSCCE). I have found that after editing the background of the panel, I was having issues repainting the background to the correct size. After scouring the internet, I found that the following works:

if(frame.isVisible()) frame.setVisible(true);

Ultimately, I solved the issue using

panel.revalidate();
panel.repaint();

, which I think is the better solution, but it got me to thinking what setVisible(true) actually does on an already visible frame. From my viewpoint, it shouldn't work - but in fact it does.


SSCCE

Here is an example that illustrates my issue. If nothing else, hopefully you find this class extremely useful in the future.

NOTE: Updated source of this file can be found on the project homepage on GitHub of the project this was created for.

Enjoy!

package com.dberm22.utils;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class JImagePanel extends JPanel {

    private static final long serialVersionUID = 6841876236948317038L;
    private Image img = null;
    private Position position = Position.CENTER;

  public enum Position{
      STRETCH,
      CENTER,
      FIT,
      FILL,
      NONE;
  }

  public JImagePanel() {
      }

  public JImagePanel(String img) {
    this(new ImageIcon(img).getImage());
  }

  public JImagePanel(Image img) {

      setBackgroundImage(img);
  }

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

    Graphics2D g2 = (Graphics2D) g;

    g2.setColor(getBackground());
    g2.fillRect(0, 0, getWidth(), getHeight());

    if (this.position.equals(Position.STRETCH))
    { 
        if(this.img != null) g2.drawImage(img, 0, 0, getWidth(), getHeight(), null); 
    }
    else if (this.position.equals(Position.FILL) || this.position.equals(Position.FIT))
    { 
        if(this.img != null)
        {

             double scaleFactor = getScaleFactor(new Dimension(img.getWidth(null), img.getHeight(null)), getSize());
             int scaleWidth = (int) Math.round(img.getWidth(null) * scaleFactor);
             int scaleHeight = (int) Math.round(img.getHeight(null) * scaleFactor);

             //Image img_scaled = img.getScaledInstance(scaleWidth, scaleHeight, Image.SCALE_SMOOTH);
            g2.drawImage(scaleImage(img, scaleWidth, scaleHeight, getBackground()), (getWidth() - scaleWidth)/2, (getHeight() - scaleHeight)/2, scaleWidth, scaleHeight, null); 
        }
    }
    else if (this.position.equals(Position.CENTER)) { if(this.img != null) g2.drawImage(img, (getWidth() - img.getWidth(null))/2, (getHeight() - img.getHeight(null))/2, null); }
  }

  public void setBackgroundImage(String img) 
  {
      setBackgroundImage(new ImageIcon(img).getImage());
  }

  public void setBackgroundImage(Image img)
  {
        this.img = img;
        Dimension size = new Dimension(img.getWidth(null), img.getHeight(null));
        setPreferredSize(size);
        setMinimumSize(size);
        setMaximumSize(size);
        setSize(size);

        repaint();
  }

  public static double getScaleFactor(int iMasterSize, int iTargetSize) {

        double dScale = 1;
        if (iMasterSize > iTargetSize) {

            dScale = (double) iTargetSize / (double) iMasterSize;

        } else {

            dScale = (double) iTargetSize / (double) iMasterSize;

        }

        return dScale;

    }

    public double getScaleFactor(Dimension original, Dimension targetSize) {

        double dScale = 1d;

        if (original != null && targetSize != null) {

            double dScaleWidth = getScaleFactor(original.width, targetSize.width);
            double dScaleHeight = getScaleFactor(original.height, targetSize.height);

            if (this.position.equals(Position.FIT)) dScale = Math.min(dScaleHeight, dScaleWidth);
            else if(this.position.equals(Position.FILL)) dScale = Math.max(dScaleHeight, dScaleWidth);

        }

        return dScale;

    }

    public BufferedImage scaleImage(Image img, int width, int height, Color background) {

        BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = newImage.createGraphics();
        try {
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                    RenderingHints.VALUE_INTERPOLATION_BICUBIC);
            g.setBackground(background);
            g.clearRect(0, 0, width, height);
            g.drawImage(img, 0, 0, width, height, null);
        } finally {
            g.dispose();
        }
        return newImage;
    }

  public void setBackgroundImagePosition(String pos)
  {
      if("Stretch".equals(pos)) setBackgroundImagePosition(Position.STRETCH);
      else if("Center".equals(pos))  setBackgroundImagePosition(Position.CENTER);
      else if("Fit".equals(pos)) setBackgroundImagePosition(Position.FIT);
      else if("Fill".equals(pos))  setBackgroundImagePosition(Position.FILL);
      else if("None".equals(pos)) setBackgroundImagePosition(Position.NONE);
  }
  public void setBackgroundImagePosition(Position pos)
  {
      this.position = pos;
      repaint();
  }

  public static void main(String[] args)
  {

      JFrame frame = new JFrame("JImagePanel Test");
      frame.setSize( Toolkit.getDefaultToolkit().getScreenSize());
      frame.setPreferredSize( Toolkit.getDefaultToolkit().getScreenSize());
      frame.setExtendedState(JFrame.MAXIMIZED_BOTH); //sets appropriate size for frame

      JImagePanel panel = new JImagePanel();
      frame.add(panel);

      frame.setVisible(true);

      try {Thread.sleep(2000);} catch (InterruptedException e) {}

      panel.setBackgroundImage("C:\\Users\\David\\Pictures\\Wood.jpg");
      panel.setBackgroundImagePosition(JImagePanel.Position.STRETCH);

      panel.revalidate(); // need to revalidate()
      panel.repaint(); //doesnt work by itself

      try {Thread.sleep(2000);} catch (InterruptedException e) {}

      panel.setBackgroundImage("C:\\Users\\David\\Pictures\\Wood.jpg");
      panel.setBackgroundImagePosition(JImagePanel.Position.FIT);

      frame.setVisible(true); //also works --why?

  }

}
like image 718
dberm22 Avatar asked Oct 02 '22 21:10

dberm22


2 Answers

Calling setVisible(true) on a JFrame which is already visible works for you because this ends up calling validate() internally, which in turn revalidates all subcomponents in the frame.

To see why, refer to the implementation of Component.setVisible(boolean b):

public void setVisible(boolean b) {
    show(b);
}

public void show(boolean b) {
    if (b) {
        show();
    } else {
        hide();
    }
}

But the show() method is overriden in Window (of which JFrame is a subclass). So this ends up calling Window.show():

public void show() {
    if (peer == null) {
        addNotify();
    }
    validate();
    [...]

Hope this explains the behaviour you are seeing.

like image 148
Grodriguez Avatar answered Oct 06 '22 00:10

Grodriguez


Assuming you are after a half-way clean implementation of your imagePanel:

  • let the panel do its own revalidation/-paint as need (vs application code)
  • do not call setXXSize, ever, not even internally in the panel

That's change your setters and override the getXXSize. Note that basing the sizing hints on the image size alone is not what you would want to do in real world code, you probably want to take super's hint into account as well (f.i. if the panel has children)

@Override
public Dimension getPreferredSize() {
    if (img != null) {
        return new Dimension(img.getWidth(this), img.getHeight(this));
    }
    return super.getPreferredSize();
}

public void setBackgroundImage(Image img) {
    this.img = img;
    // need to revalidate as our sizing hints might have changed
    revalidate();
    repaint();
}

public void setBackgroundImagePosition(Position pos) {
    this.position = pos;
    repaint();
}

Application code that uses it now simply calls the setters, nothing else:

    JFrame frame = new JFrame("JImagePanel Test");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    // frame.setLayout(new FlowLayout()); // just to see the effect of a pref-respecting layout
    final JImagePanel panel = new JImagePanel();
    frame.add(panel);

    final Image[] images = new Image[]{
            XTestUtils.loadDefaultImage(), XTestUtils.loadDefaultImage("500by500.png"), null};
    Action toggleImage = new AbstractAction("toggle image") {
        int index = 0;
        @Override
        public void actionPerformed(ActionEvent e) {
            panel.setBackgroundImage(images[index]);
            index = (index +1) % images.length;
        }
    };
    Action togglePosition = new AbstractAction("toggle position") {
        int index = 0;
        @Override
        public void actionPerformed(ActionEvent e) {
            panel.setBackgroundImagePosition(Position.values()[index]);
            index = (index +1) % Position.values().length;
        }
    };
    frame.add(new JButton(toggleImage), BorderLayout.NORTH);
    frame.add(new JButton(togglePosition), BorderLayout.SOUTH);
    // size for frame
    //frame.setSize(800, 800);
    frame.setExtendedState(JFrame.MAXIMIZED_BOTH); //sets appropriate
    frame.setVisible(true);
like image 37
kleopatra Avatar answered Oct 05 '22 23:10

kleopatra