0

I have spent two days trying to figure out how to add a JLabel to my JFrame when I already have a JLabel added as the background. I created a Container with the getContentPane() method, and I've added the background JLabel (which sows up fine), and later on, I want to add another JLabel (which I've determined exists and has a 32x32 image as its icon). However, this second JLabel does not show up whatsoever.

Here is my class containing the setup of the screen (the important bits):

package com.elek.engine.graphics;

import java.awt.Color;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

/**
 * IGNORE THIS LONG JAVADOC COMMENT; IT'S OLD AND NEEDS UPDATED!
 * A shortcut class that makes it easy to create a frame with an image on it. Uses the
 * <code>JFrame</code> class to create the frame and uses the <code>BufferedImage</code>
 * class with a <code>BufferStrategy</code> and the <code>Graphics</code> class to paint
 * the image onto the frame. 
 * <p>
 * An array of integers represents the pixels on the screen. Each element of the pixel 
 * array contains the RGB color value for the corresponding pixel. The pixels are arranged
 * in such a way that in order to access the pixel at coordinate <code><strong>(x, y)</code></strong>,
 * the element at index <code><strong>(x + y * WIDTH)</code></strong> must be referenced, 
 * where <code><strong>x</code></strong> and <code><strong>y</code></strong> are the desired
 * coordinates and <code><strong>WIDTH</code></strong> is the width of the frame.
 * <p>
 * A separate <code>Thread</code> is created to handle the screen's update and render loops.
 * Each iteration clears the screen and then draws the next frame from the pixel array.
 * <p>
 * In order to change the image on the screen, implementing classes must change the pixel
 * array values. To allow for this, the drawPixels method is called every screen loop
 * iteration. This method is to be overridden in the implementation class with information
 * regarding the color of each pixel.
 * 
 * @author my name
 * @version 1.0
 */
public class Screen extends JFrame implements Runnable {
    private static final long serialVersionUID = 1L;

    public static int WIDTH, HEIGHT;

    private Container cp;

    public JLabel label;

    private BufferStrategy strategy;

    private Graphics g;
    private BufferedImage image;

    /**
     * Holds color information of each pixel on the screen. Each element referrs to an
     * individual pixel and holds the RGB value of that pixel in base 10.
     */
    public int[] pixels;

    private Thread graphicsThread;
    private boolean running = false;

    /* -- Constructor -- */

    /**
     * Creates a new Screen object. Sets up the BufferStrategy and the JFrame.
     * 
     * @param   width   Width of the JFrame
     * @param   height  Height of the JFrame
     * @param   name    Name of the JFrame title bar
     */
    public Screen(int width, int height, String name) {
        // Create frame
        super(name);

        // Create a content pane to add multiple JLabels
        cp = this.getContentPane();
        cp.setLayout(new FlowLayout(FlowLayout.CENTER));

        // Define width and height constants
        WIDTH = width;
        HEIGHT = height;

        // Set up frame
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(WIDTH, HEIGHT);
        setLocationRelativeTo(null);
        setResizable(false);
        setVisible(true);

        // Create double buffer strategy
        createBufferStrategy(2);
        strategy = getBufferStrategy();

        // Set up initial image-drawing stuff
        g = strategy.getDrawGraphics(); // Changes every iteration of render method
        image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();

        // Create component to draw image on
        label = new JLabel(new ImageIcon(image));

        // Create a new thread for this screen and start it
        running = true;
        graphicsThread = new Thread(this);
        graphicsThread.start();

        // Add the label with the images drawn onto it to the frame
        cp.add(label);
    }

    /* -- Methods -- */

