JButton responds in JFrame, but not in a JPanel - java

I'm new to java and I'm trying to figure out how action listeners and buttons work. I've found that I can get a working JButton if I put it directly into my JFrame object. But if I put it in a JPanel within that JFrame, it won't respond. Why is that?
Main.java
public class Main {
private static Frame f = new Frame();
public static void main(String[] args) {}
}
Frame.java
public class Frame extends JFrame {
private final int WIDTH = 640, HEIGHT = 480;
private Panel p = new Panel();
Frame() {
super("Java Program");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(WIDTH, HEIGHT);
this.setLayout(null);
this.setLocationRelativeTo(null);
this.setVisible(true);
}
public void paint(Graphics g) {
super.paint(g);
p.paintComponent(g);
}
}
Panel.java
public class Panel extends JPanel {
JButton b = new JButton("Button");
Panel() {
b.setBounds(0, 0, 200, 100);
add(b);
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
b.setText("Pressed");
}
});
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
b.paint(g);
}
}

I am not a Swing expert so I can't really explain why it does not work. It seems like an unresponsive button is painted on top of you button. I tweaked it a little and here are a few modifications to get it to work:
Add the panel to the Frame: add(p);
Remove the this.setLayout(null); line, it seems to mess up the frame
To set the size of the Frame, use setPreferredSize: this.setPreferredSize(new Dimension(WIDTH, HEIGHT));
You also need to call pack() at then end of your Frame constructor.
And you need to remove b.paint(g) from your Panel.paintComponent(), this seems to be what paints the "unresponsive" button, (see image at the end of the answer).
Optionally, you can remove the paint() from the Frame, it does nothing more than the JFrame's one
Here is a modified working version:
class Frame extends JFrame {
private final int WIDTH = 640, HEIGHT = 480;
private Panel p = new Panel();
Frame() {
super("Java Program");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setPreferredSize(new Dimension(WIDTH, HEIGHT));
this.setLocationRelativeTo(null);
this.setVisible(true);
// add the panel to the frame
add(p);
pack();
}
}
class Panel extends JPanel {
JButton b = new JButton("Button");
Panel() {
b.setBounds(0, 0, 200, 100);
add(b);
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
b.setText("Pressed");
}
});
}
// You can also get rid of this method,
// I just leave it here to show that I removed the b.paint(g) line
public void paintComponent(Graphics g) {
super.paintComponent(g);
}
}
Here is what the same code shows if you leave b.paint(g) in Panel.paintComponent(), as you can see there are 2 buttons, the one in the corner does not work.

Related

java- repaint() method is misbehaving - 2?

