Draw on top of an Applet inside a Frame
Asked Answered
C

2

12

I am trying to create a Applet loader and I need to draw on top of the displayed Applet, however I can't seem to find a way to do that.

My original understanding was that Applet, by extending Component is just like any regular java.awt.Component that can be added inside Container that just have overriden paint method, but it seems like its not working.

In my initialization code I create a java.awt.Frame on which I add my custom implementation of java.awt.Container that has overriden all the paint methods so that they fill rect at x: 5, y:5 with size w:10, h:10 after calling parent method

However, when the applet is added, it always, no matter what is drawn on top of everything

public class AppletTest {

    public static void main(String[] args) {
        Frame frame = new Frame("Applet Test!");

        Container container = new Container() {

            @Override
            public void paint(Graphics g) {
                super.paint(g);
                g.fillRect(5, 5, 10, 10);
            }

            @Override
            public void paintAll(Graphics g) {
                super.paintAll(g);
                g.fillRect(5, 5, 10, 10);
            }

            @Override
            public void paintComponents(Graphics g) {
                super.paintComponents(g);
                g.fillRect(5, 5, 10, 10);
            }

            @Override
            public void print(Graphics g) {
                super.print(g);
                g.fillRect(5, 5, 10, 10);
            }

            @Override
            public void printComponents(Graphics g) {
                super.printComponents(g);
                g.fillRect(5, 5, 10, 10);
            }

            @Override
            public void update(Graphics g) {
                super.update(g);
                g.fillRect(5, 5, 10, 10);
            }

        };

        Dimension dimension = new Dimension(50, 50);
        container.setPreferredSize(dimension);

        Applet applet = new Applet() {
            @Override
            public void paint(Graphics g) {
                super.paint(g);
                g.fillRect(0, 0, 10, 10);
            }
        };

        container.add(applet);

        applet.setBounds(0, 0, 50, 50);


        frame.add(container);
        frame.pack();
        frame.setVisible(true);

        applet.init();
        applet.start();


    }

}

What are the steps that are needed to be taken to be able to draw on top of the Applet from its parent Container?

Here is also a screenshot of result of running the above code

![enter image description here

If I however change the type of applet to Component such as

Component applet = new Component() {
    @Override
    public void paint(Graphics g) {
        super.paint(g);
        g.fillRect(0, 0, 10, 10);
    }
};

the result is correct

enter image description here

The limitations of required solution is that I cannot modify the Applet itself, as its a legacy component that's only provided as binary. I know there's a solution by doing bytecode modification, but due to the many flavors of Applets, that's out of the question.

Cimino answered 9/1, 2017 at 17:16 Comment(7)
1) "I am trying to create a Applet loader.." Why? "..and I need to draw on top of the displayed Applet" Consider using a JLayeredPane. 2) For better help, post a minimal reproducible example.Cleek
1) I am trying to create a layer to extend legacy Java binary applet and wanting to keep the 'API' similar to the swing as possible, I mean.. This is how its supposed to work? You add an component to a Container and the container draws the child, however with Applets, it doesn't seem to be so.. 2) The code I posted is minimal, complete and verifiable, I don't know anything else I could rip off the code.. It creates a frame, adds a container to it which draws a rectangle, that an applet is added on that also draws an rectangle.. I also posted the current and expected result of the run..Cimino
@AndrewThompson There is a MCVE...!?. The goal may be questionable, but regardless of that, it's an interesting glitch. I tried it with different components, and added print statements (after the super calls). If the component is a Container, then it prints "Paint component, paint container", and it works. If the component is a Panel, then it prints "Paint container, paint component", and does not work. But there should be nothing in the Panel class that should cause such a different behavior. I haven't invested sooo much time, but would be curious to know what's going onSecularism
Very curious behavior indeed! I am pretty positive this has something to do with the Applet sandboxing.. I tied attaching a debugger to the Applet's onPaint method and the stack trace shows that it is not being drawn by the parent component, but by some RepaintArea that has received someking of repaint event.. Thanks for taking your time tho!Cimino
Is the current answer by Sergey acceptable for you, or are you looking for alternatives (and a possible explanation of the odd behavior)?Secularism
I am definetely looking for an explanation, if its unachiavable as now stated by many people. I couldn't find explanation this, not in even the Applet.java implementation even after looking at grepcode..Cimino
@Cimino The only explanation I could find for this exact problem was on EEVeneering
E
7

