0

I am currently working on a program that requires custom buttons (i.e idle, hover and press graphics). I tried to make a custom button class as subclass of Component (AWT)/Button (AWT)/JComponent (Swing)/JButton (Swing) and override their paint (AWT)/paintComponent (Swing) methods to draw the images. But no matter which class I used, I always got an inconsistend flickering wenn hovering/clicking on the custom buttons. I searched for this and all I found were posts that said to only override paintComponent and always use super.paintComponent inside but I did all that and it didn't change at all. Also including setDoubleBuffered(true) didn't made any difference. Here is the code of my custom button:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.JComponent;


public abstract class CustomButton extends JComponent implements MouseListener
{

    private Image   image , hoverImage , clickImage;
    private String  displayText;
    private Font    font;
    private Color   textColor;
    private boolean hovered = false , pressed = false;

    public CustomButton( final Image img , final Image hoverImg , final Image clickImg )
    {

        super();
        setImages( img , hoverImg , clickImg );
        addMouseListener( this );
        setFocusable( false );
        setSize( getPreferredSize() );

    } // End of constructor

    public CustomButton( final String text , final Font font , final Color textColor , final Image img , final Image hoverImg , final Image clickImg )
    {

        this( img , hoverImg , clickImg );
        setText( text , font , textColor );

    } // End of constructor

    public void setText( final String text , final Font font , final Color textColor )
    {

        displayText = text;

        if( text != null && font == null )
            throw new IllegalArgumentException( "Font type of a button can't be null if text has to be displayed." );
        this.font = font;
        if( font == null )
            this.textColor = null;
        else if( textColor == null )
            throw new IllegalArgumentException( "Font color of a button can't be null if font type isn't null." );
        else
            this.textColor = textColor;

        repaint();

    } // End of setText( final String text , final Font font , final Color textColor )

    public void setText( final String text )
    {

        if( text != null && ( font == null || textColor == null ) )
            throw new IllegalArgumentException( "Text of a button can't be changed without assigning a proper colour and font type." );

        displayText = text;
        repaint();

    } // End of setText( final String text )

    public void setImages( final Image img , final Image hoverImg , final Image clickImg )
    {

        if( img == null )
            throw new IllegalArgumentException( "Background image of a button can't be null." );

        if( hoverImg != null && ( img.getWidth( this ) != hoverImg.getWidth( this ) || img.getHeight( this ) != hoverImg.getHeight( this ) ) )
            throw new IllegalArgumentException( "Background image and hover image can't have different sizes." );

        if( clickImg != null && ( img.getWidth( this ) != clickImg.getWidth( this ) || img.getHeight( this ) != clickImg.getHeight( this ) ) )
            throw new IllegalArgumentException( "Background image and click image can't have different sizes." );

        image = img;
        hoverImage = hoverImg;
        clickImage = clickImg;

        repaint();

    } // End of setImages( final ImageIcon img , final ImageIcon hoverImg , final ImageIcon clickImg )

    @Override
    public void mousePressed( MouseEvent e )
    {

        pressed = true;
        repaint();

    } // End of mousePressed( MouseEvent e )

    @Override
    public void mouseReleased( MouseEvent e )
    {

        pressed = false;
        repaint();

    } // End of mouseReleased( MouseEvent e )

    @Override
    public void mouseEntered( MouseEvent e )
    {

        hovered = true;

        if( pressed )
            return;

        repaint();

    } // End of mouseEntered( MouseEvent e )

    @Override
    public void mouseExited( MouseEvent e )
    {

        hovered = false;

        if( pressed )
            return;

        repaint();

    } // End of mouseExited( MouseEvent e )

    @Override
    public Dimension getPreferredSize()
    {

        return new Dimension( image.getWidth( this ) , image.getHeight( this ) );

    } // End of getPreferredSize()

    @Override
    public Dimension getMaximumSize()
    {

        return getPreferredSize();

    } // End of getMaximumSize()

    @Override
    public Dimension getMinimumSize()
    {

        return getPreferredSize();

    } // End of getMinimumSize()

    @Override
    public void paintComponent( Graphics g )
    {

        super.paintComponent( g );

        if( pressed )
            g.drawImage( clickImage , 0 , 0 , this );
        else if( hovered )
            g.drawImage( hoverImage , 0 , 0 , this );
        else
            g.drawImage( image , 0 , 0 , this );

    } // End of paint( Graphics g )

} // End of class

The other two classes for a MRE are:

import java.awt.Panel;

import javax.swing.JFrame;


public class MainFrame extends JFrame
{

    private Panel mainPanel;

    public MainFrame()
    {

        setResizable( false );
        setDefaultCloseOperation( EXIT_ON_CLOSE );
        setBounds( 0 , 0 , 1920 , 1080 );
        setUndecorated( true );
        setVisible( true );

        enterMainMenuState();

    }// End of constructor

    public void enterMainMenuState()
    {

        getContentPane().removeAll();

        mainPanel = new MainMenuPanel();

        setContentPane( mainPanel );

        revalidate();

    }// End of enterMainMenuState()

    public static void main( String [] args )
    {

        // Hardware acceleration
        System.setProperty( "sun.java2d.opengl" , "true" );

        new MainFrame();

    }

} // End of class

and:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Image;
import java.awt.Panel;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;