    /**
     * Loop of the Screen class. Calls the update and render methods repeatedly.
     */
    @Override
    public void run() {
        while (running) {
            // Focus on this window
            this.setFocusable(true);
            this.requestFocus();
            this.requestFocusInWindow();

            // Update and render the screen
            update();
            render();

            // Sleep a bit to avoid choking up the Thread
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
                //System.exit(1);
            }
        }
    }

    /**
     * Called by the thread run method repeatedly. First, clears the screen of the previous
     * image in order to prevent ghost-imaging or blurring. Then, updates the pixel array 
     * to whatever it needs to be for the next iteration of the render method.
     */
    private void update() {     
        // Clean up the screen and then update pixel array to make the next frame
        clearScreen();
        drawPixels();
    }

    /**
     * Called by the thread run method repeatedly. Draws the pixel array and TextString 
     * objects onto the JFrame using a Graphics object and the BufferStrategy.
     */
    private void render() {
        // Draw image
        g = strategy.getDrawGraphics();
        g.drawImage(image, 0, 0, WIDTH, HEIGHT, null);

        // Dispose old frames and then show current frame
        g.dispose();
        strategy.show();
    }

    /**
     * Clears the screen by setting every pixel in the pixel array to black. Used to prevent
     * ghost-images or blurring.
     */
    public void clearScreen() {
        for (int i = 0; i < pixels.length; i++)
            pixels[i] = 0;
    }

    /**
     * Adds a component to the screen at a specific X and Y coordinate.
     * 
     * @param   component   Component to draw onto the screen
     * @param   x           X coordinate to place the component
     * @param   y           Y coordinate to place the component
     */
    public void addComponent(JLabel component, int x, int y) {
        component.setLocation(x, y);
        component.setSize(component.getPreferredSize());
        cp.add(component);
        System.out.println(component.getLocation());
    }

    /**
     * Screen objects draw images by directly referencing the {@link #pixels} array. Implementation
     * classes will override this method with pixel color information. If this method is
     * not overridden it will draw the String "Override drawPixles()" onto the screen.
     */
    public void drawPixels() {
        drawText("Override drawPixels()", 50, 100);
    }
}

When I add another JLabel using the addComponent() method, the added JLabel does not show up. It adds the JLabel, as shown by the println(component.getLocation()) command that displays its position.

I believe that the issue is that the added JLabel is beneath the background JLabel, but I do not know how to fix this. I have tried extending the JPanel class as opposed to the JFrame class, but that resulted in no window appearing at all.

Any help would be appreciated.

  • Mixing passive painting and active painting approaches together is not a good idea, as the way they want to up-date is at odds with each other – MadProgrammer Mar 12 '17 at 20:37
  • `public class Screen extends JFrame implements Runnable` is an abomination. You should almost _never_ extend Swing classes. The only exception is if you are changing behavior: extending `JFrame` so you can more conveniently invoke public methods on it is _not_ changing behavior. Next, is your class a frame or a module designed to be run by a thread? Please, use separate classes! That `sleep(5)` is a nasty code smell as well. This code would benefit from refactoring into separate classes, each with its own concern. –  Mar 12 '17 at 20:51
  • @Snowman I appreciate your answer, but that doesn't help me answer my question whatsoever. Even if I broke that class into multiple classes, I'd still have the problem of layering my `JLabel` objects. If it helps, I'm trying to create a shortcut class as a part of an engine so I don't have to create a new `BufferedImage`, `JFrame` and `JLabel` every time I want to get a window on the screen. – elektrikpulse61 Mar 12 '17 at 20:58
  • 1
    @elektrikpulse61 I realize that is not an answer, which is why I left a comment. –  Mar 12 '17 at 21:05
  • @Snowman I understand. My apologies. Do you by chance have anything that could help me find a solution? At this point, I'm thinking about scrapping that class and building a new one that doesn't extend `JFrame` and instead makes a `JPanel` and adds it to a new `JFrame`. – elektrikpulse61 Mar 12 '17 at 21:15
  • So long as you're mixing paint approaches, you're going to have problems, the moment you start using a `BufferedStrategy`, you start interfering with the way Swing paints and it will at best do nothing, at worst, flicker till you have fit – MadProgrammer Mar 12 '17 at 21:43
  • @MadProgrammer I appreciate your help. I will avoid a `BufferStrategy` altogether then and just use `JPanel` and `JFrame`. – elektrikpulse61 Mar 12 '17 at 21:54
  • Also, if you add a component after you make the frame visible, you will need to trigger a new layout pass by calling revalidate (and possibly repaint) – MadProgrammer Mar 12 '17 at 21:57
  • @MadProgrammer Duly noted, thank you. – elektrikpulse61 Mar 12 '17 at 22:07
  • @MadProgrammer I made a new class and redid the way things are added, but my background `JLabel` containing the `BufferedImage` is on top of my `JLabel` added using the `addComponent()` method. Is there a way to bring my `JLabel` with the component to the front of the screen? – elektrikpulse61 Mar 13 '17 at 02:04
  • If they're been added to the same container, then you can try using the containers setComponentZOrder method – MadProgrammer Mar 13 '17 at 02:07

0 Answers0