4

If I have a JPanel object that I can't modify, is there a way I can modify the paintComponent method of it without using injection?

One approach I was thinking of was getting the JPanel's Graphics object, passing it to paintComponent(), performing operations on this Graphics object, and finally painting that in my custom JPanel. The problem with this, is I need to be able to do this every time the original JPanel's paintComponent() is called.

I don't need to replace what's in paintComponent(), I just need to add on to it.

For example:

JFrame frame = null;
for (Frame f : JFrame.getFrames()) {
  if (((JFrame) f).getTitle().equals("Title")) {
    JPanel panel = null;
    // ... Cycle through all components and get the one that's a JPanel

    // I want to use ColorConvertOp to make panel greyscale
  }
}
LanguagesNamedAfterCofee
  • 5,782
  • 7
  • 45
  • 72

4 Answers4

6

One approach would be to use the Decorator Pattern to wrap the existing class. Your decorator can then implement paintComponent to first delegate to the original component and after that paint on top of it. For this approach you need to actually gain control over the creation of the components, or you need to replace them after the component hierarchy has been created (using getComponents() of the parent container to find the components to be altered).

Durandal
  • 19,919
  • 4
  • 36
  • 70
  • @LanguagesNamedAfterCofee `JPanel panel = new JPanel(new BorderLayout()) { ... overrides...}; panel.add(instanceYouCantModify);` – Guillaume Polet Jul 30 '12 at 17:53
  • 2
    @LanguagesNamedAfterCofee You should accept an answer only if it is the solution to your problem and not switch the accepted answer over and over. You don't have to accept an answer right away, you can wait a few days to make your decision. It is unlikely that someone will post another answer on a "solved" question. – Guillaume Polet Jul 30 '12 at 17:54
5

I think one possibility is to use a GlassPane and position it exactly over your JPanel (maybe let it follow the panel using listeners if the panel changes its location). Then you can simply draw your stuff in the glasspane and it will be overlayed.

Of course this is not really elegant... But I don't see any possibility to change the paintComponents behaviour of an already existing instance without injection. (Proof me wrong, Java geeks of this world! :P)

brimborium
  • 9,362
  • 9
  • 48
  • 76
  • Thanks, I'll look into that. I specifically want to use ColorConvertOp to make it greyscale. – LanguagesNamedAfterCofee Jul 30 '12 at 17:27
  • @LanguagesNamedAfterCofee You can take a look at [this link](http://weblogs.java.net/blog/alexfromsun/archive/2006/09/a_wellbehaved_g.html) to get some more information about how to use a `GlassPane` – brimborium Jul 30 '12 at 17:33
3

I guess this would complete @Durandal's answer:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class TestPanels {

    private static final boolean GRAY_SCALE = true;

    protected void initUI() {
        final JFrame frame = new JFrame(TestPanels.class.getSimpleName());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel unmodifiablePanel = new JPanel(new GridBagLayout());
        JLabel label = new JLabel("Some unmodifiable test label");
        unmodifiablePanel.add(label);
        unmodifiablePanel.setBackground(Color.GREEN);
        JPanel wrappingPanel = new JPanel(new BorderLayout()) {
            private ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
            private BufferedImage image;

            @Override
            public void paint(Graphics g) {
                if (!GRAY_SCALE) {
                    super.paint(g);
                    return;
                }
                BufferedImage bi = getImage();
                if (bi != null) {
                    Graphics big = bi.createGraphics();
                    super.paint(big);
                    big.dispose();
                    bi = op.filter(bi, null);
                    g.drawImage(bi, 0, 0, null);
                }
            }

            protected BufferedImage getImage() {
                if (image == null) {
                    if (getWidth() > 0 && getHeight() > 0) {
                        image = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
                    }
                } else if (image.getWidth() != getWidth() || image.getHeight() != image.getHeight()) {
                    image = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
                }
                return image;
            }

        };
        wrappingPanel.add(unmodifiablePanel);

        frame.add(wrappingPanel);
        frame.setSize(200, 200);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new TestPanels().initUI();
            }
        });
    }
}

You can turn the GRAY_SCALE flag to false, to see how it renders normally.

Guillaume Polet
  • 47,259
  • 4
  • 83
  • 117
1

If you had the ability to modify the construction of your class, you could extend that class and then call super.paintComponent(g) in your extended class. Example:

public class NewPanel extends OldPanel{

    public void paintComponent(Graphics g){
        super.paintComponent(g);
        // Insert additional painting here
    }
}

In this case you will need a new class in order to build onto the existing class's painting.

What this does is execute everything that was done in the parent class's painting, and give you the option to do more (which seems to be what you're looking for).

EDIT
BUT... given that you don't have access to this, your options get more limited. If the panel uses a null layout manager, you could add a child jpanel who paints over the parent (a layout manager would restrict the amount of area the child could paint over though). This is a long shot.

You can also use reflection to but about the only option you've got (besides byte code injection). This seems about equally ugly as byte code injection - here's a decent overview of what you'd be looking to do: Java reflection: How do I override or generate methods at runtime?

My personal preference is to decompile the class, modify it the way you want, recompile it and insert it back into the original jar. This will probably void some warrenties, licenses and get you in other trouble... but at least it's maintainable.

Community
  • 1
  • 1
Nick Rippe
  • 6,465
  • 14
  • 30