Here is my code: I took out some stuff that I felt wasn't necessary. I might've took out some brackets too, but I'm just trying to show the content I have.
What happens is, when I run the program, the background image paints (it's a PNG in resources), and only ONE button appears (my PLAY button), which is the first button - it's auto-selected.
I actually have four buttons but I've only included PLAY and INSTRUCTIONS in my code. The other three don't show up unless I mouse over them. I know it's probably something weird with the paint method, but I don't know how to fix it.
If I select a different button and minimize the window then open it again, that selected button is the only one that appears. I have to mouse over to get the other buttons to appear.
I've added super.paint() to the paint method too and I get all my buttons but the background is grey.
I think the problem is super.paint() paints all my buttons, and g.drawImage(bg, 0, 0, null) only paints my background and I can't do one without painting over the other.
Sorry if this was a mess. I'm new at Java and I have trouble articulating what I'm trying to say.
public class MainMenu extends JFrame {
private JPanel contentPane;
/**
* Launch the application.
*/
//variables
public static Image bg;
public static void main(String[] args) {
MainMenu mainFrame = new MainMenu();
mainFrame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
mainFrame.setResizable(false);
mainFrame.setLocationRelativeTo(null);
mainFrame.setTitle ("Zumby");
mainFrame.setLayout(null);
// Loads the background image and stores in bg object.
try {
bg = ImageIO.read(new File("zumby.png"));
} catch (IOException e) {
}
mainFrame.setVisible(true);
}
/**
* Overrides the paint method.
* MONDAY
*/
public void paint(Graphics g)
{
// Draws the img to the BackgroundPanel.
System.out.println("paint");
g.drawImage(bg, 0, 0, null);
}
/**
*/
public MainMenu() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 800, 500);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setOpaque(false);
setContentPane(contentPane);
contentPane.setLayout(null);
//create buttons
JButton btnPlay = new JButton("PLAY");
btnPlay.setBackground(Color.BLACK);
btnPlay.setForeground(Color.WHITE);
btnPlay.setFont(font);
btnPlay.setBorder(border);
btnPlay.setFocusPainted(false);
//if "Play" is clicked
btnPlay.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent click) {
setVisible(false);
new GamePlay(); //opens up GamePlay window
}
});
btnPlay.setBounds(600, 64, 141, 61);
contentPane.add(btnPlay);
JButton btnInstructions = new JButton("INSTRUCTIONS");
btnInstructions.setBounds(600, 160, 141, 61);
btnInstructions.setBackground(Color.BLACK);
btnInstructions.setFocusPainted(false);
// btnInstructions.setEnabled(true);
contentPane.add(btnInstructions);
repaint();
pack();
setVisible(true);
}
}
Swing uses a "layering" concept for it's painting...
paint calls paintComponent, paintBorder and paintChildren. By overriding paint and failing to call super.paint, you've prevented the component from painting it's various layers.
In Swing, it is preferred to use paintComponent to provide custom painting, which allows you to paint underneath any other components that might be added to the component.
public class TestPaint01 {
public static void main(String[] args) {
new TestPaint01();
}
public TestPaint01() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Image backgroundImage;
public TestPane() {
try {
BufferedImage background = ImageIO.read(new File("/path/to/image.jpg"));
//backgroundImage = background.getScaledInstance(-1, background.getHeight() / 4, Image.SCALE_SMOOTH);
backgroundImage = background;
} catch (IOException ex) {
ex.printStackTrace();
}
setLayout(new GridBagLayout());
add(new JButton("Hello"));
}
#Override
public Dimension getPreferredSize() {
return backgroundImage == null ? super.getPreferredSize() : new Dimension(backgroundImage.getWidth(this), backgroundImage.getHeight(this));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int x = (getWidth() - backgroundImage.getWidth(this)) / 2;
int y = (getHeight() - backgroundImage.getHeight(this)) / 2;
g.drawImage(backgroundImage, x, y, this);
}
}
}
You might find A Closer look at the Paint Mechanism and Painting in AWT and Swing informative.
I think it's because you're overriding the paint method. It's better to override repaint, then call super.repaint(); Like this:
public void repaint(Graphics g)
{
super.repaint(g);
// Draws the img to the BackgroundPanel.
System.out.println("paint");
g.drawImage(bg, 0, 0, null);
}
Then the components get redrawn as well.
But if all you want to do is show an image as the background see here.
You're overriding paint() but don't call super.paint(). So the normal painting of the components done by the JFrame's paint() method implementation is not executed.
Since you are using Swing and JFrame the painting mechanism used to override paintComponent not paint that is usually used with applets or AWT.
Related
I'm developing a Java application that demands a customized button. I'm using Swing for the GUI and found myself limited to some tricky solutions.
Here's one I found (from this website). It's supposed to use a custom image for the button and make it round.
public class RoundButton extends JButton
{
private static final long serialVersionUID = 1L;
protected Shape shape, base;
public RoundButton()
{
this(null, null);
}
public RoundButton(Icon icon)
{
this(null, icon);
}
public RoundButton(String text)
{
this(text, null);
}
public RoundButton(Action a)
{
this();
setAction(a);
}
public RoundButton(String text, Icon icon)
{
setModel(new DefaultButtonModel());
init(text, icon);
if(icon==null) return;
setBorder(BorderFactory.createEmptyBorder(1,1,1,1));
setBackground(Color.BLACK);
setContentAreaFilled(false);
setFocusPainted(false);
//setVerticalAlignment(SwingConstants.TOP);
setAlignmentY(Component.TOP_ALIGNMENT);
init_shape();
}
protected void init_shape()
{
if(!getBounds().equals(base))
{
Dimension s = getPreferredSize();
base = getBounds();
shape = new Ellipse2D.Float(0, 0, s.width-1, s.height-1);
}
}
#Override
public Dimension getPreferredSize()
{
Icon icon = getIcon();
Insets i = getInsets();
int iw = Math.max(icon.getIconWidth(), icon.getIconHeight());
return new Dimension(iw+i.right+i.left, iw+i.top+i.bottom);
}
#Override
protected void paintBorder(Graphics g)
{
init_shape();
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(getBackground());
//g2.setStroke(new BasicStroke(1.0f));
g2.draw(shape);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
}
#Override
public boolean contains(int x, int y)
{
init_shape();
return shape.contains(x, y);
//or return super.contains(x, y) && ((image.getRGB(x, y) >> 24) & 0xff) > 0;
}
}
Here's some test code I wrote:
public class BtnTest extends JFrame
{
private static final long serialVersionUID = 1L;
private RoundButton btn;
public BtnTest()
{
init_components();
}
private void init_components()
{
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400, 400);
setBackground(Color.BLACK);
setResizable(false);
btn = new RoundButton(
new ImageIcon("/path/to/file.png"));
btn.setBounds(50, 50, 50, 50);
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e)
{
System.out.println("click");
}
});
add(btn);
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
BtnTest frame = new BtnTest();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
This is the result:
The problem is: the image's rendered away from the button location, so my ActionListener isn't triggered when I press the icon, but when the region inside the black circle (top left) is clicked. Can someone explain me why, and offer a solution?
PS: I'm in my first year of Java programming, so make it as simple as possible please.
PS2: JavaFX and other external solutions are out of question, this must be done pure Java.
The problem is: the image's rendered away from the button location,
What is happening is that you are adding the button to the frame without using a constraint. By default this means the component is added to BorderLayout.CENTER, which means the component will be sized to fill the entire frame.
Also, by default, when you paint an Icon in a JLabel, the Icon is centered in the spaced available to the label, so you see the Icon in the center of the frame. Try resizing the frame to see the Icon move.
However, you hard code the painting of the Border to be painted at the (0, 0) location of the label so it paints at the top/left.
The contains() method is also defined from the top/left of the component, so mouse detection only works from the top/left, not the center.
This means your button must always be painted at its "preferred size" in order for it to be painted properly and for the contains(...) method to work.
A simple way to demonstrate this is to use:
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout( new FlowLayout() );
The FlowLayout does respect the preferred size of the button, so the Icon and Border will be painted properly.
Other options (instead of changing the layout of the frame) are to use a "wrapper" panel for the button. For example:
//add(btn);
JPanel wrapper = new JPanel(); // default to FlowLayout
wrapper.add( btn );
add( wrapper );
Now when you add the panel to the frame, the panel will grow in size, but the button will still be painted at its preferred size.
btn.setBounds(50, 50, 50, 50);
Also, not you should NOT be using this method. It is the job of the layout manager to set the size/location of the component. The above statement is effectively ignored.
So, after messing around for a good hour trying to figure out why my JPanel was flickering when moving my mouse I found out that that wasn't the case at all. From what I found out, from changing the order im drawing images, was that the buffer image is being drawn before everything is done being drawn to it. I can only assume that moving my mouse somehow causes the image to be drawn before the render cycle is finished. Im drawing everything to a separate image, overriding a JPanels paintComponent method, then letting the JFrame paint the panel. Moving the frame or resizing it causes the same thing but im not worried about those.
public void render() {
Graphics g = screen.getGraphics();
// State based system, renders all subsequent images
Manager.render(g);
// For fading between state changes
g.setColor(new Color(0, 0, 0, Variables.alpha));
g.fillRect(0, 0, Variables.frame.getWidth(), Variables.frame.getHeight());
// Only way that worked to get the frame to update the JPanel
Variables.frame.repaint();
}
public static void generateFrame(String title, int width, int height, boolean resize, KeyListener keys,
MouseListener mouse, MouseMotionListener mMotion, MouseWheelListener mWheel) {
frame = new JFrame(title);
frame.setResizable(resize);
frame.addKeyListener(keys);
frame.addMouseListener(mouse);
frame.addMouseMotionListener(mMotion);
frame.addMouseWheelListener(mWheel);
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
shutdown();
}
});
pane = new JPanel() {
private static final long serialVersionUID = -7796800583217917918L;
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(Variables.screen, -Variables.xOffset, -Variables.yOffset,
Variables.frame.getWidth() + Variables.yOffset * 2,
Variables.frame.getHeight() + Variables.xOffset * 2, null);
}
};
frame.add(Variables.pane);
frame.pack();
frame.setSize(width, height);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
I am trying to draw an image to a JPanel which in turn is added to a JFrame, see here:
JFrame screen;
public void welcome(){
screen = new JFrame("Welcome");
screen.setVisible(true);
screen.pack();
screen.setBackground(Color.darkGray);
screen.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
// in the original code there is series of methods here that eventually calls the drawBoard() method
public void drawBoard(){
try {
final BufferedImage gboard = ImageIO.read(new File("cutsomGameBoard.jpg"));
final BufferedImage featPanel = ImageIO.read(new File("extraPanel.png"));
board = new JPanel(){
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(gboard, 0, 0, this);
}
};
extra = new JPanel(){
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(featPanel, 0, 0, this);
}
};
board.setSize(480, 480);
extra.setSize(480, 320);
}
catch (IOException e) {
e.printStackTrace();
}
}
gameScreen.add(toolbar, BorderLayout.PAGE_START);
gameScreen.add(board, BorderLayout.EAST);
gameScreen.add(extra, BorderLayout.WEST);
gameScreen.setVisible(true);
screen.add(gameScreen);
}
My problem is that when running the code, only a small corner of the buffered image is visible and I am not sure if it is a problem with the frame layout, the panel size or the drawImage method arguments, ideas?
P.S. The output: screenshot of java window
You didn't override the getPreferredSize() method of your custom component so the default size is basically (10, 10) which is the size of a panel using a FlowLayout with no added components.
Don't use a JPanel to display an image. Or if you do want to use a JPanel then you need to implement the getPreferredSize() method to return the size of your image.
The easiest solution is to just use a JLabel with an ImageIcon.
Read the section from the Swing tutorial on How to Use Icons for more information and working examples.
I have a very simple java swing application, I have a canvas class extended from JPanel
public class Canvas extends JPanel
{
private void doDrawing(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.drawString("Java 2D", 50, 50);
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
doDrawing(g);
}
}
And then I have my main class
public class SwingCounter extends JFrame {
private JTextField tfCount; // Use Swing's JTextField instead of AWT's TextField
private int count = 0;
public SwingCounter () {
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(new JLabel("Counter"));
tfCount = new JTextField("0", 10);
tfCount.setEditable(false);
cp.add(tfCount);
JButton btnCount = new JButton("Count");
cp.add(btnCount);
Canvas canvas = new Canvas();
canvas.setSize(150, 150);
cp.add(canvas);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Exit program if close-window button clicked
setTitle("Swing Counter"); // "this" JFrame sets title
setSize(300, 100); // "this" JFrame sets initial size
setVisible(true); // "this" JFrame shows
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new SwingCounter(); // Let the constructor do the job
}
});
}
}
It is basically code from a tutorial, apart from the JPanel. Everything shows fine, the JPanel/Canvas doesn't. What is missing?
You are adding your Canvas class to a panel which uses a FlowLayout. The FlowLayout respects the preferred size of all components. Your component has a preferred size of (0, 0) so there is nothing to paint.
You need to override the getPreferredSize() method of your Canvas class to return an appropriate Dimension for your panel.
Read the section from the Swing tutorial on Custom Painting for more information and a working example that does implement the getPreferredSize() method.
Also, don't call your class Canvas, since that is already an AWT component and is confusing. Use a more descriptive name.
I have been searching for a reason for this behavior in my code for quite some time now. I do not want to dive too deeply into the Swing API to figure why this occurs. I would appreciate any information regarding what causes this problem.
This is a simplified version of the application I am writing, the problems seems to be when I click draw the first time, the image will not paint onto the panel, but when I click it the second time, it would paint the image perfectly. Any drawing done after will work correctly, but the initial paint problem is annoying me greatly. Thanks for any help! :)
public class ImageTester {
public static void main(String[] args) {
final JFrame window = new JFrame("Image Tester");
final JPanel pane = new JPanel();
JButton draw = new JButton("Draw");
JButton paint = new JButton("Paint");
Toolkit k = Toolkit.getDefaultToolkit();
final Image i = k.createImage("tester.jpg");
//pane.getGraphics().drawImage(i, 22, 15, null);
draw.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
System.out.println(pane.getGraphics());
pane.getGraphics().drawRect(5, 5, 15, 15);
pane.getGraphics().drawImage(i, 15, 15, null);
System.out.println("Performance");
}
});
paint.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
}
});
pane.add(draw);
pane.add(paint);
window.add(pane);
window.setVisible(true);
window.setSize(new Dimension(400, 400));
window.setLocationRelativeTo(null);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Besides the advice of camickr..
Images load asynchronously using Toolkit.createImage(). Either use ImageIO.read(URL/File/InputStream) or add a MediaTracker.
E.G.
On first run I see.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.net.URL;
import javax.imageio.ImageIO;
public class ImageTester {
public static void main(String[] args) throws Exception {
final JFrame window = new JFrame("Image Tester");
JButton draw = new JButton("Draw");
JButton paint = new JButton("Paint");
final Image i = ImageIO.read(new URL(
"http://pscode.org/media/citymorn2.jpg"));
ImagePanel gui = new ImagePanel();
gui.setImage(i);
draw.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
}
});
paint.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
}
});
gui.add(draw);
gui.add(paint);
window.add(gui);
window.setVisible(true);
window.setSize(new Dimension(400, 400));
window.setLocationRelativeTo(null);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
class ImagePanel extends JPanel {
Image i;
public void setImage(Image image) {
i = image;
}
public void paintComponent(Graphics g) {
//System.out.println(pane.getGraphics());
super.paintComponent(g);
g.drawRect(5, 5, 15, 15);
g.drawImage(i, 15, 15, null);
System.out.println("Performance");
}
}
Don't use the getGraphics() method. That painting will be lost the next time Swing determines a component needs to repaint itself.
Custom painting is done by overriding the paintComponent() method of a JPanel (or JComponent) and then you add the panel to the frame.
See Custom Painting for more information and examples.
When you use createImage(), the image data is loaded, but it isn't translated into renderable pixels until it knows what component it's going to be drawn on. The Toolkit.prepareImage() method can do this. Add this line to the end of your program, and the paint problem will go away:
k.prepareImage(i, -1, -1, pane);