The Student Room Group

Java GUI Button Issue

The chances of me getting any sort of response here is slim, even stack overflow couldn't help me , plus I can hardly explain my program as I'm on the brink of mental breakdown... but anyway:

In short I've written a program with a GUI in probably the worst way possible, I have 1 single panel which I've drawn a white box over using a paint method, and then in the same paint method I've drawn every component of the GUI, including a network and table, all line by line.

Anyway it all looks really nice but I've run into an issue because now I want to add a button onto this 'white box' GUI I've created, but it appears since I haven't used BorderLayout and created separate panels for separate things I can't. I really want to know if there's a way around my problem.

Here's a screenshot of my current GUI displaying where I want to add the buttons:



This probably makes no sense, probably because I haven't a clue what the heck I'm doing
It looks like TSR has gobbled up your screenshot, so I can't really tell what you're trying to do. (Guessing you're using Java Swing for your UI, since you mentioned BorderLayout..)

Am I right in thinking that you've got a class inherited from JPanel, and you're using paintComponent to draw lines and other things directly on top of that?

Are you certain that you're not using a layout manager? i.e. did you call JPanel.setLayout(null)?

This will allow you to use absolute positioning from your top-left corner in order to place components like buttons.

Here's a quick and dirty example:

MyPanel.java


import javax.swing.*;
import java.awt.*;

public class MyPanel extends JPanel {
// Buttons
JButton btnHello = new JButton( "Hello" );

public MyPanel() {
this.setLayout(null);
btnHello.setBounds(200, 200, 200, 200);
this.add(btnHello);
}

@Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}

@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int width = getWidth() - 100;
int height = getHeight() - 100;
int x = (getWidth() - width) / 2;
int y = (getHeight() - height) / 2;
g2d.setColor(Color.RED);
g2d.drawRect(x, y, width, height);
g2d.dispose();
}
}


Test.java


import javax.swing.*;

public class Test {
/**
* Create the GUI and show it. For thread safety,
* this method should be invoked from the
* event-dispatching thread.
*/
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame( "HelloWorldSwing" );
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

//Add the ubiquitous "Hello World" label.
MyPanel myPanel = new MyPanel();
frame.getContentPane().add(myPanel);

//Display the window.
frame.pack();
frame.setVisible(true);
}

public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}


Is this what you're trying to do?
Oh, I can see the screenshot now! TSR is weird sometimes!

Anyway, once you've killed off the default LayoutManager that comes with a JPanel, you can use setBounds on any component to specify a position relative to that JPanel's top-left corner.
(edited 6 years ago)
Original post by winterscoming
Oh, I can see the screenshot now! TSR is weird sometimes!

Anyway, once you've killed off the default LayoutManager that comes with a JPanel, you can use setBounds on any component to specify a position relative to that JPanel's top-left corner.


TSR has a habit of acting up! I've tried absolute positioning once as it seems to be what my project leads to. However as soon as I set layout to null I get a NullPointerError come up on the paint method I'm using and It's absolutely beyond me why that's happening. Then of course because that happens nothing paints and I have a tiny empty panel display to screen.

EDIT: Actually that's a lie if I simply set it to null I don't get the error but I just get a tiny black panel with nothing painted on it when I run the program.
(edited 6 years ago)
Original post by EmilySarah00
TSR has a habit of acting up! I've tried absolute positioning once as it seems to be what my project leads to. However as soon as I set layout to null I get a NullPointerError come up on the paint method I'm using and It's absolutely beyond me why that's happening. Then of course because that happens nothing paints and I have a tiny empty panel display to screen.


That NullPointerError will most likely be caused by something in your program trying to use the LayoutManager, but if all you're doing is drawing lines in paintComponent that would seem odd. Does the NullPointerError give you a useful stack trace which identifies the place where this is happening? (That may of course be a wild goose-chase, because the exception might end up being triggered from somewhere in Swing itself and not directly from your code. but it's worth checking if you haven't already.)

There might be a better approach, because absolute positioning is a bit hacky anyway! Could you add the buttons to your "outermost" JFrame layout instead, using NORTH and SOUTH?

I'll use the same thing that I just knocked together above.. there's really just one really minor "tweak" in the class which creates the JFrame.

MyPanel.java (Now sans buttons!)

import javax.swing.*;
import java.awt.*;

public class MyPanel extends JPanel {

@Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}

@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int width = getWidth() - 100;
int height = getHeight() - 100;
int x = (getWidth() - width) / 2;
int y = (getHeight() - height) / 2;
g2d.setColor(Color.RED);
g2d.drawRect(x, y, width, height);
g2d.dispose();
}
}



MyButtonPanel.java (All new panel with buttons)

import javax.swing.*;
import java.awt.*;

public class MyButtonPanel extends JPanel {
// Buttons
JButton btnHello = new JButton("Hello");
JButton btnWorld = new JButton("World");

public MyButtonPanel() {
this.setLayout(new FlowLayout());
this.add(btnHello);
this.add(btnWorld);
}
}


Test.java (updated to use the NORTH/SOUTH panes in its default layout manager)

import javax.swing.*;
import java.awt.*;

