Overriding paintComponent() Method within a custom JButton - java

So my ultimate goal is to change the design of a JButton from the basic looking blue button to whatever I want, like a circle.
So I create a class called "Button" and made it extend JButton
public class Button extends JButton {
public Button(String text) {
super(text);
this.setContentAreaFilled(false);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("hello");
//Paint Stuff Will Happen Here
}
#Override
public Dimension getPreferredSize() {
return (new Dimension(120, 120));
}
}
My first goal was just to make sure that the paintComponent method was being called, so I put in a debug message. That debug message has never shown.
Basically the paintComponent() method is never called, even though I'm manually calling the "repaint" method for my JFrame.
Despite the fact that the method is not being called, a regular button still shows up on my JFrame, which is really confusing to me.
Here is my JPanel code
public class Scene extends JPanel {
public Scene() {
//Initialize Listeners
Button button = new Button("Hello");
button.setBounds(400, 400, 50, 25);
this.setLayout(null);
this.add(button);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
//Paint Stuff Below
for (int xI = 0; xI < Sprite.allSprites.size(); xI++) {
Sprite sprite = Sprite.allSprites.get(xI);
if (sprite.isVisible) {
g2.drawImage(sprite.image, sprite.rawLocation.x.intValue(), sprite.rawLocation.y.intValue(), null);
}
}
g2.dispose();
}
}
Basically in my JPanel I override the paintComponent method as well so as to paint my various sprites onto the screen, which has worked just fine and is probably irrelevant to the issue.
And finally, this is my JFrame code
public class GameWindow extends JFrame {
private Scene currentScene;
public void initialize(Scene scene) {
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(Settings.DEFAULT_WINDOW_SIZE);
this.setResizable(false);
this.setLocation(this.getLocationToCenterScreen());
this.setScene(scene);
}
//Gets the center of the screen with the given window
public Point getLocationToCenterScreen() {
return new Point(Settings.SCREEN_CENTER.x - (this.getSize().width / 2), Settings.SCREEN_CENTER.y - (this.getSize().height / 2));
}
public void setScene(Scene scene) {
this.currentScene = scene;
this.setContentPane(scene);
}
public Scene getCurrentScene() {
return currentScene;
}
}
Nothing really fancy in this code as far as I can tell.
I've set the content pane.
I've made sure each paintComponent() method also includes super.paintComponent(g).
I've set the LayoutManager to null for testing purposes.
I've set the Button's bounds.
As I said, the button actually does show up on the screen. But the debug message never shows.
Also, the button that shows up on the screen looks like the really old Windows buttons from 10 years ago. It's all grey with a black border.
This old button only shows up if I'm using a class that extends JButton.
Anyways, thanks guys! I hope I can get over this weird problem.

First and foremost, as a few other people have said, don't name your class "Button"; that belongs to Swing's predecessor, AWT (Advanced Windowing Toolkit), and is likely to confuse the compiler at best, and get you the wrong "Button" at worst.
That should solve the paintComponent() problem, but in addition, if all you're trying to do is change the feel of the button, then you're overprogramming this.
There are two ways to accomplish this with a JButton.
The first, and probably easiest (for an image), is AbstractButton.setIcon(Icon defaultIcon) An Icon is a type of image, loadable from a BufferedImage with ImageIcon(Image image) and manipulable in the same way. This is probably what you need.
The other method which comes to mind, which is much more broad in scope, is change the Look and Feel of the application. Most of us have several available to our systems, inclusive of the default Java look and feel, and the platform look and feel. I recommend setting it as early as possible; since it's entirely done through static methods, for small projects you might even get away with slipping it into the main method, before anything is even initialized.
Let me know if this doesn't solve your problem, and I wish you luck with the rest of your project!

Related

Drawing Image on JPanel as Background