import javax.swing.Box;
import javax.swing.BoxLayout;


public class MainMenuPanel extends Panel
{

    private final Image btnIdlImg   = Toolkit.getDefaultToolkit().getImage( "src/MainmenuButtonIdle.png" );
    private final Image btnHvrImg   = Toolkit.getDefaultToolkit().getImage( "src/MainmenuButtonHover.png" );
    private final Image btnPrsImg   = Toolkit.getDefaultToolkit().getImage( "src/MainmenuButtonPress.png" );
    private final Color buttonColor = Color.WHITE;
    private final Font  buttonFont  = new Font( "Arial" , Font.PLAIN , 36 );

    private Panel buttonPanel = new Panel();

    private MainMenuButton  buttonContinue  = new MainMenuButton( "CONTINUE_LAST_SAVE" , buttonFont , buttonColor , btnIdlImg , btnHvrImg , btnPrsImg );
    private MainMenuButton  buttonNew       = new MainMenuButton( "NEW_GAME" , buttonFont , buttonColor , btnIdlImg , btnHvrImg , btnPrsImg );
    private MainMenuButton  buttonLoad      = new MainMenuButton( "LOAD_GAME" , buttonFont , buttonColor , btnIdlImg , btnHvrImg , btnPrsImg );
    private MainMenuButton  buttonSettings  = new MainMenuButton( "OPTIONS" , buttonFont , buttonColor , btnIdlImg , btnHvrImg , btnPrsImg );
    private MainMenuButton  buttonCredits   = new MainMenuButton( "CREDITS" , buttonFont , buttonColor , btnIdlImg , btnHvrImg , btnPrsImg );
    private MainMenuButton  buttonClose     = new MainMenuButton( "CLOSE_BUTTON" , buttonFont , buttonColor , btnIdlImg , btnHvrImg , btnPrsImg );

    public MainMenuPanel()
    {

        // Hintergrundfarbe festlegen
        setBackground( Color.BLACK );

        setLayout( new BorderLayout() );

        buttonPanel.setLayout( new BoxLayout( buttonPanel , BoxLayout.Y_AXIS ) );

        buttonPanel.add( buttonContinue );
        buttonPanel.add( Box.createVerticalStrut( 50 ) );
        buttonPanel.add( buttonNew );
        buttonPanel.add( Box.createVerticalStrut( 50 ) );
        buttonPanel.add( buttonLoad );
        buttonPanel.add( Box.createVerticalStrut( 50 ) );
        buttonPanel.add( buttonSettings );
        buttonPanel.add( Box.createVerticalStrut( 50 ) );
        buttonPanel.add( buttonCredits );
        buttonPanel.add( Box.createVerticalStrut( 50 ) );
        buttonPanel.add( buttonClose );

        add( buttonPanel , BorderLayout.CENTER );

    } // End of constructor

    private class MainMenuButton extends CustomButton
    {

        public MainMenuButton( final String text , final Font font , final Color textColor , final Image img , final Image hoverImg , final Image clickImg )
        {

            super( text , font , textColor , img , hoverImg , clickImg );

        } // End of constructor

        @Override
        public void mouseClicked( MouseEvent e )
        {

            // TODO

        } // End of mouseClicked( MouseEvent e )

    } // End of class

} // End of class
  • 1
    Post a proper [mre] demonstrating the code. That is we need your class with a main() method a JFrame and your custom button added to the frame so we can copy/paste/compile/test to see the described behaviour. – camickr Aug 05 '20 at 14:34
  • Agree with @camickr. But a quick glance at the existing code raises the question.. *why?* As in, why extend button when the functionality of the custom button would be better served with a standard button and a factory method to instantiate & configure it? – Andrew Thompson Aug 05 '20 at 14:45
  • I added the mre. @Andrew Thompson which standard button can I use for this? The normal JButtons are bigger than the image you paint on them and the AWT buttons can't be used with an image. Am I missing something? – Philuminati Aug 05 '20 at 16:17
  • *"I added the mre."* No, it's not an MRE. Firstly, if you can get this to work with 1 button, it will likely solve the problem with two or more. Next, the source references images we don't have handy. Either generate them in code or hot-link to them. One way to get image(s) for an example is to hot link to images seen in [this Q&A](http://stackoverflow.com/q/19209650/418556). E.G. [This answer](https://stackoverflow.com/a/10862262/418556) hot links to an image embedded in [this question](https://stackoverflow.com/q/10861852/418556). Finally, there should only be one **public** class in a .. – Andrew Thompson Aug 05 '20 at 17:49
  • .. MRE. That would be the class with the `main` method. Demote all other classes to default (ie remove the `public` qualifier). Then paste the code in the other classes in after the end of the class with `main` method, then to test it is an MRE, copy/paste the lot into a *new project* in the IDE and check it can be compiled without a single change, and run it to check it demonstrates the problem. The easier it is for others to test code, the more likely help will be offered. *"The normal JButtons are bigger than the image you paint on them"* This depends on the margin of the button. .. – Andrew Thompson Aug 05 '20 at 17:53
  • .. The margin can be set. The answer linked above shows how to remove all the cruft that usually surrounds a button. It also show how a standard button supports functionality such as swapping the icon on focus or hover. – Andrew Thompson Aug 05 '20 at 17:55

0 Answers0