Why container square is not visible

It's happen because of applet overlap drawaple area of container. You can see this, if setup background color of applet and change size:

applet.setBackground(Color.RED);
applet.setBounds(0, 0, 12, 12);

In result we can see red border (background of applet) under black square drawed on applet:

Part overlap

With your applet size and with red background of applet fully overlap container drawable area:

Full overlapp

If you change Applet to Component you can see black square from container becouse of class Component not have background. I.e. change type of applet variable:

Component applet = new Component() {
//...
};
applet.setBackground(Color.RED);

You can see image as in your experiment:

Component no background

Drawing on top of applet

What are the steps that are needed to be taken to be able to draw on top of the Applet from its parent Container?

It is not posible to draw on top of applet, except drawing directly on applet.

Using GlassPane not solve this problem for applets. I tried example from documentation

Documentation example

and replace code:

contentPane.add(new JButton("Button 1"));
contentPane.add(new JButton("Button 2"));

to:

Applet applet = new Applet() {
    @Override
    public void paint(Graphics g) {
        super.paint(g);
        g.fillRect(0, 0, 10, 10);
    }
};
applet.setPreferredSize(new Dimension(100, 25));
applet.setBackground(Color.GREEN);
contentPane.add(applet);

In result we can see applet, which owerlap darawed circle:

Glasspane overlaped

Circle is fully drawed, if we change type of applet variable to JLanel.

Glasspane draw full circle

Enchain answered 13/1, 2017 at 6:33 Comment(6)
Thanks for providing illustrated examples, but even if this answers the question abstractly - 'no you can't do it', it doesn't really tell me why? I took a look at the Grepcode of applet.java and still can't figure out what is going on there.. How is Applet given a special capability of drawing over everything?Cimino
I understand what do you asking for. Tomorrow I will research this questionEnchain
Sergey, as far as I can tell, the OP does not want to change the type from applet as they are using a premade applet and just reading it from a binary file. Correct me if I'm wrong @CiminoVeneering
@Dan, I sense this, but I researched different ways to suggest appropriate solution and show most interesting results in examples. I understand, that changing of type - it's not solutionEnchain
@Ruuhkis, better way to drawing on top of applet what I found, is placing Panel as drawing region on top of applet. And draw on it without transparency. Also you can place Translucent Window on top of frame and draw on it, but this solution looks like a hackEnchain
this repeats the statement of the problem. With more colorful examplesTitleholder
V
0

As Sergey said your Applet overlaps your Container and as it has a background you can't see the painted rectangle on the Container. However, as far as I know it is not possible to set this background to a transparent colour. You can see these posts, here and here, that discuss the topic and a possible solution.

The best way for a situation for when you can change the code of the applet but can't change the type of applet

import java.applet.Applet;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;

@SuppressWarnings("serial")
public class AppletTest {
    public static void main(String[] args) {
        Frame frame = new Frame("Applet Test");
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent we) {
                frame.dispose();
             }
         });

        int apHeight = 50;
        int apWidth = 50;

        Container container = new Container() {
            @Override
            public void paint(Graphics g) {
                super.paint(g);
                Graphics2D graphs = (Graphics2D) g;
                graphs.setBackground(Color.WHITE);
                graphs.clearRect(0, 0, apWidth, apHeight);
                g.setColor(Color.RED);
                g.fillRect(5, 5, 10, 10);
            }
        };

        Dimension dimension = new Dimension(50, 50);
        container.setPreferredSize(dimension);

        frame.add(container);
        frame.pack();
        frame.setVisible(true);

        BufferedImage bufImage = new BufferedImage(dimension.width, dimension.height, BufferedImage.TYPE_INT_RGB);
        container.paint(bufImage.createGraphics());

        Applet applet = new Applet() {
            @Override
            public void paint(Graphics g) {
                super.paint(g);
                g.drawImage(bufImage, 0, 0, this); 
                g.fillRect(0, 0, 10, 10);
            }
        };

        container.add(applet);
        applet.setBounds(0, 0, apWidth, apHeight);
        applet.init();
    }
}

The output of this looks like the following.

Red and black square

Edit for clarified question
If you are reading from a file the best way would be to declare your applet, create an image, and then paint the image onto the applet. I made an example that makes binary file and then reads the object from it. After which it paints the background on it.