im trying to insert a gif as a background for my app. I cut all frames and renamed them f1/f2/f3/f4/f5/f6/..... I would use a timer to change the frame so it looks like an animation.
There is a total of 42 frames, so f42.png is the last frame. The code seems to be fine, but there is no result. Any help?
Global variables:
private String backgroundFile;
public JPanel backgroundPanel, areaImage;
private BufferedImage background;
private javax.swing.Timer timerBackground;
Constructor where the Timer is initialized:
public Game()
{
entryWindow();
this.setLayout(null);
timerBackground = new javax.swing.Timer(100,this);
timerBackground.stop();
}
Animation method code:
private void backgroundAnimation()
{
backgroundFile = "f"+backgroundNum+".png";
try{
background=ImageIO.read(new File(backgroundFile));
}
catch(IOException e)
{
}
backgroundPanel = new JPanel()
{
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(background, 0, 0, 1100,800,null);
}
};
backgroundPanel.setBackground(Color.BLACK);
backgroundPanel.setBounds(0, 0, 1100, 800);
if (backgroundNum>42)backgroundNum++;
else backgroundNum=1;
add(backgroundPanel);
backgroundPanel.setVisible(true);
}
Action Listener for timer:
if (ae.getSource() == timerBackground)
{
backgroundAnimation();
}
In order to show JPanel, you need to add it to something like JFrame with an BorderLayout for instance, then you need to show the JFrame. JFrame is a application window, the JPanel can be only added and drawn on Window, it can't be viewed without something on which it can draw (like app Window). Beside that you don't need to create new JPanel each time the animation changes, just make a setter for the current image to show, and after assigning the image call repaint(), the ImagePanel could be like this:
public class ImagePanel extends JPanel {
private volatile BufferedImage image;
public void showImage(BufferedImage image) {
this.image=image;
repaint();
}
public void paintComponent(Graphics g) {
g.drawImage(image, 0,0,getWidth(),getHeight(),null);
}
}
add it to your JFrame at application start, also set the LayoutManager of JFrame to BorderLayout preferably, because without that your panel will have size(0,0) since you didn't set it, and it could be one of reasons why you don't see it (you can't see something which is 0 pixel in size, can you?).
Then in your timer just call the ImagePanel method public void showImage(BufferedImage image) with the image to show. If that's don't solve your problem, then post your entire code. As without that i'm just guessing, but those are common problems, so there's big chance you hit something from this.
I can see a few issues here
1. Assuming your Game class is extending JFrame, You need to add the JPanel to the ContentPane of the JFrame. Use one of the approaches setContentPane(backgroundPanel); or getContentPane().add(backgroundPanel)
You are not using a LayoutManager. So either use a LayoutManager or set the Size of the 'JFrame' and 'JPanel' explicitly using setBounds() method. I would recommend using a LayoutManager.
The JPanel or any Component for that matter does not automatically refresh itself. Once you change the image, you need to call repaint() on your JPanel.
You dont need to create a new JPanel every time you change the image. Just extend the JPanel and override the paintComponent()like you have done. Use the Timer to change the image of that single instance and call repaint() with every change.
The complete example, with hat output you are seeing will help understand the problem better and give you a solution. Please see How to create a Minimal, Complete, and Verifiable example
There are multiple problems here, but first let me answer your question:
You are creating a new JPanel and add it to the Game on every run through. That is wrong, since you add infinite panels to your Game
Also in your if/else you have a wrong condition. You increase the iterator when it is greater 42. You probably mean lesser than 42.
Here is how I would do it:
public class BackgroundPanel extends JPanel {
private int currImage = 0;
private BufferedImage[] backgroundImages;
public BackgroundPanel() {
int numberOfImages = 42;
backgroundImages = new BufferedImage[42];
for(int i = 1; i <= numberOfImages; i++) {
String backgroundFile = "f" + i + ".png";
backgroundImages[i] = ImageIO.read(new File(backgroundFile));
}
}
public void nextImage() {
/*if(currImage <= 42) currImage++;
else currImage = 1;*/
if(currImage++ > 42) currImage = 1;
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(backgroundImages[currImage], 0, 0, getWidth(), getHeight(), null);
}
}
You need to add this panel ONCE to your "Game":
//Somewhere in your Game
private BackgroundPanel backgroundPanel;
...
...
public Game() {
entryWindow();
this.setLayout(null);
backgroundPanel = new backgroundPanel();
backgroundPanel.setSize(getWidth(), getHeight());
add(backgroundPanel);
timerBackground = new javax.swing.Timer(100,this);
timerBackground.stop();
}
Your timer:
if (ae.getSource() == timerBackground) {
backgroundPanel.nextImage();
}
It's easier to put the background on JLabel. It requires only 3 lines of code and works fine! :) Hope it helps for anyone that will have the same problem :)
All you have to do is copy this code, change the name (i have all pictures in a folder called "Images") with any kind of Java supported picture/video/.... (just change the suffix .gif to your file format) and at last the size. Good luck! :)
public JLabel backgroundGIF;
backgroundGIF = new JLabel(new ImageIcon(getClass().getResource("Images/background.gif")));
backgroundGIF.setBounds(0,0,1100,800);
add(backgroundGIF);

