Swing overriding object's paintComponent - java

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
}
}

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).

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)

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.

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.

Related

How to add new Label(png) to GUI in the controller class? [duplicate]

I am new to making GUIs so I decided to try the the windows builder for eclipse, and while great I do have some doubts. I have been searching but I cannot seen to find a good way to add a background image to my "menu". For example I tried this:
public Menu() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(50, 50, 300, 250); //Dimensiones
contentPane = new JPanel() { //Imagen de Fondo
public void paintComponent(Graphics g) {
Image img = Toolkit.getDefaultToolkit().getImage(
Menu.class.getResource("/imgs/rotom.jpg"));
g.drawImage(img, 0, 0, this.getWidth(), this.getHeight(), this);
}
};
And adding the following classes:
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
But to no avail the window remains with its dull grey color, so far my code is just the standard one WindowsBuilder cooks for you plus 4 buttons but I doubt they're of importance here. Shouldn't the code I added override the paintComponent() method of the jPanel and draw the image in it?
The class for the menu is in a package within my project and the image is within a imgs package is within the same project as well.
Thanks a lot in advance.
A simple method, if you're not interested in resizing the background image or applying any effects is to use a JLabel...
BufferedImage bg = ImageIO.read(Menu.class.getResource("/imgs/rotom.jpg"));
JLabel label = new JLabel(new ImageIcon(bg));
setContentPane(label);
setLayout(...);
There are limitations to this approach (beyond scaling), in that the preferred size of the label will always be that of the image and never take into account it's content. This is both good and bad.
The other approach, which you seem to be using, is to use a specialised component
public class BackgroundPane extends JPanel {
private BufferedImage img;
public BackgroundPane(BufferedImage img) {
this.img = img;
}
#Override
public Dimension getPreferredSize() {
return img == null ? super.getPreferredSize() : new Dimension(img.getWidth(), img.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(img, 0, 0, this);
}
}
You should avoid trying to perform any task in the paintComponent method which may take time to complete as paintComponent may be called often and usually in quick succession....
Getting the image to scale when the component is resized is an entire question into of it self, for some ideas, you could take a look at...
Java: maintaining aspect ratio of JPanel background image
Java: JPanel background not scaling
Quality of Image after resize very low -- Java
Reading/Loading images
Oh, and, you should avoid extending directly from top level containers, like JFrame, they reduce the reusability for your components and lock you into a single container

repaint() call from the constructor

I'm trying to display loaded resource-image.
And it works, but only after user resized the window.
import javax.swing.*; import java.awt.*;
class MyMainWindow extends JFrame{
Image img;
public MyMainWindow(){
setSize(300,300);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
img = MyResourceLoader.getImage("res.jpg");
repaint();
setVisible(true);
}
#Override
public void paint (Graphics g){
super.paint(g);
if (img != null)
g.drawImage(img, 0,0, null);
}
}
public class ResourcesProgram {
public static void main(String[] args) {
new MyMainWindow();
}
}
Why the constructor's repaint() call doesn't work?
And what should I do to show this image from the start?
Resource Loader class:
import java.awt.*;
public class MyResourceLoader{
static MyResourceLoader rl = new MyResourceLoader();
static public Image getImage(String fileName){
return Toolkit.getDefaultToolkit().getImage(rl.getClass().getResource("images/" + fileName));
}
}
Okay, so a bunch of things...
First...
Because the window has not yet been realised on the screen (attached to a native peer), there is nothing for repaint to paint...
Second...
Toolkit.getDefaultToolkit().getImage(rl.getClass().getResource("images/" + fileName));
Passes the actually loading of the image to a background thread, meaning that while you get a non-null value returned, the actual image data might not have been fully loaded.
You should consider using ImageIO.read instead, it will only return AFTER the image data has been fully loaded and will also throw an exception if something goes wrong (unlike Toolkit.getImage). See Reading/Loading an Image for more details...
Third...
All Swing components implement the ImageObserver interface, this means when you call Graphics#drawImage, you should pass the object as the ImageObserver parameter
g.drawImage(img, 0,0, this);
This allows the component to monitor updates from the loading of the image and reschedule repaint as required.
Fourth...
You should avoid overriding paint of top level containers, apart from not been double buffered, there is a bunch of components between the frame and the user, which can interfere with the painting process.
Instead, you should extend from something like JPanel and override it's paintComponent method, performing your custom painting there. You should then add this panel to an instance of JFrame (or some other component as needed)
Take a look at Painting in AWT and Swing and Performing Custom Painting for more details
Fifth...
You should be making sure that you create and modify your UI only from within the context of the Event Dispatching Thread, see Initial Threads for more details
Paint is immediately called when the code is initialized. To see this, get rid of repaint(); in your constructor, and add in your paint method, g.drawLine(0, 0, 300, 300);
Try this, you may have to add some imports:
import javax.swing.*; import java.awt.*;
class MyMainWindow extends JFrame{
Image img;
public MyMainWindow(){
setSize(300,300);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
img = MyResourceLoader.getImage("res.jpg");
setVisible(true);
}
#Override
public void paint (Graphics g){
super.paint(g);
if (img != null)
g.drawImage(img, 0,0, this);
}
}
public class ResourcesProgram {
public static void main(String[] args) {
new MyMainWindow();
}
}
Resource Loader:
import javax.swing.*; import java.awt.*;
public class MyResourceLoader {
public static Image getImage(String fileName){
ImageIcon icon = new ImageIcon(getClass().getResource(fileName));
return icon.getImage();
}
}

Add GUI with Swing, but unsure of the sequence to take

I'm building a GUI for a data processing algorithm. I can instantiate the window, give it a background, title, etc., but when I try adding panels to it, I run into trouble. What I'm really looking for more than a proofreader is a suggestion for the sequence in which to build, configure, and add objects in Java Swing so that they behave correctly, in a generic sense. So, is this the best way to build a JFrame with a different-colored panel in it?
Declare JFrame
Set JFrame color (background color)
Declare JPanel (box to represent data graphically)
Set JPanel color (box color)
Add JPanel to JFrame
Set JFrame to visible = true
It makes sense intuitively but it doesn't seem to work, no matter what I do. I've found step-by-step instructions elsewhere but they tend to explain what to type more than why you're typing it, so you get a very narrow understanding of what's going on. Thanks for any help!
Below is the full code; I hesitated to post it because I'd begun experimenting with Graphics2D and it isn't well-commented, but if it helps:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import javax.swing.*;
import javax.swing.border.TitledBorder;
public class GUI extends JFrame
{
JFrame mainWindow = new JFrame();
JPanel backgroundPanel = new JPanel();
JPanel subPanel = new JPanel();
Color background = new Color(40,40,40);
Color subWindow = new Color(255, 255, 255);
TitledBorder title = BorderFactory.createTitledBorder("title");
Rectangle rect1 = new Rectangle(10, 10, 40, 40);
Graphics2D g;
public static void main (String[] args)
{
new GUI();
}
public GUI()
{
initializeGUI();
}
private void initializeGUI()
{
mainWindow.setSize(1340, 880);
backgroundPanel.setBackground(background);
subPanel.setBackground(subWindow);
subPanel.setBorder(title);
mainWindow.setTitle("Ed");
mainWindow.setLocationRelativeTo(null);
mainWindow.setDefaultCloseOperation(EXIT_ON_CLOSE);
mainWindow.add(backgroundPanel);
backgroundPanel.add(subPanel);
updateGUI();
}
public void updateGUI()
{
mainWindow.setVisible(false);
mainWindow.setVisible(true);
}
public void paintComponent(Graphics g)
{
this.g.setColor(subWindow);
this.g.fill(rect1);
this.g = (Graphics2D) g;
}
}
Let's break this down....
public class GUI extends JFrame {
JFrame mainWindow = new JFrame();
There is no need to extend from JFrame as you are neither using it nor are you adding any value to the class.
This...
public void paintComponent(Graphics g) {
this.g.setColor(subWindow);
this.g.fill(rect1);
this.g = (Graphics2D) g;
}
is doing nothing and will never be called, as nothing you've extended from implements a paintComponent method (that is, JFrame does not have a paintComponent methd) (and you class is not attached to anything displayed on the screen anyway). Also, you should NEVER maintain a reference to ANY Graphics context you did not create yourself.
The reason that subPanel is appearing so "small" is because it has not definable size, aside from the border.
You could rectify this in one of three ways...
You could change the layout manager of backgroundPanel to something like BorderLayout
You could override the getPreferredSize method of the subPanel to return a more suitable size or
You could add other components to it and let the layout manager figure it out...
In any case, you should have a look at Laying Out Components Within a Container.
You should also have a look at Painting in AWT and Swing and Performing Custom Painting for more details about how painting is done in Swing

JButtons "extending" out of my JFrame

Sorry about my English, and my ignorance in programming, its because I'm new at this , and I'm having problem with Buttons and JFrame, please help me ;)
I'll post the print of the problem, and the codes of my the two classes I have so far, Game and Menu, hope you guys can solve it, I want the buttons to paint inside the gray panel.
Thanks.
Print of my Problem
Print
(GAME CLASS)
package br.com.lexo.dagame;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import javax.swing.JFrame;
import br.com.lexo.dagame.menu.Menu;
public class Game extends Canvas implements Runnable {
private static final long serialVersionUID = 1L;
private static int width = 300;
private static int height = width / 16 * 9;
private static int scale = 3;
private static String title = "Da Game";
private Thread thread;
public JFrame janela;
private Menu menu;
private boolean running = false;
public Game() {
Dimension size = new Dimension(width * scale, height * scale);
setPreferredSize(size);
janela = new JFrame();
menu = new Menu(janela, this);
}
private synchronized void start() {
if (running) return;
running = true;
thread = new Thread(this, "Thread_01");
thread.start();
}
private synchronized void stop() {
if (!running) return;
running = false;
try {
thread.join();
} catch (Exception e) {
e.printStackTrace();
}
}
public void render() {
BufferStrategy bs = getBufferStrategy();
if (bs == null){
createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
g.setColor(Color.LIGHT_GRAY);
g.fillRect(0, 0, getWidth(), getHeight());
g.dispose();
bs.show();
}
public void update() {
}
public void run() {
while (running){
render();
update();
}
stop();
}
public static void main(String[] args) {
Game game = new Game();
game.janela.add(game);
game.janela.setTitle(title);
game.janela.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
game.janela.pack();
game.janela.setLocationRelativeTo(null);
game.janela.setResizable(false);
game.janela.setVisible(true);
game.start();
}
}
(MENU CLASS)
package br.com.lexo.dagame.menu;
import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import br.com.lexo.dagame.Game;
public class Menu extends Canvas {
private static final long serialVersionUID = 1L;
public boolean inMenu = false;
JButton startGame = new JButton("Começar Jogo");
JButton exitGame = new JButton("Sair do Jogo");
JButton howToPlay = new JButton("Como Jogar");
private Game game;
public Menu(JFrame janela, Game game){
this.inMenu = true;
this.game = game;
game.janela.setLayout(new GridBagLayout());
game.janela.add(startGame);
game.janela.add(exitGame);
game.janela.add(howToPlay);
howToPlay.setEnabled(false);
}
#Override
public void paint(Graphics g) {
super.paint(g);
}
}
I don't know what are you trying to accomplish but you are not adding the components correctly:
Look at:
game.janela.setLayout(new GridBagLayout());
game.janela.add(startGame);
game.janela.add(exitGame);
game.janela.add(howToPlay);
This is incorrect, the add method has two arguments, like this: container.add(component, constraints); your error is not specifying the constraints. The constraints contains all the details to know where in the panel you want to add that component.
For each LayoutManager the Object constraints is diferent. For the GridBagLayout the constraints is a GridBagConstraints object.
However GridBagLayout is a the most difficult layout to use and you don't really need it. I recommend you to look at this visual guide pick a layout and learn it properly. The tutorial for each LayoutManager explains what do you need to put in the constraints parameter.
The call container.add(component) exists because sometimes the LayoutManager does not need extra information (like the BoxLayout), in the other cases it just uses the "default" constraints for the LayoutManager in use, which may not be what you need.
For example the line in your main:
game.janela.add(game);
Is correct, but what it actually does is calling game.janela.add(game, defaultConstraints); where defaultConstraints is the default constraints value for the LayoutManager of the JFrame janela. Because you didn't explicitely specify a layout for the frame it is using the default layout for JFrames: BorderLayout, and the default constraints for the BorderLayout is the constant BorderLayout.CENTER.
So what that line actually does is:
game.janela.add(game, BorderLayout.CENTER);
Which incidentally is what you wanted to do.
To summarize:
Most calls to add must have two parameters: the component and the constraints. Each LayoutManager uses different constraints. You must be aware of what means to not specify the constraints for your LayoutManager. Do not start learning about how to properly use LayoutMangers with the GridBagLayout it's much more complex.
A quick way to somehow paint components to a graphics object is calling the paint method of component class. So in your render method:
g.fillRect(0, 0, getWidth(), getHeight());
menu.startGame.paint(g);
...
But as you'll soon see that everything is painted on the top left as components are laid out as said in the other answer and to get everything working to how you want them to work is a bit more complicated.
Now the following advice is based on my limited knowledge and was quickly put together so there are probably better ways.
About the menu class:
You are extending java.awt.Canvas when I think it would be best to extend a container like javax.swing.JPanel as you want it (I assume) to hold those 3 buttons.
Next would be to set the appropriate layout for this application, which would be null. So instead of:
game.janela.setLayout(new GridBagLayout());
it would now be:
setLayout(null);
This is because you want components (which are those buttons) to be paint on top of another component which is the Game class that extends Canvas and null allows you to do that.
Because the layout is now null, you must specify the bounds of the components which are the x and y coordinates alone with the width and the height otherwise everything will just be 0, 0, 0, 0 and nothing would show up.
So in the Game's constructor
setBounds(0, 0, width * scale, height * scale);
and janela.setPreferredSize(size); instead of setPreferredSize(size);
Back in the Menu class you will have to set the bounds of the buttons like so:
Dimensions sgSize = startGame.getPreferredSize();
startGame.setBounds(50, 50, sgSize.width, sgSize.height);
I am using preferred size to get the optimal width and height of the button that was set in the buttons UI (I think).
and add them to the Menu which is now a JPanel instead of adding them to the JFrame(janela). (add(startGame);) Also, don't forget to add the game to the menu panel.
and it should work like so:
(http://i.imgur.com/7cAopvC.png) (image)
Alternatively you could make your own widget toolkit or custom layout, but I wouldn't recommend that. I had this same problem last year but ended up moving to OpenGL but anyway, I hope this has helped :)

Java Canvas Won't Paint

I want my program to display the canvas that is repainted once at the start and then whenever a change is made afterwards. I thought I had everything coded correctly, but for some reason nothing that is painted onto the canvas actually shows (I know it's repainting, I tested that).
Here are the code segments:
public TileMapCreator()
{
currentView = new BufferedImage(640, 640, BufferedImage.TYPE_INT_ARGB);
currentView.getGraphics().setFont(new Font("Arial", Font.BOLD, 100));
currentView.getGraphics().drawString("No Map Yet Open", currentView.getWidth()/2, currentView.getHeight()/2);
this.setJMenuBar(createMenuBar());
this.setContentPane(createMapPanel());
}
private JPanel createMapPanel()
{
JPanel p = new JPanel();
p.add(setUpCanvas());
p.setVisible(true);
return p;
}
private Canvas setUpCanvas()
{
mapCanvas = new Canvas(){
private static final long serialVersionUID = 1L;
public void repaint()
{
mapCanvas.getGraphics().drawImage(currentView, 0, 0, this);
}
};
mapCanvas.setIgnoreRepaint(true);
Dimension size = new Dimension(currentView.getWidth(), currentView.getHeight());
mapCanvas.setSize(size);
mapCanvas.setPreferredSize(size);
mapCanvas.setMaximumSize(size);
mapCanvas.setMinimumSize(size);
mapCanvas.setFocusable(true);
mapCanvas.addMouseListener(this);
mapCanvas.addMouseMotionListener(this);
mapCanvas.setVisible(true);
return mapCanvas;
}
Currently the area where the canvas should be painting is just the regular grey color of the Java GUI. Thanks for your help!
You appear to be mixing Swing with AWT components, and drawing in a very strange way, one that I've honestly never seen before (and I've seen a lot). Why not simply do your drawings in the paintComponent(Graphics g) method of a JPanel using the Graphics object given by the JVM, like you'll find in the Swing graphics tutorials and 98% of the Swing graphics answers on this site? Also for my money, I'd avoid using Canvas or trying to mix heavy and light weight components together. Stick with Swing all the way, and things should go more smoothly.
I'd be happy to give you more specific advice and perhaps some if you could create and post a minimal example program. Please have a look at the link and let us know if you need more information.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class TestImagePanel extends JPanel {
private static final int BI_WIDTH = 640;
BufferedImage currentView = new BufferedImage(BI_WIDTH, BI_WIDTH, BufferedImage.TYPE_INT_ARGB);
public TestImagePanel() {
Graphics g = currentView.getGraphics();
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setColor(Color.black);
g.setFont(new Font("Arial", Font.BOLD, 60));
g.drawString("No Map Yet Open", 20, currentView.getHeight()/2);
g.dispose();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (currentView != null) {
g.drawImage(currentView, 0, 0, this);
}
}
#Override
public Dimension getPreferredSize() {
if (currentView != null) {
return new Dimension(BI_WIDTH, BI_WIDTH);
}
return super.getPreferredSize();
}
private static void createAndShowGui() {
TestImagePanel mainPanel = new TestImagePanel();
JFrame frame = new JFrame("TestImagePanel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
I've found the Swing tutorials to be a great asset, and bet you would too. Please have a look at them.
Edit
You ask:
Hmm, so apparently getting the graphics object of my buffered image twice did not result in anything actually being painted... I had to get the graphics object, assign it to a variable, and then paint.
I wouldn't say that. I'd say that your way of getting the Graphics object should work the same as mine, but yours is not safe since the Graphics objects obtained in this way cannot be disposed of, and you risk running out of resources. I think that your image didn't show up due to your very convoluted and unusual way of trying to display your image. You override repaint(), put some weird code inside of it, tell the JVM to ignore repaint calls, never call the repaint super method inside of your override, so that repaint does in fact nothing. I have to wonder -- where did you get this code? Was it from a tutorial? If so, please share the link here. And never get advice from that site again.

Categories

Resources