This question is an extension of java- repaint() method is misbehaving?
(Reading it, is optional)
I am working on a Music Player
I am using a JSlider as seek bar and using a JLabel to draw text on screen, such as song name.
I am new to Graphics2D
Here's the minimized code:
public class JSliderDemo extends JFrame
{
JLabel label;
JSlider seek = new JSlider();
int y = 10;
public JSliderDemo()
{
setSize(400, 400);
setLocationRelativeTo(null);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
createWindow();
setVisible(true);
startThread();
}
public void createWindow()
{
JPanel panel = new JPanel(new BorderLayout());
panel.setOpaque(true);
panel.setBackground(Color.BLUE);
panel.setBorder(new LineBorder(Color.YELLOW));
JLayeredPane layeredPane = new JLayeredPane();
layeredPane.setPreferredSize(new Dimension(300, 310));
label = new Component();
label.setSize(300, 300);
createSlider();
layeredPane.add(seek, new Integer(50));
layeredPane.add(label, new Integer(100));
panel.add(layeredPane);
add(panel);
}
protected void createSlider()
{
seek.setUI(new SeekBar(seek, 300, 10, new Dimension(20, 20), 5,
Color.DARK_GRAY, Color.RED, Color.RED));
seek.setOrientation(JProgressBar.HORIZONTAL);
seek.setOpaque(false);
seek.setLocation(10, 50);
seek.setSize(300, 20);
seek.setMajorTickSpacing(0);
seek.setMinorTickSpacing(0);
seek.setMinimum(0);
seek.setMaximum(1000);
seek.setBorder(new MatteBorder(5, 5, 5, 5, Color.CYAN));
}
protected void startThread()
{
Thread thread = new Thread(new Runnable(){
#Override
public void run()
{
try
{
while(true)
{
if(y == label.getHeight()){y = 1;}
label.repaint();
y += 1;
Thread.sleep(100);
}
}
catch(Exception ex){}
}
});
thread.start();
}
protected class Component extends JLabel
{
#Override
public void paintComponent(Graphics g)
{
Graphics2D gr = (Graphics2D) g;
gr.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
gr.setColor(Color.RED);
gr.setFont(new Font("Calibri", Font.PLAIN, 16));
gr.drawString("Song Name", 50, y);
gr.dispose();
}
}
public static void main(String[] args)
{
new JSliderDemo();
}
}
The problem is, when I call repaint() for JLabel it automatically repaints JSlider with it even though JSlider is not included in JLabel.
Output :
Slider re-painted
Slider re-painted
Slider re-painted
Slider re-painted
Slider re-painted
Slider re-painted.........
Now if I remove label.repaint() from the Thread, then the JSlider is not re-painted.
Output:
Slider re-painted
Slider re-painted
Is the repaint() method supposed to work like this?
In my last question, I was told to use Layout Manager and when I did use GridLayout just for checking if it's the solution, then it worked!
Only JLabel was repainted.
But I want to overlap JLabel on JSlider, so I thought of using JLayeredPane. And now, the problem is back.
How can I solve this?
Bottom Line : How can I overlap JLabel on JSlider without leading to repaint() method misbehave ?
OR
Does the repaint() method work like this?
As was already mentioned in the comments, the reason for your JSlider being repainted is that it has overlapping bounds with the JLabel. Even though your label doesn't paint over the area of the slider swing will still mark the overlapping area as dirty (i.e. the overlapping part of the slider will need to be repainted) because swing doesn't know that you are only painting in one part of the component.
To reduce the amount of repaints you will need to make the size of your JLabel smaller. Preferably only as large as it needs to be by invoking its getPreferredSize() method. You'll then be able to move the text by moving the location of the label.
Also you shouldn't be doing updates to the gui in a plain Thread. Use javax.swing.Timer instead. It ensures that all updates to the gui happen on the swing event thread, which is where they should be made.
After making these adjustments to your code the slider is only repainted while the label is actually visually over the slider.
public class JSliderDemo extends JFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(JSliderDemo::new);
}
private final JLabel label = new CustomLabel();
public JSliderDemo() {
setSize(400, 400);
setLocationRelativeTo(null);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
createWindow();
setVisible(true);
startTimer();
}
public void createWindow() {
JPanel panel = new JPanel(new BorderLayout());
JLayeredPane layeredPane = new JLayeredPane();
layeredPane.setPreferredSize(new Dimension(300, 310));
label.setLocation(0, 0);
label.setBorder(new LineBorder(Color.RED));
label.setSize(label.getPreferredSize());
layeredPane.add(createSlider(), Integer.valueOf(50));
layeredPane.add(label, Integer.valueOf(100));
panel.add(layeredPane);
setContentPane(panel);
}
protected JSlider createSlider() {
JSlider seek = new CustomSlider();
seek.setOrientation(JProgressBar.HORIZONTAL);
seek.setOpaque(false);
seek.setLocation(10, 50);
seek.setSize(300, 20);
seek.setMajorTickSpacing(0);
seek.setMinorTickSpacing(0);
seek.setMinimum(0);
seek.setMaximum(1000);
seek.setBorder(new LineBorder(Color.BLUE));
return seek;
}
private void startTimer() {
new Timer(100, e -> {
int y = label.getY();
int maxY = label.getParent().getHeight();
if (y == maxY) {
y = -label.getHeight();
}
label.setLocation(label.getX(), y + 1);
label.repaint();
}).start();
}
private static class CustomLabel extends JLabel {
protected CustomLabel() {
setFont(new Font("Calibri", Font.PLAIN, 16));
setText("Song Name");
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("Painting Label");
}
}
protected static class CustomSlider extends JSlider {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("Painting Slider");
}
}
}

ActionListener to draw shapes on separate panel

