I am trying to display two different images on my screen. one of which is a banner that goes at the top of my JFrame, and another that I just placed randomly below the banner for testing purposes. The issue I am having is that while I can display a single image on the screen by adding an object of class WindowStructure to my window, I am not able to display more than one image at a time. Only the last image added to the window is displayed on the screen:
Here is the window class:
import javax.swing.JFrame;
public class Window extends JFrame {
public Window(String name) {
super(name);
setSize(1200, 700);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
WindowStructure banner = new WindowStructure("Beatles Logo.jpg", 0, 0, getWidth(), 75);
WindowStructure fireball = new WindowStructure("fireball.png", 100, 100, 100, 100);
add(banner); //banner
add(fireball);
setVisible(true);
while(true){
repaint();
}
}
public void paint(Graphics g
) {
super.paintComponents(g);
}
}
Here's the actual class that creates the image:
public class WindowStructure extends JPanel {
ImageIcon imageIcon;
int xLoc, yLoc, xSize, ySize;
public WindowStructure(String bannerImg, int xLoc, int yLoc, int xSize, int ySize){
URL bannerImgURL = getClass().getResource(bannerImg);
imageIcon = new ImageIcon(bannerImgURL);
this.xLoc = xLoc;
this.yLoc = yLoc;
this.xSize = xSize;
this.ySize = ySize;
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(imageIcon.getImage(), xLoc, yLoc, xSize, ySize, null);
}
}
The default layout manager for JFrame is BorderLayout.
As the documentation says: "BorderLayout interprets the absence of a string specification the same as the constant CENTER". For instance:
add(banner); // Same as p.add(banner, BorderLayout.CENTER);
add(fireball); // Same as p.add(fireball, BorderLayout.CENTER);
You can fix this if you specify the location as a second argument to add():
add(banner, BorderLayout.NORTH);
add(fireball, BorderLayout.CENTER);
Or you can use another layout manager for the JFrame by invoking setLayout(LayoutManager) in your Window class constructor.
public class Window extends JFrame {
public Window(String name) {
super(name);
setLayout(new FlowLayout()); // or another the layout that best fit your needs...
...
Guide about layout managers: http://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html
The JFrame javadocs state that the default layout manager used is BorderLayout. To add multiple components, you have to specify a different place in the layout to put each one (NORTH, SOUTH, EAST, WEST, CENTER). By default it's BorderLayout.CENTER if not specified, which is why you only see the last one added.
Related
I want to create a grid of squares in my Java Swing GUI. I need to toggle their state so I'm thinking a JToggleButton is appropriate for each of the squares.
The problem that I have is that I want to partially colour each toggle button according to given percentages. e.g. if 50% and 50% I want the left half of the button to be green and the right to be red. If 25%,25%,50% I'd need 3 colours. I also need to use the button Text field so hiding that isn't allowed in the solution.
Is it possible to do something like this with a JToggleButton? Is there a better element to use? Or how might I go about it?
I apologise for not posting my work so far but I can't find anything close to an example of this type of thing.
I want to end up with something like this where each square is a button.
You can construct a button with changeable 2-color background as required by overriding
paintComponent:
import java.awt.*;
import javax.swing.*;
public class TwoColorsButton extends JButton{
private final Color leftColor, rightColor;
private int percentOfLeftColor;
public TwoColorsButton(String text) {
this(text,Color.RED, Color.GREEN, 50);
}
public TwoColorsButton(String text, Color leftColor,Color rightColor, int percentOfLeftColor) {
super(text);
this.leftColor = leftColor;
this.rightColor = rightColor;
this.percentOfLeftColor = percentOfLeftColor;
//make button transparent
setOpaque(false);
setContentAreaFilled(false);
setBorderPainted(false);
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
int leftWidth = getWidth() * percentOfLeftColor/100;
g2.setColor(leftColor);
g2.fillRect(0, 0, leftWidth , getHeight());
g2.setColor(rightColor);
g2.fillRect(leftWidth, 0, getWidth() -leftWidth, getHeight());
g2.setPaint(Color.BLACK);
super.paintComponent(g2); //button is transparent so super paints text only
g2.dispose();
}
public void setPercentOfLeftColor(int percentOfLeftColor) {
this.percentOfLeftColor = percentOfLeftColor;
repaint();
}
public int getPercentOfLeftColor() {
return percentOfLeftColor;
}
public static void main(String[] args) {
//run button test
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
JPanel topPanel = new JPanel();
TwoColorsButton twoColorBtn = new TwoColorsButton("Some Text");
topPanel.add(twoColorBtn);
frame.add(topPanel, BorderLayout.PAGE_START);
JPanel bottomPanel = new JPanel();
JButton runTestBtn = new JButton("Run test");
runTestBtn.addActionListener(e->{
runTestBtn.setEnabled(false);
new Timer(1000, e1 ->{
int percent = twoColorBtn.getPercentOfLeftColor() +25;
percent = percent > 100 ? 0 : percent;
twoColorBtn.setPercentOfLeftColor(percent);
}).start();
});
bottomPanel.add(runTestBtn);
frame.add(bottomPanel, BorderLayout.PAGE_END);
frame.pack();
frame.setVisible(true);
}
}
The code can easily be modified to allow 3 colors, if needed.
(Test it online here)
(See a basic 3 colors toggle button here)
I learned that if you override
protected void paintComponent(Graphics g)
You can get an image painted as the background of a class that extends javax.swing.JPanel.
In my code I have 2 instances of the same Class extending JPanel with almost exactly the same code just with a different position and background image in a second JPanel and while one gets the background the other one does not. Here is my code:
public class CardPanel extends JPanel {
private int x, y, width, height;
private BufferedImage background;
public CardPanel(int x, int y, int width, int height, BufferedImage background) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.background = background;
createCardPanel();
}
private void createCardPanel() {
setPreferredSize(new Dimension(width, height));
setMaximumSize(new Dimension(width, height));
setMinimumSize(new Dimension(width, height));
setFocusable(false);
setOpaque(true);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(background, x, y, null);
}
}
And how I use it:
pCardPanel = new CardPanel();
dCardPanel = new CardPanel();
Declaring the CardPanels
private void createCardPanels(String imgPath) {
BufferedImage background = ImageLoader.loadImage(imgPath);
pCardPanel = new CardPanel(0, (height - Card.CARD_HEIGHT), width, Card.CARD_HEIGHT, background.getSubimage(0, (height - Card.CARD_HEIGHT), width, Card.CARD_HEIGHT));
dCardPanel = new CardPanel(0, 0, width, Card.CARD_HEIGHT, background.getSubimage(0, 0, width, Card.CARD_HEIGHT));
this.add(pCardPanel, BorderLayout.SOUTH);
this.add(dCardPanel, BorderLayout.NORTH);
}
Method for creating and adding the CardPanels
createCardPanels("/textures/background.png");
Using the method
public void addCardImage(BufferedImage img, boolean playerCard) {
JLabel imgLabel = new JLabel();
ImageIcon icon;
icon = new ImageIcon(img);
imgLabel.setIcon(icon);
cardImages.add(imgLabel);
if (playerCard)
pCardPanel.add(imgLabel);
else
dCardPanel.add(imgLabel);
display.pack();
}
This last method is called for adding Card Images to te panel, this part works. Now to my problem:
this is how it looks. (there are some other flaws like the card position but this will be a later issue I can fix myself)
As you can see, the panel on the bottom (pCardPanel) has no background image. Any ideas why it might be this way? Thanks in advance
You can get an image painted as the background of a class that extends javax.swing.JPanel
A background generally implies that the image fills the entire panel and the size of the panel is the same as the size of the image. Therefore when you paint the image the code should be:
g.drawImage(background, 0, 0, null);
So the image is always painted at the top left of the panel.
When the panel is added to the frame, the layout manager will set the location of the panel.
just with a different position
pCardPanel = new CardPanel(0, (height - Card.CARD_HEIGHT),
I would guess the problem is the "y" value is outside the size of the panel, so you don't see the image.
That is your preferred size does not account for the fact that you are attempting to paint the image at some location other than (0, 0) in which case the preferred size should be something like:
setPreferredSize(new Dimension(x + width, y + height));
However, you don't want to do that, since each component should be independent of other components. It should not know or care that you are trying to position two panels above/below one another. It should just worry about painting its own image and let the layout manager worry about setting the location of each panel.
So what you really want to do is just paint the image at (0, 0) and let the layout manager determine the location of the panel.
You are already using the BorderLayout. So it is the job of the layout manager to set the location of the component in the "SOUTH" to some non-zero "y" value.
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.
when I run this code PaintComponent is never called because the "painted" message is never printed and I do not know why? can anyone help?
public class DisplayManager extends JPanel {
public static final int WIDTH = 700, HEIGHT = 900;
public Bottle bottle1 = new Bottle("res/bottleimage.png");
public Slider slider1 = new Slider();
public void initDisplay()
{
JFrame frame = new JFrame();
JPanel panel = new JPanel();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(WIDTH, HEIGHT));
frame.add(panel);
frame.setVisible(true);
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
bottle1.imageIcon.paintIcon(this, g, 50, 50);
System.out.println("painted");
}
}
There are a couple of problems with the basic code:
as already mentioned you need to add an instance of your DisplayManager class to a frame or panel.
When you do custom painting you need to override the getPreferredSize() method of the component to return your desired size. Currently the preferred size of your component is (0, 0).
The suggestion to add the DisplayManager to the frame only works because the default layout manager is a BorderLayout and by default is added to the CENTER of the layout which means it get all the available space in the frame.
However if you use:
frame.add(this, BorderLayout.PAGE_START);
you won't see the component size it has a size of (0, 0);
I made a JComponent that displays a rectangle of a specified color. (Haven't found any other way to achieve this effect). Problem is, it doesn't follow JFrame.pack() and Layout Managers as expected.
Code:
import java.awt.*;
import javax.swing.*;
public class FooRunnable implements Runnable{
private class ColorSample extends JComponent{
private Color sampleColor;
private int width, height;
public ColorSample(int rgb, int w, int h){
sampleColor = new Color(rgb);
width = w;
height = h;
}
public Dimension getSize(){
return new Dimension(width, height);
}
public int getWidth(){
return width;
}
public int getHeight(){
return height;
}
public boolean isDisplayable(){
return true;
}
public void paintComponent(Graphics g){
g.setColor(sampleColor);
g.fillRect(0, 0, width, height);
}
}
public void run(){
JFrame mainFrame = new JFrame();
//mainFrame.setSize(500, 300);
Container mainContent = mainFrame.getContentPane();
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainContent.setLayout(new BoxLayout(mainContent, BoxLayout.PAGE_AXIS));
JPanel specifyFilePanel = new JPanel();
specifyFilePanel.setLayout(new BoxLayout(specifyFilePanel, BoxLayout.LINE_AXIS));
JLabel filenameLabel = new JLabel("File: ");
JButton browseButton = new JButton("Browse...");
specifyFilePanel.add(Box.createHorizontalStrut(8));
specifyFilePanel.add(filenameLabel);
specifyFilePanel.add(browseButton);
specifyFilePanel.add(Box.createHorizontalStrut(8));
JPanel colorStatusPanel = new JPanel();
colorStatusPanel.setLayout(new BoxLayout(colorStatusPanel, BoxLayout.Y_AXIS));
JLabel statusLabel = new JLabel("");
JButton roll = new JButton("Operate");
colorStatusPanel.add(new ColorSample(Color.red.getRGB(), 50, 100));
colorStatusPanel.add(statusLabel);
colorStatusPanel.add(roll);
mainContent.add(Box.createVerticalStrut(5));
mainContent.add(specifyFilePanel);
mainContent.add(Box.createVerticalStrut(10));
mainContent.add(colorStatusPanel);
mainContent.add(new JPanel());
mainFrame.pack();
mainFrame.setVisible(true);
}
}
I tried experimenting between pack and explicitly specifying the frame's size. Here are the default appearances of my GUI on various settings:
Plain mainFrame.pack():
mainFrame.setSize(500, 500):
mainFrame.setSize(500, 300):
The closest to what I intend to achieve is mainFrame.setSize(500, 500) although, as I plan to add a few more components, I expect it will be fragile. As you see, in the other two, the "Operate" button overlaps with the ColorSample Component---like it's not following the Layout Manager I set. And then see how pack cuts of the ColorSample Component. Any tips on how I can achieve the effect I want?
LayoutManagers are free to size/position components as they deem appropriate, components cannot force them but only give hints in their getXXSize (XX == min/pref/max) methods. So the best a component implementation can do is
implement all getXXSize and return the size they ideally want
implement paintComponent to cope with a differing size
a snippet only
public class MyBox extends JComponent {
Dimension boxSize;
public void setBoxSize(Dimension box) {
this.boxSize = new Dimension(box);
...
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// position the box in the actual size
// and paint it
}
#Override
public Dimension getPreferredSize() {
return getBoxSize();
}
#Override // same for min/max
public Dimension getM...Size( {
return getBoxSize();
}
}
pack() uses getPreferredSize() of your component. So just return desired size of your rectangle and the size will be used in LayoutManager.