public class Test {
private static void createAndShowGUI() {
JFrame frame = new JFrame("HelloWorldSwing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

MyPanel myPanel = new MyPanel();
frame.add(myPanel, BorderLayout.NORTH);

MyButtonPanel myButtonPanel = new MyButtonPanel();
frame.add(myButtonPanel);

frame.pack();
frame.setVisible(true);
}

public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}


I'd still be really curious as to why you're getting the Null Pointer problem, but sometimes it's best not to waste the time and try something different.
Original post by winterscoming
That NullPointerError will most likely be caused by something in your program trying to use the LayoutManager, but if all you're doing is drawing lines in paintComponent that would seem odd. Does the NullPointerError give you a useful stack trace which identifies the place where this is happening? (That may of course be a wild goose-chase, because the exception might end up being triggered from somewhere in Swing itself and not directly from your code. but it's worth checking if you haven't already.)

There might be a better approach, because absolute positioning is a bit hacky anyway! Could you add the buttons to your "outermost" JFrame layout instead, using NORTH and SOUTH?

I'll use the same thing that I just knocked together above.. there's really just one really minor "tweak" in the class which creates the JFrame.

MyPanel.java (Now sans buttons!)


import javax.swing.*;
import java.awt.*;

public class MyPanel extends JPanel {

@Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}

@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int width = getWidth() - 100;
int height = getHeight() - 100;
int x = (getWidth() - width) / 2;
int y = (getHeight() - height) / 2;
g2d.setColor(Color.RED);
g2d.drawRect(x, y, width, height);
g2d.dispose();
}
}




MyButtonPanel.java (All new panel with buttons)


import javax.swing.*;
import java.awt.*;