import java.applet.Applet;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

@SuppressWarnings("serial")
public class AppletTest {
    private static void createBinaryApplet() throws IOException {
        Applet applet = new Applet() {
            @Override
            public void paint(Graphics g) {
                super.paint(g);
                g.fillRect(0, 0, 10, 10);
            }
        };

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("applet.dat"));
        oos.writeObject(applet);
        oos.close();
    }

    public static void main(String[] args) {
        Frame frame = new Frame("Applet Test");
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent we) {
                frame.dispose();
             }
         });

        final int prefHeight = 50;
        final int prefWidth = 50;

        Container container = new Container() {
            @Override
            public void paint(Graphics g) {
                super.paint(g);
                Graphics2D graphs = (Graphics2D) g;
                graphs.setBackground(Color.WHITE);
                graphs.clearRect(0, 0, prefWidth, prefHeight);
                g.setColor(Color.RED);
                g.fillRect(5, 5, 10, 10);
            }
        };

        container.setPreferredSize(new Dimension(prefWidth, prefHeight));

        frame.add(container);
        frame.pack();
        frame.setVisible(true);

        BufferedImage bufImage = new BufferedImage(prefWidth, prefHeight, BufferedImage.TYPE_INT_RGB);
        container.paint(bufImage.createGraphics());

        try {
            createBinaryApplet();

            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("applet.dat"));
            Applet applet = (Applet) ois.readObject();
            ois.close();

            container.add(applet);
            applet.setBounds(0, 0, prefWidth, prefHeight);
            applet.init();

            Graphics g = applet.getGraphics();
            g.drawImage(bufImage, 0, 0, applet);
            applet.paint(g);
        } catch(ClassNotFoundException | IOException e) {
            System.out.println("Whoops");
        }
    }
}

This produces the same output as before.

Veneering answered 13/1, 2017 at 16:59 Comment(13)
Sorry if I didn't make it too clear.. I can't modify the Java applet(s) as they are provided as binaries, and doing this for each applet by doing bytecode modification is a way too big chore to do. What do you mean by messing with JNA? How can I achieve this by accessing something native?Cimino
@Cimino Ah okay. I see. Let me do a little more research and I will edit my answer with a new method to achieve the result and explain the problem, or at least using JNA to solve it a little betterVeneering
@Cimino May I ask where you are getting your binary applets from or for a copy of them to test with?Veneering
@Cimino Also I was mistaken about JNA. That can not be used to make applets transparent, only root windowsVeneering
@Cimino I edited my answer for how to update the background on a "premade" applet. If I have assumed something wrong, let me know and I will try to amend my answer. Also if you use a different type of file where this does not work would it be possible for me to get a sample file that is similar so I can find a method of doing this for that file typeVeneering
For the edit: The applet also have its own drawing cycle, so that doesn't really help, right? The draw could occur before or midst drawing the applets own content, so that really doesn't solve the problem :/Cimino
@Cimino I'm not entirely sure I understand the problem as it shouldn't really matter when it draws it as it should be drawn like the desired output you posted, but if you want to wait for everything to be ready you could use EventQueue.invokeLater(() -> applet.paint(g)); instead of applet.paint(g). However, if you want to draw the background as an overlay (I.e. Always on the very top of the applet) I suggest you try to implement what Andrew suggested in his comment or unless you post more information, you might not get the answer you are looking for.Veneering
I mean that some of the applets have their own drawing cycle, so they request their own repaint, meaning I can't know when the applet is repainting, so if i just randomly draw my overlay it wont always be visible and will be flickeringCimino
@Cimino could you please post a more complete example of what you have done that demonstrates that?Veneering
Say you have an Applet that has an own thread and calls repaint() by itself, say every 30ms Now you want to draw on top of those frames, you don't know when those frames are drawn so if you just make your own loop with some random interval, it might be that you're drawing before the applet has drawn, causing flickeringCimino
@Ruuhkis, what do you want to draw on top of applet? Can you explain this?Enchain
@Sergey certain metrics needs to be shown on top of the applet, imagine fraps, but per applet.Cimino
@Cimino I see your problem now. I made such an overlay and will try get a "transparent" background on it tomorrowVeneering

© 2022 - 2024 — McMap. All rights reserved.