Strange repositioning using repaint();

I have a JSlider in a JPanel that return me a value of R-G-B .
I create it, in the Costructor of JPanel. I draw in same Panel (using paintComponent) a little circle, and I change his color using the Slider. I want that the color change in contemporany of slider shift.
So, i use the method repaint.. Next to Panel there is another Panel, with two button.. If I use method repaint in first panel , the buttons of second panel duplicated in the topLeft of First Panel. Why? Thank's you.
First Panel:
public class OptionsPanel extends JPanel {
static JSlider RBG = new JSlider(0,255);
OptionsPanel(){
this.setVisible(false);
this.setSize(350,1000);
this.setLayout(null);
this.setBackground(new Color(200,200,0));
Main.f1.add(this);
RBG.setVisible(true);
RBG.setSize(255,50);
RBG.setLocation(30,240);
this.add(RBG);
LotL lotl = new LotL();
Button save = new Button("Save");
save.setVisible(true);
save.setSize(100,40);
save.setLayout(null);
save.setLocation(60,300);
save.addActionListener(lotl);
save.setBackground(Color.yellow);
save.identificatore=3;
this.add(save);
}
boolean draw=false;
#Override
public void paintComponent(Graphics g){
g.drawOval(50,100,70,70);
g.setColor(new Color(RBG.getValue(),180,200));
g.fillOval(50,100,70,70);
repaint();
}
}
Second Panel:
public class FirstPanel extends JPanel{
FirstPanel(){
this.setVisible(true);
this.setSize(1000,1000);
this.setLayout(null);
this.setBackground(new Color(255,200,180));
Main.f1.add(this);
Button start = new Button("Start Game!");
Button options = new Button("Options");
LotL LotL = new LotL();
start.setVisible(true);
start.setSize(200,80);
start.setLayout(null);
start.setLocation(400,450);
start.addActionListener(LotL);
start.setBackground(Color.green);
start.identificatore=1;
this.add(start);
options.setVisible(true);
options.setSize(200,70);
options.setLayout(null);
options.setLocation(400,550);
options.addActionListener(LotL);
options.setBackground(Color.green);
options.identificatore=2;
this.add(options);
}
}
You've broken the paint chain...
#Override
public void paintComponent(Graphics g){
g.drawOval(50,100,70,70);
g.setColor(new Color(RBG.getValue(),180,200));
g.fillOval(50,100,70,70);
repaint();
}
Graphics is a shared resource, which gets passed to ALL the components that are painted during a given paint cycle.
One of the jobs of paintComponent is to prepare the Graphics context for painting, but filling with the components background color.
You MUST call super.paintComponent before performing any custom painting.
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
g.drawOval(50,100,70,70);
g.setColor(new Color(RBG.getValue(),180,200));
g.fillOval(50,100,70,70);
}
Also, there is never any need for paintComponent to be public, no one should ever be calling directly and NEVER modify the state of a component from within any paint method which may trigger a repaint, you will get yourself into a infinite loop which will eventually consume your CPU and make you computer unusable.
Take a look at Painting in AWT and Swing and Performing Custom Painting for more details
You should also avoid using null layouts, pixel perfect layouts are an illusion within modern ui design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify

