1

I'm new to Java and have built a simple Swing application that runs in full screen mode, continually polls a data source on regular intervals, and shows different output depending on said data source. I've created a short MCVE to showcase what I'm doing.

My problem in a nutshell: I have a background image which should always show, and a foreground image which should only show under certain circumstances. Unfortunately it seems they take a few seconds to load for the first time, which means the screen is white until they are loaded and the screen gets repainted. To solve this, I keep track of the number of "cycles" with a private member, and at certain values toward the beginning, I force it to try and paint these images. However this feels very "hacky" and I'd much rather learn the correct way to load and display images in Swing applications.

Main.java

import javax.swing.*;
import java.io.IOException;

public class Main {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }

    private static void createAndShowGUI() {
        try {
            final MyDisplay myDisplay = new MyDisplay();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

MyDisplay.java

import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.net.URL;

public class MyDisplay extends Thread {

    private long cycleCounter = 0;
    private MyDataSource dataSource;
    private MyFrame myFrame;

    public MyDisplay() throws IOException {
        dataSource = new MyDataSource();

        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice device = env.getDefaultScreenDevice();
        myFrame = new MyFrame(device);

        final int windowWidth = myFrame.getWindowWidth();
        final int windowHeight = myFrame.getWindowHeight();

        final URL bgPath = this.getClass().getResource("/bg.png");
        final ImageIcon bgLoader = new ImageIcon(bgPath);
        final Image bgImage = bgLoader.getImage().getScaledInstance(windowWidth, windowHeight, Image.SCALE_SMOOTH);
        myFrame.setBgImage(bgImage);

        final URL fgPath = this.getClass().getResource("/fg.png");
        final ImageIcon fgLoader = new ImageIcon(fgPath);
        final Image fgImage = fgLoader.getImage().getScaledInstance(200, 85, Image.SCALE_SMOOTH);
        myFrame.setFgImage(fgImage);

        start();
    }

    @Override
    public void run() {
        try {
            while (true) {
                if (dataSource.hasData()) {
                    myFrame.showData();
                } else {
                    myFrame.clearData();
                }
                //  THIS IS THE "HACKY" PART
                cycleCounter++;
                if (cycleCounter == 20) {
                    myFrame.showData();
                } else if (cycleCounter == 21) {
                    myFrame.clearData();
                }
                //  END OF HACK
                Thread.sleep(100);
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

MyFrame.java

import javax.swing.*;
import java.awt.*;

public class MyFrame extends JFrame {

    private GraphicsDevice device;
    private MyPanel myPanel;

    public MyFrame(GraphicsDevice device) {
        super(device.getDefaultConfiguration());
        this.device = device;

        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
        } catch (Exception ignored) { }

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setUndecorated(true);
        setResizable(false);
        setVisible(true);

        myPanel = new MyPanel();
        add(myPanel);

        pack();
        device.setFullScreenWindow(this);
    }
    public void setBgImage(Image bgImage) {
        myPanel.setBgImage(bgImage);
    }
    public void setFgImage(Image fgImage) {
        myPanel.setFgImage(fgImage);
    }
    public int getWindowHeight() {
        final Double height = device.getDefaultConfiguration().getBounds().getHeight();
        return height.intValue();
    }
    public int getWindowWidth() {
        final Double width = device.getDefaultConfiguration().getBounds().getWidth();
        return width.intValue();
    }
    public void showData() {
        myPanel.showData();
    }
    public void clearData() {
        myPanel.clearData();
    }
}

MyPanel.java

import javax.swing.*;
import java.awt.*;

public class MyPanel extends JPanel {

    private boolean showData = false;
    private Image bgImage;
    private Image fgImage;

    public MyPanel() {
        setFocusable(true);
        requestFocusInWindow();
    }
    public void paintComponent(Graphics graphics) {
        super.paintComponent(graphics);
        setBackground(Color.WHITE);
        if (bgImage != null) {
            graphics.drawImage(bgImage, 0, 0, null, null);
        }
        if (showData) {
            if (fgImage != null) {
                graphics.drawImage(fgImage, 0, 0, 200, 85, null, null);
            }
        }
    }
    public void showData() {
        if (showData == false) {
            showData = true;
            repaint();
        }
    }
    public void clearData() {
        if (showData == true) {
            showData = false;
            repaint();
        }
    }
    public void setBgImage(Image bgImage) {
        this.bgImage = bgImage;
    }
    public void setFgImage(Image fgImage) {
        this.fgImage = fgImage;
    }
    public Dimension getPreferredSize() {
        return new Dimension(800, 600);
    }
}

MyDataSource.java

public class MyDataSource {
    public boolean hasData() {
        return false;
    }
}

TL;DR: Look towards the end of the MyDisplay.java file. You'll see the part I've hacked in to make it work. Please help me understand how to do this correctly.

EDIT: added in MyDataSource.java, per Andrew Thompson's suggestion.

Community
  • 1
  • 1
soapergem
  • 9,263
  • 18
  • 96
  • 152
  • Use a `SwingWorker`, for [example](http://stackoverflow.com/a/4530659/230513), to load the images at startup. – trashgod Nov 30 '14 at 23:24
  • 1) `UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");` should be `UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());`.. 2) For better help sooner, post an [MCVE](http://stackoverflow.com/help/mcve) (Minimal Complete Verifiable Example) or [SSCCE](http://www.sscce.org/) (Short, Self Contained, Correct Example). – Andrew Thompson Nov 30 '14 at 23:56
  • @AndrewThompson I *did* post a MCVE. www.hookedonphonics.com – soapergem Dec 01 '14 at 04:15
  • Post it ***here*** not at at an external link. – Andrew Thompson Dec 01 '14 at 07:26
  • @AndrewThompson ok I'm quite confused; are you blind, or looking at the wrong question perhaps? Because half of this question is code. – soapergem Dec 01 '14 at 12:32
  • 1) An MCVE needs to be a single source file. Demote the public classes with no main to default access and paste them in at the end of the `Main` source. 2) Remove useless code like `try {UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"); } catch (Exception ignored) { }` (if it does not change the problem it is useless in an MCVE). 3) One way to get images for an MCVE/SSCCE, is to hot link to images seen in [this Q&A](http://stackoverflow.com/q/19209650/418556). – Andrew Thompson Dec 01 '14 at 12:45
  • There is no such requirement for an MCVE to be a single source file. You can read the instructions on MCVE yourself (which I'd already linked to in the question) to verify this. Nowhere in that document does it say anything about arbitrarily grouping things into one file. In general, Java programs never put everything in a single source file; that's really bad practice. Secondly, I don't think it was detrimental to leave in the UIManager line; you've already shown me a better way of writing it! And besides, with my relatively small knowledge of Swing, I couldn't be sure it had no effect. – soapergem Dec 01 '14 at 13:24
  • *" You can read the instructions on MCVE yourself.."* I wrote them. Or at least, I wrote the initial draft. The SSCCE document explains better (I also wrote that). The point it, you want to make code easy enough for people to do a single copy/paste, run it and see the problem. As it happens, I thought I'd toss you a favor and turn that code into an MCVE, after doing all that stuff above (that I advised you to do), I realized that `MyDataSoucre` was missing. Screw this, I have better things to do with my time. Good luck with it.. – Andrew Thompson Dec 01 '14 at 15:02
  • An SSCCE needs to be a single source file, but not an MCVE (according to your own documentation). Anyways, I would have strongly preferred if you would have just looked at the code rather than arguing semantics. – soapergem Dec 01 '14 at 15:10
  • 1
    You have not included the background image nor the foreground image as part of you MCVE. It took me almost 20 minutes to get your code to compile. Your idea of a Swing application is backwards. You construct the Swing components in one or more classes. You read the image files in a separate Runnable that you can start as a thread. First, get the images. Then construct the GUI, completely separately. Do not mix the image retrieval and the GUI construction in any way, shape, or form. Keep them absolutely, permanently separate processes. – Gilbert Le Blanc Dec 03 '14 at 18:22
  • I readily admit that I'm not a Swing expert -- which is why I'm asking the question. What images I use are frankly irrelevant to the question, which is really more about code. But instead of getting responses that include code, I keep getting pedantic replies about MCVE or SSCCE. (Is this a site for computer scientists, or English majors?) Anyways, I apologize that it took you 20 minutes to get that code to run, but since you've already put in that much work I'd recommend posting whatever restructuring you did as an answer. – soapergem Dec 03 '14 at 18:26

2 Answers2

2

Try to develop from something like this..

import java.awt.Component;
import java.awt.Graphics;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferedImage;
import javax.swing.*;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;

public class Main {

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

    JFrame frame = new JFrame("FullScreen Demo");
    frame.setUndecorated(true);

    frame.add(new Component() {
        BufferedImage bg = ImageIO.read(new URL("http://upload.wikimedia.org/wikipedia/commons/thumb/b/b3/Neige_dans_le_djebel_Ch%C3%A9lia.JPG/1920px-Neige_dans_le_djebel_Ch%C3%A9lia.JPG"));
        BufferedImage fg = ImageIO.read(new URL("http://upload.wikimedia.org/wikipedia/commons/thumb/f/f5/W1769-Vallet_BMX_N_82131.JPG/1920px-W1769-Vallet_BMX_N_82131.JPG"));
        @Override
        public void paint(Graphics g) {
            super.paint(g);
            g.drawImage(bg, 0, 0, getWidth(), getHeight(), this);
            g.drawImage(fg, 50, 50, getWidth()/10, getHeight()/10, this);

        }
    });

    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsDevice gs = ge.getDefaultScreenDevice();
    gs.setFullScreenWindow(frame);
    frame.validate();
}
}
0

If you don't want to see a blank screen at launch, display the Frame only when images are completely loaded. You can load the image in the Panel constructor

Or if you want a quick display, load a low definition image right away, then launch a Thread that would load the big picture in the background, and set it in the panel when finished.