I am working with GUIs in Java using JFrame and JPanel, as well as ActionListener to edit an image when I click a button. Currently, I am having a lot of trouble getting my JPanel class called ButtonPanel to interact with my BufferedImage img. I am trying to display the height of the image but nothing is working and I have tried various solutions. My code for the ButtonPanel class is:
class ButtonPanel extends JPanel
{
//JButton makeBlue;
BufferedImage img;
ButtonPanel(BufferedImage x)
{
final JButton makeBlue = new JButton ("Make Blue");
img = x;
//int width, height;
setBackground(Color.RED);
int height = 0;
add(makeBlue);
ActionListener action =
new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
if (e.getSource()== makeBlue)
{
//img = x;
height = img.getHeight();
// System.out.println(rgbvalue);
//System.out.println(width + " " + height);
setBackground(Color.GREEN);
repaint();
}
}
};
makeBlue.addActionListener(action);
}
}
Whenever I try to use a method from the BufferedImage API in order to edit the image, such as in the above code, I get this error:
ImageEditorDeluxe.java:184: error: local variable height is accessed from within inner class; needs to be declared final
height = img.getHeight();
I have played around with where I initialize height but nothing has worked. Any help would be appreciated.
I have another class called ProgramWindow in which I add all the different JPanels of the image editor to one main JFrame and I think this is where my problem might be, as the BufferedImage is null. Here is the code for ProgramWindow:
class ProgramWindow extends JFrame
{
ProgramWindow()
{
ImagePanel ip = new ImagePanel();
ChooseFile cf = new ChooseFile();
ButtonPanel bp = new ButtonPanel(ip.getImg());
add(ip, BorderLayout.CENTER);
add(cf, BorderLayout.SOUTH);
add(bp, BorderLayout.WEST);
}
}
I have concluded that the ButtonPanel in ProgramWindow is being passed a null parameter but I do not know why this is. I have a method called getImg in the ImagePanel class which I am calling as the parameter for ButtonPanel. Here is the code for ImagePanel:
class ImagePanel extends JPanel
{
BufferedImage img;
ImagePanel()
{
setBackground(Color.BLUE); //to test
final JButton button = new JButton ("Display picture");
add(button);
ActionListener action =
new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
if (e.getSource()==button)
{
try
{
img = ImageIO.read(ChooseFile.getFile());
}
catch(IOException f)
{
f.printStackTrace();
}
repaint();
}
}
};
button.addActionListener(action);
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if (img != null)
g.drawImage(img, 0, 0, this);
}
public void setImage(BufferedImage i)
{
img = i;
repaint();
}
public BufferedImage getImg()
{
return img;
}
}
You're declaring height in the constructor and so it is local to the constructor. Perhaps it would be better to make it an instance field of the class instead.
public class ButtonPanel extends JPanel {
private BufferedImage img;
private int height;
Having said that, why even have a height variable field, since you can obtain it any time you'd like by calling img.getHeight()?
Edit
Your second problem, one we can't help you on yet, is that your img variable holds a null reference. Given your new code, this suggests that ip.getImg() is returning null, but don't take my word on this, test it. Put this line in your code:
System.out.println("is ip.getImg() returning null? " + (ip.getImg()));
Edit 2
Of course you're getting null. You're extracting img from ImagePanel before the user has had a chance to interact with it. Since it only gets an image when the user presses the button, and since you're extracting the image on class creation, before the user has had a chance to do squat, the only possibility is that when you extract the image on class creation, you're going to get null. Solution: don't do that.
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.
I'm looking to display an image on a JPanel, but when I run the code, the panel is blank. The method I'm attempting is a slightly modified version of this tutorial.
I've confirmed that all methods in the code are actually called using println. My frame and panel are visible, and I'm fairly certain I've added both the icon to the label, and the label to the panel. What am I missing?
The relevant parts of my code:
public class SYSTEM
public static void start() {
GameFrame GF = new GameFrame();
GamePanel GP = new GamePanel();
GF.add(GP);
GP.loadSprite("Player", new Dimension(0,0));
}
}
public class GameFrame extends JFrame {
public GameFrame() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(SYSTEM.windowSize);
setVisible(true);
}
}
public class GamePanel extends JPanel {
ArrayList<Sprite> sprites = new ArrayList<Sprite>();
GamePanel() {
setVisible(true);
setSize(SYSTEM.windowSize);
setLayout(null);
}
public void loadSprite(String spriteName, Dimension pos) {
sprites.add(new Player());
add(sprites.get(0).getIcon());
}
public class Sprite {
protected JLabel icon;
protected BufferedImage image;
protected String filePath;
protected Dimension pos;
public JLabel getIcon() {
return icon;
}
}
public class Player extends Sprite {
public Player() {
filePath = "FILEPATH_OMITTED";
pos = new Dimension(0,0);
try {
image = ImageIO.read(new File(filePath));
} catch (IOException e) {
System.out.println(e);
}
icon = new JLabel(new ImageIcon(image));
}
}
Don't use components for games, you've thrown out the layout manager which is responsible for determining the size and location of the components, but you've failed to compensate for it. I'd recommend that you use a custom painting approach instead, see Painting in AWT and Swing and Performing Custom Painting for starters.
Just so I'm clear, you problem is directly related to setLayout(null); which is bad regardless of what you're doing
This is my first post in stackoverflow :D
Sorry for my english (I am italian)
So I have a method called setFrame
public static void setFrame()
{
JFrame GameMain = new JFrame();
GameMain.setSize(width, high);
GameMain.setLocationRelativeTo(null);
GameMain.setTitle("Tic Tac Toe!");
GameMain.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Grid grid = new Grid(600, 600);
GameMain.add(grid);
GameMain.pack();
GameMain.setVisible(true);
}
And this is the class Grid
public class Grid
{
private static int high;
private static int width;
public Grid(int width, int high)
{
width = width;
high = high;
BufferedImage img = null;
try {
img = ImageIO.read(new File("/grids/griglia1.png"));
} catch (IOException e) {
}
}
}
Ok so the problem is that when in the method setFrame I write "GameMain.add(grid);"
The console gives me an error like
"The method add(Component) in the type Container is not applicable for the arguments (Grid)"
Thanks for helping
From what I can tell, you would like to add an image to the JFrame. However, there are a few things wrong with your implementation. First of all, you cannot add images directly to a component. You need to encapsulate them in a JLabel. Additionally, the reason why you cannot add the grid class to your JFrame is because it isn't a swing component.
The easiest way to accomplish what you are trying to do would be to make a JPanel and then add it to the frame, and then make a JLabel that encapsulates your image and add that to the JPanel.
public static void setFrame()
{
JFrame GameMain = new JFrame();
GameMain.setSize(width, high);
GameMain.setLocationRelativeTo(null);
GameMain.setTitle("Tic Tac Toe!");
GameMain.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel GamePanel = new JPanel();
GamePanel.setSize(width, high);
GameMain.add(GamePanel);
background = new JLabel(new ImageIcon(ImageIO.read(new File("/grids/griglia1.png"))));
GamePanel.add(background);
GameMain.pack();
GameMain.setVisible(true);
}
This way you can bypass the Grid class altogether. Hope this helps.
I've been working on a drinking game program for school.
//this is the game //http://sotallytober.com/games/verbal/mexican/
Anyway, I painted a image in an JPanel using the following code (it's an class that extends JPanel)
public class iconPanel extends JPanel {
ImageIcon image;
Image pic;
public iconPanel(String startScreenImage) {
image = new ImageIcon(startScreenImage);
pic = image.getImage();
this.setBackground(new Color(0, true));
}
#Override
public void paintComponent(Graphics g) {
//Paint background first
g.drawImage (pic, 0, 0, getWidth (), getHeight (), this);
}
Now in my other class, where I have the layout and all the components I declare on top my JPanels like this :
private JPanel pnDrinkPlayerBW;
Then in a method in the same class named MakeComponents I set the JPanel to :
pnDrinkPlayerBW = new iconPanel("img/glass.jpg");
pnDrinkPlayerBW.setPreferredSize(new Dimension(183,61));
Afterwards I add it to the Panel where it has to come, and that panel onto the frame in the method makeLayout() (I don't think that it's useful code, so if you want to see it, ask me)
Then if a button gets pressed, I want to change the glass.jpg image to another image, for example beerGlass0.png, so in the actionlistener in another method actionEvents() I do this:
pnDrinkPlayerBW = new iconPanel("img/beerGlass.png");
pnDrinkPlayerBW.setPreferredSize(new Dimension(183,61));
pnDrinkPlayerBW.repaint();
I'll put the constructor of this class also here, just if people need it :
public SpelScreen(){
makeComponents();
makeLayout();
actionEvents();
} // note : this is'nt the full constructor, just the call for the methods I talked about, SpelScreen extends JFrame..
So what I want to do, is to set in the class SpelScreen a new image for the iconPanel and repaint it using the same instance of the spelscreen.
I am quite new to Java, so don't expect me to rapidly understand complicated code :)
Thanks!
First off you're forgetting to call super.paintComponent in your paintComponent method. Also paintComponent should be protected not public
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(..);
}
Second, I don't think you want to create a new iconPanel and immediately call repaint on it. That will probably do nothing.
Instead have a setter for your pic, then just repaint(); inside that method.
public void setPic(Image pic) {
this.pic = pic;
repaint();
}
Then you can just call the setPic from the the class you created the iconPanel in. For example
iconPanel panel = new iconPanel("...");
... // then in some listener
public void actionPerformed(ActionEvent e) {
Image pic = null;
try {
pic = ImageIO.read(...);
panel.setPic(pic);
} catch ...
}
Another option is just to have an array of images you initialize in the iconPanel. Then in a a listener, you can just change the index the if the image array then call repaint. Something like this
Image[] images = new Image[5];
int imageIndex = 0;
// fill the array with images
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(images[imageIndex], ...);
}
Then you could just change the imageIndex and repaint()
Side Note
You should be using Java naming convention. Class names being with capital letters i.e. iconPanel → IconPanel
Update
Using ImageIcon
public void setImage(ImageIcon img) {
pic = img.getImage();
repaint();
}
Code in Question
There isn't an actionListener for the image thumbnails, yet when clicked they update the image.
From this webpage.
Edit: I am currently importing images using JFileChooser and then creating a thumbnail and displaying the full image in a similar way to this, although not using ImageIcons. But would like to use this method so when I add an image it adds to the list and allows me to click the thumbnail to show that image.
However mine using actionListeners to change when something is pressed but this doesn't and can't understand the code where it does.
Thanks
Edit2:
Regarding the repaint option:
I have a class which extends component which then calls a repaint function.
public class Image extends Component {
private BufferedImage img;
//Print Image
public void paint(Graphics g) {
g.drawImage(img, 0, 0, null);
}
}
I then have a class with all my Swing components which call methods from other classes.
Image importedImage = new Image(loadimageone.openFile());
Image scaledImage = new Image();
// Save image in Buffered Image array
images.add(importedImage.getImg());
// Display image
imagePanel.removeAll();
imagePanel.add(importedImage);
imagePanel.revalidate();
imagePanel.repaint();
previewPanel.add(scaledImage);
previewPanel.revalidate();
previewPanel.repaint();
If I remove the revalidate or repaint it wont' update the image on the screen.
Edit 3:
This is the code on how I implemented the dynamic buttons:
//Create thumbnail
private void createThumbnail(ImpImage image){
Algorithms a = new Algorithms();
ImpImage thumb = new ImpImage();
//Create Thumbnail
thumb.setImg(a.shrinkImage(image.getImg(), 75, 75));
//Create ImageIcon
ImageIcon icon = new ImageIcon(thumb.getImg());
//Create JButton
JButton iconButton = new JButton(icon);
//Create ActionListener
iconButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
bottomBarLabel.setText("Clicked");
imagePanel.removeAll();
imagePanel.add(images.get(position)); //Needs Fixing
imagePanel.revalidate();
}
});
//Add to previewPanel
previewPanel.add(iconButton);
previewPanel.revalidate();
previewPanel.repaint();
}
It looks like it uses ThumbnailAction instead which extends AbstractAction (at the very bottom of the code). Swing components can use Actions instead of ActionListeners. The advantage of Actions is that buttons can share an Action and they will automatically use the same key-bindings etc.
http://docs.oracle.com/javase/tutorial/uiswing/misc/action.html
EDIT: I have added some code demonstrating that you do not need to explicitly repaint(). Give it a try.
public static void main(String args[]) {
JFrame frame = new JFrame();
frame.setSize(200, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel(new GridLayout(2, 1));
final JLabel iconLabel = new JLabel();
JButton button = new JButton("Put Image");
button.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent arg0) {
JFileChooser fc = new JFileChooser();
int returnVal = fc.showOpenDialog(null);
if (returnVal == JFileChooser.APPROVE_OPTION) {
try {
iconLabel.setIcon(new ImageIcon(ImageIO.read(fc.getSelectedFile())));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
panel.add(iconLabel);
panel.add(button);
frame.add(panel);
frame.setVisible(true);
}
EDIT 2 (There is no Edit 2)
EDIT 3: Try this
public class MyActionListener implements ActionListener {
private JPanel imagePanel;
private Image image;
public MyActionListener(JPanel imagePanel, Image image) {
this.imagePanel = imagePanel;
this.image = image;
}
#Override
public void actionPerformed(ActionEvent arg0) {
System.out.println("Clicked");
imagePanel.removeAll();
imagePanel.add(image); //Needs Fixing
imagePanel.revalidate();
}
}