I want my GUI to draw circles/rectangles on the exact position I coded in the method paintComponent when I click on the respective buttons.
But I just don't know how to go on. What should I tell actionPerformed to do? Trying for a few hours to figure out a way, but I'm only getting errors.
public class Kreise extends JFrame {
Kreise() {
setLayout(new GridLayout(2, 1));
JLabel label = new JLabel("Draw Circ / Rect here: ");
label.setLayout(new FlowLayout(FlowLayout.CENTER));
JPanel jp1 = new JPanel();
jp1.setBackground(Color.LIGHT_GRAY);;
jp1.add(label);
JPanel jp2 = new JPanel(new FlowLayout());
JButton circ = new JButton("Circle");
JButton rect = new JButton("Rectangle");
circ.addActionListener(new KRListener(true));
rect.addActionListener(new KRListener(false));
jp2.add(circ);
jp2.add(rect);
MyPanel obj = new MyPanel();
jp1.add(obj);
add(jp1);
add(jp2);
setSize(400, 250);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
public class MyPanel extends JPanel {
public boolean circleZ = true;
public void paintComponent(Graphics g) {
if (circleZ = true) {
super.paintComponent(g);
g.drawOval(150, 50, 50, 50);
} else if (circleZ = false) {
super.paintComponent(g);
g.drawRect(150, 50, 50, 50);
}
}
}
public class KRListener implements ActionListener {
boolean b;
KRListener(boolean b) {
this.b = b;
}
public void actionPerformed(ActionEvent e) {
?
}
}
public static void main(String[] args) {
new Kreise();
}
}
Presuming I understand the question clearly (you wish to toggle between the a rectangle or circle), in the ActionListener implementation you need to:
Toggle the appropriate boolean value
Call repaint on the JPanel instance that performs the painting
One way to accomplish these steps is to have a single toggle JButton, and pass an instance of the JPanel used for drawing to your ActionListener implementation, which can be used to accomplish both steps above:
public class KRListener implements ActionListener {
private MyPanel panel;
KRListener(MyPanel panel) {
this.panel = panel;
}
#Override
public void actionPerformed(ActionEvent e) {
panel.circleZ = !panel.circleZ;
panel.repaint();
}
}
And when you paint:
if ( circleZ ){
g.drawOval(150, 50, 50, 50);
}else{
g.drawRect(150, 50, 50, 50);
}
I dont know what you are using the global boolean variable b for But I noticed that you have to call the repaint() method when you press the Button.
public class KRListener implements ActionListener {
boolean b;
KRListener(boolean b) {
this.b = b;
}
#Override
public void actionPerformed(ActionEvent e){
//add some code here to change properties of the drawing before calling the repaint method?
repaint();
}
}

No rectangle shown in JPanel

I trying to draw a rectangle in JPanel but the rectangle not showing. What did I missed ?
Here is what I have tried so far.
public class selectSeat extends JFrame {
JPanel panel = new JPanel();
public static void main(String[] args) {
selectSeat frameTabel = new selectSeat("","","");
}
public selectSeat(String title, String day, String time)
{
super("Select Seat");
setSize(350,350);
setLocation(500,280);
panel.setLayout(null);
RectDraw rect= new RectDraw();
panel.add(rect);
getContentPane().add(panel);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
private static class RectDraw extends JPanel
{
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawRect(230,80,10,10);
g.setColor(Color.RED);
g.fillRect(230,80,10,10);
}
public Dimension getPreferredSize() {
return new Dimension(50, 20); // appropriate constants
}
}
}
You're drawing the rectangle, but it's located at 280, 80, which is way outside the confines of your visible JPanel. Understand that the drawing location is relative to the coordinates within the JPanel itself.
Noticed that you are using Absolute layout (null layout). Component.setbounds are needed in order to position the object in place.
public Test(String title, String day, String time)
{
super("Select Seat");
setSize(350,350);
setLocation(500,280);
panel.setLayout(null);
RectDraw rect= new RectDraw();
rect.setBounds(0, 0, 100, 100);
panel.add(rect);
getContentPane().add(panel);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
Check out the details:
https://docs.oracle.com/javase/tutorial/uiswing/layout/problems.html
Note: Try Ctrl+Shift+F1 to get debug message from AWT as well.

Why isn't a painting drawn to the screen?

I have this simple code which is supposed to have three panels and draws and oval at the top left corner of each panel
public class main1 extends JPanel {
public main1() {
// TODO Auto-generated constructor stub
this.setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
JPanel1 panel1 = new JPanel1(Color.YELLOW);
panel1.setBackground(Color.black);
JPanel1 panel2 = new JPanel1(Color.red);
panel2.setBackground(Color.blue);
JPanel1 panel3 = new JPanel1(Color.pink);
panel3.setBackground(Color.green);
this.add(panel1);
this.add(panel2);
this.add(panel3);
}
class JPanel1 extends JPanel{
Color c;
public JPanel1(Color c) {
this.c = c;
}
public void paintComponent(Graphics g){
super.paintComponent(g);
System.out.println(this.getBounds().x);
g.setColor(c);
g.drawOval(this.getBounds().x, this.getBounds().y, 200, 200);
}
}
public static void main(String args[]) {
JFrame f = new JFrame("Two Panels");
f.setContentPane(new main1());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(300, 300);
f.setVisible(true);
}
}
however, it only seems to draw the first oval of the first panel and ignores the rest.
can somebody explain. What am I doing wrong?
Do not use getBounds() as it gives the component location relative to its parent. Use panel's coordinates and its width and height instead. In your example you are painting outside the boundaries of the panels. For example use this to draw an oval:
g.drawOval(0, 0, getWidth(), getHeight());
Some side notes:
Do not call setSize(), override panel's getPreferredSize() and pack() the frame. For example:
public Dimension getPreferredSize(){return new Dimension(400, 400);}
Then, add frame.pack(); before making the frame visible.
See Java Naming Conventions.
See Performing Custom Painting and Painting in AWT and Swing for more information.

Why are my panels not showing up in the JFrame

public class InputPanel extends JPanel{
public static int shapeType; //1: Rectangle; 2: Oval; 3: Line
public static boolean isFilled; //whether or not the shape is filled
public static Color color; //color of the shape
public InputPanel(){
JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
panel.setBackground(Color.GRAY);
setPreferredSize(new Dimension(200,500));
JButton rect = new JButton("Rectangle");
JButton oval = new JButton("Oval");
JButton line = new JButton("Line");
JRadioButton fill = new JRadioButton("Filled:");
JButton color1 = new JButton("Color..");
rect.addActionListener(new rectListener());
oval.addActionListener(new ovalListener());
line.addActionListener(new lineListener());
isFilled = fill.isSelected();
color1.addActionListener(new colorListener());
panel.add(rect);
panel.add(oval);
panel.add(line);
panel.add(fill);
panel.add(color1);
this.setVisible(true);
}
}
public class PaintPanel extends JPanel{
public static int x1, y1, x2, y2;
ArrayList<Shape> shapeList = new ArrayList<Shape>();
public PaintPanel(){
JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
panel.setBackground(Color.WHITE);
setPreferredSize(new Dimension(500, 500));
this.addMouseListener(new MouseAdapter() {
#Override public void mousePressed(MouseEvent e) {
PaintPanel.x1 = e.getX();
PaintPanel.y1 = e.getY();
}
#Override public void mouseReleased(MouseEvent e) {
PaintPanel.x2 = e.getX();
PaintPanel.y2 = e.getY();
if(InputPanel.shapeType == 1){
shapeList.add(new Rectangle(PaintPanel.x1, PaintPanel.y1, PaintPanel.x2, PaintPanel.y2, InputPanel.isFilled));
}
if(InputPanel.shapeType == 2){
shapeList.add(new Oval(PaintPanel.x1, PaintPanel.y1, PaintPanel.x2, PaintPanel.y2, InputPanel.isFilled));
}
if(InputPanel.shapeType == 3){
shapeList.add(new Line(PaintPanel.x1, PaintPanel.y1, PaintPanel.x2, PaintPanel.y2));
}
repaint();
}
});
this.setVisible(true);
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
for(Shape s : shapeList){
s.draw(g);
}
}
}
public class PaintGUI {
public static void main(String[] args){
JFrame frame = new JFrame("Shape Drawer!");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new InputPanel());
frame.add(new PaintPanel());
frame.pack();
frame.setVisible(true);
}
}
I'm positive I've created the JFrame properly and all of my other classes work, but there must be something in here I'm missing...
When I run the main method all I get is a gray box that is clearly a square (500x500, as instantiated in the PaintPanel class. What am I doing wrong?
Apart from what Andrew mentioned, I noticed that within both your InputPanel and PaintPanel you're creating a new JPanel. You're adding new components to this panel, for sure, but at the end you're not adding this JPanel itself to your InputPanel or PaintPanel. So, make sure that in your constructors for these panels you have a add(panel) at the end.
Also, as a side note, do please keep in mind that most operations in Swing are not thread-safe and so read about "Concurrency in Swing" before creating/interacting with UI components. In other words, any updates to the user interface must happen on the event dispatch thread, like the start-up of your application:
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Shape Drawer!");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//set the layout, add your panels
frame.pack();
frame.setVisible(true);
}
});
}
JFrame by default uses BorderLayout.
frame.add(new InputPanel());
frame.add(new PaintPanel());
is equivalent to saying,
frame.add(new InputPanel(), BorderLayout.CENTER);
frame.add(new PaintPanel(), BorderLayout.CENTER);
The net result being that the Panel that is added last would be the one that is visible, provided the rest of your code is working correctly.
Must add the panel to the frame, use:
this.add(panel);

Categories