why components redrawn when i override with empty paintComponent(g) method?

As i understand (and i am java noob), when i resize a window or change its content paintComponent() method should be called automatically. It redraws everything, so when i override it with an empty method, nothing should be redrawn...but it is. Why? Probably i am missing something. What exactly is redrawn by paintComponent(), everything? Or some backgrounds or smth?
import java.awt.*;
import javax.swing.*;
public class TextFrame extends JFrame {
public TextFrame(String text, String fontName) {
super("Show Font");
setSize(725, 150);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
TextFramePanel sf = new TextFramePanel(text, fontName);
JButton ok = new JButton("i hate disappearing");
sf.add(ok);
add(sf);
setVisible(true);
}
public static void main(String[] arguments) {
if (arguments.length < 1) {
System.out.println("Usage: java TextFrame message font");
System.exit(-1);
}
TextFrame frame = new TextFrame(arguments[0], arguments[1]);
}
}
class TextFramePanel extends JPanel {
String text;
String fontName;
public TextFramePanel(String text, String fontName) {
super();
this.text = text;
this.fontName = fontName;
}
public void paintComponent(Graphics comp) {
//super.paintComponent(comp);
/*Graphics2D comp2D = (Graphics2D)comp;
comp2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Font font = new Font(fontName, Font.BOLD, 18);
FontMetrics metrics = getFontMetrics(font);
comp2D.setFont(font);
int x = (getSize().width - metrics.stringWidth(text)) / 2;
int y = getSize().height / 2;
comp2D.drawString(text, x, y);
System.out.println("vlad");*/
}
}
I suggest you read up on the official docs for Custom Painting: http://docs.oracle.com/javase/tutorial/uiswing/painting/index.html, more importantly under the Section A Closer Look at the Paint Mechanism.
Here's the part that's directly related to your question:
[...] the paintComponent method is where all of your painting code should be placed. It is true that this method will be invoked when it is time to paint, but painting actually begins higher up the class heirarchy, with the paint method (defined by java.awt.Component.) This method will be executed by the painting subsystem whenever you component needs to be rendered. Its signature is:
public void paint(Graphics g)
[...] The API does nothing to prevent your code from overriding paintBorder
and paintChildren, but generally speaking, there is no reason for you
to do so. For all practical purposes paintComponent will be the only
method that you will ever need to override.
So, when you're declaring:
public void paintComponent(Graphics comp) {}
you're not actually doing nothing. That's because the painting, as stated from the docs, does not begin with paintComponent(), but rather with paint(), which is called much earlier.
Now, if you declare it like this:
public void paint(Graphics g){}
then nothing will be redrawn after resizing and etc, no matter what code you have in your paintComponent(), because the beginning of the paint hierarchy has just been defined as an empty routine.
"...so when i override it with an empty method, nothing should be redrawn...but it is. Why?"
So it looks like you're running this program from the command line. If you are running the program, and it's still painting the stuff inside the paintComponent method (even after you commented out its content), most likely you ran the program with compiling again. So the program is still running from the same .class file.
When I run the program as is, I see nothing but a button, no painted words.
C:\stack>java TextFrame Hello World
" What exactly is redrawn by paintComponent(), everything? Or some backgrounds or smth?"
paintComponent just paint the component itself, which is just the JPanel in your case. It doesn't paint any other components added to it. That painting is delegated by its paintComponent method. Every JComponent has its own paintComponent method. So if you're wondering why the button still appears, it's because the call to paintComponent in the JPanel only affects the JPanel, not other child components add to it.

Why won't the JPanel change color and stay that way?