public class MyButtonPanel extends JPanel {
// Buttons
JButton btnHello = new JButton("Hello":wink:;
JButton btnWorld = new JButton("World":wink:;

public MyButtonPanel() {
this.setLayout(new FlowLayout());
this.add(btnHello);
this.add(btnWorld);
}
}



Test.java (updated to use the NORTH/SOUTH panes in its default layout manager)


import javax.swing.*;
import java.awt.*;

public class Test {
private static void createAndShowGUI() {
JFrame frame = new JFrame("HelloWorldSwing":wink:;
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

MyPanel myPanel = new MyPanel();
frame.add(myPanel, BorderLayout.NORTH);

MyButtonPanel myButtonPanel = new MyButtonPanel();
frame.add(myButtonPanel);

frame.pack();
frame.setVisible(true);
}

public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}



I'd still be really curious as to why you're getting the Null Pointer problem, but sometimes it's best not to waste the time and try something different.


I managed to navigate my way around the null pointer problem I think? I'll let you know if I manage to make the whole thing work. Thank you very much for the help! I used you first example to manage to try something.
@winterscoming Scratch that I still get the null pointer error as soon as I add a button :facepalm:
It highlights the line of code 'super.paintComponent(g)'
and it simply says:
'null (in javax.swing.jComponent)'

I very much want to tear my hair out about now.
Original post by EmilySarah00
@winterscoming Scratch that I still get the null pointer error as soon as I add a button :facepalm:
It highlights the line of code 'super.paintComponent(g)'
and it simply says:
'null (in javax.swing.jComponent)'

I very much want to tear my hair out about now.


Argh! Are you calling super.paintComponent(g); anywhere else aside from the first line within your MyPanel.paintComponent method body?

I'm guessing you've got places in your code which redraw those lines when your Dijkstra graph updates. But if you're calling super.paintComponent(g) to redraw your lines, then that could be the reason. Instead, could you try using this.repaint()? (telling the panel to repaint itself)
(edited 6 years ago)
Original post by winterscoming
Argh! Are you calling super.paintComponent(g); anywhere else aside from the first line within your MyPanel.paintComponent method body?

I'm guessing you've got places in your code which redraw those lines when your Dijkstra graph updates. But if you're calling super.paintComponent(g) to redraw your lines, then that could be the reason. Instead, could you try using this.repaint()?


I'm actually not, that's the only place that line is written, repaint however wasn't working when I was programming so I resorted to cheating and calling the paint methods again from within the algorithm and using getGraphics to make them work. This could be causing the issue I guess but I don't know how..
Original post by EmilySarah00
I'm actually not, that's the only place that line is written, repaint however wasn't working when I was programming so I resorted to cheating and calling the paint methods again from within the algorithm and using getGraphics to make them work. This could be causing the issue I guess but I don't know how..


Right, that makes a lot of sense - I expect the problem you're having is that the algorithm is using an invalid Graphics instance (The graphics instance is basically a "temporary" object, so if you try to keep hold of it and use it later on, then this kind of thing can happen)

I seem to end up getting a lot of problems whenever I try to call 'paintComponent' directly. It's strange that 'repaint' wasn't working though. I always find the whole paint/repaint thing in Swing is a bit weird to be honest.

Do you think it'd be easy to add the buttons to a separate JPanel like the second example, and put those into the SOUTH part of the JFrame layout? I can't think of any reason why that would need to be a big change anyway :smile:

Of course, doing that you'd probably need the Button JPanel to have a reference to the main JPanel, but you could connect them together with a 'set' method:

// Test.Java - createAndShowGUI method:





MyPanel myPanel = new MyPanel();
frame.add(myPanel, BorderLayout.NORTH);

MyButtonPanel myButtonPanel = new MyButtonPanel();
frame.add(myButtonPanel);

myButtonPanel.setMainPanel(myPanel);





It sounds like you could end up digging endlessly deeper down into a rabbit hole with the null pointer and repaint problem,
(edited 6 years ago)
Original post by winterscoming
Right, that makes a lot of sense - I expect the problem you're having is that the algorithm is using an invalid Graphics instance - I seem to end up getting a lot of problems whenever I try to call 'paintComponent' directly. It's strange that 'repaint' wasn't working though. I always find the whole paint/repaint thing in Swing is a bit weird to be honest.

Do you think it'd be easy to add the buttons to a separate JPanel like the second example, and put those into the SOUTH part of the JFrame layout? I can't think of any reason why that would need to be a big change anyway :smile:

Of course, doing that you'd probably need the Button JPanel to have a reference to the main JPanel, but you could connect them together with a 'set' method:

// Test.Java - createAndShowGUI method:





MyPanel myPanel = new MyPanel();
frame.add(myPanel, BorderLayout.NORTH);

MyButtonPanel myButtonPanel = new MyButtonPanel();
frame.add(myButtonPanel);

myButtonPanel.setMainPanel(myPanel);





It sounds like you could end up digging endlessly deeper down into a rabbit hole with the null pointer and repaint problem,


I've also tried that, I tried just adding a panel to the south purely for buttons and again that gave me a null pointer error. At this point I think I have to give up since because this is my school coursework project I only have like 2 weeks to get the whole thing done.

I considered using a key press as a replacement for a button, since all I want the button to do is make the program move on to the next step through the algorithm. But a key press would involve writing a key listener which is something I've never done before and I'm not sure I can get my head around it, and especially how I'd implement it in to my already really long and complex program.

I'm already definitely way in to that rabbit hole of errors.
Original post by EmilySarah00
I've also tried that, I tried just adding a panel to the south purely for buttons and again that gave me a null pointer error. At this point I think I have to give up since because this is my school coursework project I only have like 2 weeks to get the whole thing done.

I considered using a key press as a replacement for a button, since all I want the button to do is make the program move on to the next step through the algorithm. But a key press would involve writing a key listener which is something I've never done before and I'm not sure I can get my head around it, and especially how I'd implement it in to my already really long and complex program.

I'm already definitely way in to that rabbit hole of errors.
Never give up! :smile:

Key presses sound like a good idea, but you don't need to use the KeyListener (actually, KeyListener is another "weird" bit of Java Swing which is better to avoid if you can, because it won't work unless the component has focus. The easiest thing is just to avoid it!)

Anyway, a better way to do keyboard events is a Key Binding, which uses a bit more code than a Key Listener, but should be fairly simple anyway -
https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html

Paraphrasing that link, you need 3 things:

1.

An Alias (a name) linked to a keypress event. (Call it whatever you like really, ideally something sensible related to the name of the keypress - e.g. if you're linking it to the Left-arrow then you could call it "Left" - basically just a string).

2.

An Action - Which is just an object that has a method. That can just be an instance of an anonymous class with a method doing whatever your button would have done. Java has a type called AbstractAction for this.

3.

An entry in the JPanel's "Action map" - The action map is a similar idea to a HashMap belonging to the JPanel, containing action objects. The alias string is used as an index-key for the actions in the map.



So, if you've added an action to the action map using "Left" as the index-key, then whenever the keypress event happens, whose alias is "Left", the action gets called.

Sorry, that's probably not very well explained. It's probably easier to show code. I've updated the previous example again, this time using a Key Binding which just toggles the colour of the rectangle whenever you press the space bar. Maybe this'll finally do the trick?


import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;

public class MyPanel extends JPanel {

// The border colour
Color colour = Color.RED;

private static final String SPACE = "Space";
private Action space = new AbstractAction(SPACE) {
@Override
public void actionPerformed(ActionEvent e) {
MyPanel.this.colour =
MyPanel.this.colour == Color.RED
? Color.BLUE
: Color.RED;
MyPanel.this.repaint();
}
};

public MyPanel(){
this.getInputMap().put(
KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), SPACE);
this.getActionMap().put(SPACE, space);
}

@Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}

@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int width = getWidth() - 100;
int height = getHeight() - 100;
int x = (getWidth() - width) / 2;
int y = (getHeight() - height) / 2;
g2d.setColor(this.colour);
g2d.fillRect(x, y, width, height);
g2d.dispose();
}
}


The Action might look strange to you if you haven't seen anonymous classes before - it's just a plain old class without a name, but it extends the AbstractAction base class, and overrides the actionPerformed method with the button press logic.

The other oddity about the anonymous class is that you need to use MyPanel.this. in order to actually get it to use the JPanel methods. That's because the anonymous class is a class itself, "nested" inside MyPanel.
(edited 6 years ago)

Quick Reply

Latest

Trending

Trending