I'm working on a assignment for school but I got a problem :P.
I got this code:
public void mouseEntered(MouseEvent e) {
MyPanel b = (MyPanel)e.getSource();
System.out.println("ID: "+b.getId()+"");
b.setColor(Color.blue);
}
In the MyPanel object I got:
public void setColor(Color kleur) {
if(this.getBackground()==Color.white) {
this.setBackground(kleur);
repaint();
}
}
When I enter the panel with my mouse the color flashes that I entered. But I want it to stay the color so I can draw a trail in a Jform with 500 Jpanels(I've added them to a ArrayList but this part works just fine)
What am I doing wrong?
Based on #ErickRobertson's comment on the question, I guess the problem is the following:
Your MyPanel replaces the JPanel#paintComponents() method. Is that possible? If so, you could do the following. In your MyPanel#setColor(Color) method, you don't set the background, but a field containing your new background color:
private Color backgroundColor = Color.white;
public void setColor(Color kleur) {
backgroundColor = kleur;
repaint();
}
Then, in your MyPanel#paintComponents(Graphics):
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// draw background
g.setColor(backgroundColor);
g.fillRect(0, 0, getWidth(), getHeight());
// draw your stuff here
}
Make sure that only one JPanel is visible at a time.
When you add the JPanels to their parent, are they all going on top of each other? If so, then when you call repaint() on one of them, it's being repainted immediately and you can see it as blue. But as soon as the whole window repaints again, the JPanels are painted in the order they have been added and the last one is ultimately painted on top. This panel still has a white background, so that's what you're seeing.
Make sure only one of these panels are visible at a time, or that you have some plan in place to manage these panels so that only one of them are visible. Otherwise, make sure they are laid out in a grid or some other way so they don't appear on top of each other.
Where is your MouseListener implemented, since you are getting the panel from the MouseEvent. It's easier to have the panels implement the MouseListener and let them decide when to change color for themselves.
class Panel extends JPanel implements MouseListener {
public Panel() {
// Make sure the listener listens
addMouseListener(this);
}
#Override
public void mouseEntered(MouseEvent e) {
setColor(Color.blue);
}
// ... other mouselisteners can be ignored or implemented as needed
}
You can still keep a reference to some other class if you need it to be notified of a mouseenter. Just create a private member and set the reference in the constructor.
public void setColor(Color kleur) {
if(this.getBackground()==Color.white) {
this.setBackground(kleur);
repaint();
}
}
dont use == repalce with equals and try invalidate(. Your code is basically saying only replace background if background is white ???

Drawing graphics on top of a JButton

I have a situation wherein I have a bunch of JButtons on a GridLayout. I need each of the JButtons to have:
a background image (but retain the ability to keep the default button look if needed)
custom graphics drawn on top by other classes
I have no trouble with the background image, since I am using setIcon() but I am having problems drawing things on top of the background. At one point I was able to draw on top of the button, but after the button was clicked, the drawings disappeared. How can make the button keep this drawing state?
Basically, I need a way for my JButtons to have public methods that would allow another class to draw anything on it such as:
public void drawSomething() {
Graphics g = this.getGraphics();
g.drawOval(3,2,2,2);
repaint();
}
or
public Graphics getGraphics() {
return this.getGraphics();
}
then another class could do this:
button.getGraphics().drawSomething();
The latter is more what I am looking for but the first is equally useful.
Is there any way to go about this? Also, overriding the parent class method paintComponent() doesn't help since I need each button to have different graphics.
you can subclass JButton and override paintComponent().
you can handle each button having a different graphic by providing an external 'painter' to the subclass. Or just have a different subclass for each different graphic.
public class MyButton extends JButton {
private Painter painter;
public void paintComponent(Graphics g) {
super.paintComponent(g);
painter.paint(g);
}
}
public interface Painter {
public void paint(Graphics g);
}
you cannot just paint on the button as you are doing as the painting will get lost when the button is next repainted.
You can create a BufferedImage and do custom painting on it and then draw the image in your custom paintComponent(...) method.
Look at the DrawOnImage example from Custom Painting Approaches.

Categories

Resources