Why i need to click twice to let my JButton work? - java

I'm building a program that draws random (User input) rectangles on a JPanel.
Problem 1:
Whenever I type a number in my JTextfield,I need to click twice on
the JBUtton for Rectangles to show up.
Problem 2:
When i type a new number in the JTextField the number of that don't
show the rectangle but it shows the rectangles I typed in previous.
CODE:
private void init() {
final int FRAME_WIDHT = 800;
final int FRAME_HEIGHT = 1000;
int input = 3;
JFrame frame = new JFrame();
frame.setSize(FRAME_WIDHT, FRAME_HEIGHT);
frame.setTitle("Frame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
west = new JPanel();
west.setSize(500, 500);
west.setBorder(BorderFactory.createLineBorder(Color.black));
east = new JPanel();
east.setSize(300, 1000);
button = new JButton("Add squares");
field = new JTextField(10);
button.setSize(100, 50);
east.add(button);
east.add(field);
east.setBorder(BorderFactory.createLineBorder(Color.black));
button.addActionListener(new java.awt.event.ActionListener() {
#Override
public void actionPerformed(java.awt.event.ActionEvent evt) {
JButton1ActionPerformed(evt);
}
public void JButton1ActionPerformed(ActionEvent evt) {
int aantalRect = Integer.parseInt(field.getText());
MyDrawing draw = new MyDrawing(aantalRect);
west.add(draw);
draw.revalidate();
draw.repaint();
}
});
frame.add(west, BorderLayout.CENTER);
frame.add(east, BorderLayout.EAST);
frame.setResizable(true);
frame.setVisible(true);
}
public static void main(String[] a) {
P1027 form = new P1027();
}
}
class MyDrawing extends JPanel {
int input = 0;
public MyDrawing(int i) {
this.input = i;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Random r = new Random();
setPreferredSize(new Dimension(500, 1000));
for (int i = 0; i < input; i++) {
int x = r.nextInt(460);
int y = r.nextInt(960);
g.drawRect(x, y, 40, 40);
}
}
Can any one tell me how to fix that?

Problem 1: You're not be seeing the squares being drawn on your MyDrawing JPanel the first time because you are calling the setPreferredSize(...) method, when you really should be overriding the getPreferredSize() method as explained by this answer. It is also possible that they are being drawn off-screen. You set the preferred height of MyDrawing to 1000, which doesn't fit on my laptop's screen (The green line is the border of a MyDrawing).
To fix Problem 1, override the method and lower the preferred height if necessary:
class MyDrawing extends JPanel {
... //Constructor
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500); //Changed from 1000 to 500
}
... //paintComponent(...)
//If you change 1000 to 500, don't forget to change 960 to 460 too
}
Problem 2: You're seeing the amount of rectangles you typed into the JTextField previously because:
You are forgetting to remove the previously added MyDrawing from west before adding the new one.
You are calling revalidate() and repaint() on draw when you should be calling it on its parent component, west.
To fix problem 2, remove the old MyDrawing from west, add the new one, then call revalidate() and repaint():
...
public void JButton1ActionPerformed(ActionEvent evt) {
west.removeAll(); //If the old MyDrawing is the only thing
//that has been added to west. Otherwise use
//remove(int index) or remove(Component comp)
west.add(draw);
west.revalidate();
west.repaint();
}
...
Other things:
You switched the T and H around in FRAME_WIDTH.
You could put the code in JButton1ActionPerformed(...) into the actual actionPerformed method.
Your JFrame looks exactly the same with and without the calls to setSize(...) on west, east, and button and that answer I mentioned earlier suggests not using those methods, so consider removing them.

Related

Making Multi-Coloured Buttons in Java

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)

JPanel dropdown menu cut off

I'm practicing Java by creating a text analyzing program (word count, TF-IDF, etc).
I'm now constructing a small GUI to be displayed at the top of the frame. I want a dropdown menu, and I'm trying to create one using a JPanel and several JButton components. But to make the dropdown reusable, I've been trying to construct a class to manage it.
However, I encountered a problem: when I try to create the dropdown menu it's only visible in the upper left corner of the frame, and if I try to move it, it gets cut off or disappears completely.
I tried creating a simple method to do the same thing, but the problem remains the same. Here's the relevant code (ignore the processor and State stuff):
public class ExperimentState extends State {
private JFrame frame;
public ExperimentState(Processor processor) {
super(processor);
initialize();
}
private void initialize() {
int width, height;
//these variables are temporary, for testing
width = 800;
height = 640;
//Set up the frame
frame = new JFrame();
frame.setSize(width, height);
frame.getContentPane().setLayout(null);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Test code
String[] buttonNames = { "42", "Don't panic!", "Towel"};
// x, y, width, height, button names
JPanel panel = createPanel(0, 0, 100, 25, buttonNames);
frame.getContentPane().add(panel);
}
public JPanel createPanel(int x, int y, int width, int height, String[] names) {
//Create and setup the panel
JPanel panel = new JPanel();
panel.setLayout(null);
panel.setBounds(x, y, width, height);
JButton[] buttons = new JButton[names.length];
for(int i = 0; i < names.length; i++) {
JButton button = new JButton(names[i]);
button.setBounds(x, y + height*i, width, height); //each button has the size of the panel, and are placed on top of each other
panel.add(button);
buttons[i] = button;
}
buttons[0].addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
panel.setBounds(x, y, width, height*names.length); //the panel extends to reveal all the buttons
}
public void mouseExited(MouseEvent e) {
panel.setBounds(x, y, width, height); //the panel retracts
}
});
return panel;
}
#Override
public void display() {
frame.setVisible(true);
}
#Override
public void hide() {
frame.setVisible(false);
}
}
I thought there would be a problem with the mouse listener, since I don't really know how they work and thought they wouldn't be able to access the x, y, width and height variables anytime they were called, but there was no problem.
The problem appears when I call createPanel() with something like createPanel(50, 0, 100, 25, buttonNames).
Here, x is 50 instead of 0. When displayed, the panel is moved to the right, as it should be, but its buttons are now cut in half by some invisible line. If x = 100, it will go completely invisible.
Why is this happening? What am I missing?
Components inside a Container will only completely show (without a LayoutManager) if the bounds of these Components are completely inside it. By changing the x-coordinate from 0 to 50 you move the location of the Button. If you do not change its size or the size of your container your Button will be cut off.
The easiest way to fix this problem is to replace x and y in the location of your button with 0 and replace the 0‘s in the location of the Panel with x and y.
EDIT: Similar Problem here: Components not showing in second JPanel

JFrame pack() and requestFocus() is not working

My main issue is with the following piece of code when setting up a JFrame:
Why the panel doesn't show if I use the pack() and how to make it work?
Why the first requestFocusInWindow() doesn't work and what the principle to use it?
Why the default layout manager of JPanel doesn't work if I delete the setLayout()?
public class SoundGUI extends KeyAdapter{
public static void main(String[] args) {
SoundGUI sGUI = new SoundGUI();
sGUI.setUp();
}
public void setUp () {
JFrame frame = new JFrame ("Key test");
frame.setSize (1000, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible (true);
Panel p = new Panel ();
p.setLayout(new BorderLayout());//why this sentence is necessary FlowLayout doesn't fill the container but rather lets components size to their preferredSizes.
p.addKeyListener (this);
p.requestFocusInWindow();//it's useless here
//requestFocus only works on focusable components that are displayed.
MyDrawPanel dp = new MyDrawPanel();
dp.setBackground(Color.darkGray);
JLabel test = new JLabel("a trial");
JButton t = new JButton("b");
dp.add(t);
dp.add (test);
p.add (dp);
frame.getContentPane().add(p);
p.requestFocusInWindow();
//frame.pack();//why it doesn't work
//frame.setVisible(true);
}
class MyDrawPanel extends JPanel {
public void paintComponent (Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.white);
for (int i = 0; i < 1000; i += 42) {
g2.fill3DRect(i,100 ,20 ,80 ,true);
}
g2.setColor(Color.black);
for (int i = 21; i < 1000; i += 42) {
g2.fill3DRect(i,100 ,20 ,80 ,true);
}
}
}
}
Suggestions:
Call setVisible(true) after calling pack(). Makes sense, doesn't it?
The BorderLayout will tell the MyDrawPanel to fill the p container, since the component is being added in a default way (meaning BorderLayout.CENTER), and that's how BorderLayout works.
FlowLayout doesn't fill the container but rather lets components size to their preferredSizes.
Don't mix Swing with AWT components. i.e., don't use Panels, but rather use JPanels.
requestFocus only works on focusable components that are displayed.
Better to use Key Bindings than KeyListeners.
Better to avoid setting the sizes of anything if possible.
Based on your new code, your problem is due to you're calling setSize(). Most layout managers don't respect this but rather the preferred size. If your drawing JPanel needs to be so big, then make it so. For example try:
class MyDrawPanel extends JPanel {
private static final int PREF_W = 1000;
private static final int PREF_H = 300;
public void paintComponent(Graphics g) {
super.paintComponent(g); //!! ******** don't forget this!!! *********
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.white);
for (int i = 0; i < 1000; i += 42) {
g2.fill3DRect(i, 100, 20, 80, true);
}
g2.setColor(Color.black);
for (int i = 21; i < 1000; i += 42) {
g2.fill3DRect(i, 100, 20, 80, true);
}
}
// the getPReferredSize will make this JPanel preferentially be this size
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
}
Also note that request focus does work if the component is focusable:
JPanel p = new JPanel(); //!! This should be a JPanel, not a Panel
p.setFocusable(true); //!! This is needed
p.setLayout(new BorderLayout());
p.addKeyListener(this);
p.requestFocusInWindow();
But also note that KeyListeners should be avoided. Use Key Bindings instead.

Drawing multiple images using paintComponent()

I would like to display more than one image on the screen in the same JPanel.
A for loop iterates over each element in the array and displays their corresponding image, but only seems to keep the last image.
The code:
import javax.swing.*;
import java.awt.*;
import java.io.*;
import javax.imageio.ImageIO;
public class GameGUI extends JFrame{
JPanel mainPanel = new JPanel();
JPanel buttonPanel = new JPanel();
int arrayLength;
public GameGUI() {
super("Gameplay");
//Set size of the frame.
setSize(650, 580);
//Location inside frame.
setLocation(10, 8);
SwingUtilities.isEventDispatchThread();
The methods that contain each individual panel:
createMainPanel();
createCentrePanel();
createNorthPanel();
createSouthPanel();
createWestPanel();
createEastPanel();
setVisible(true);
}
//creating panels
public void createMainPanel() {
//here is the main panel which the others will be nested in.
mainPanel.setLayout(new BorderLayout());
mainPanel.setBackground(Color.red);
add(mainPanel);
}
public boolean createCentrePanel() {
JPanel CENTRE = new JPanel(new BorderLayout());
CENTRE.setBackground(Color.WHITE);
CENTRE.setBorder(BorderFactory.createLineBorder(Color.black));
mainPanel.add(CENTRE, BorderLayout.CENTER);
return true;
}
This is the panel which i am using to print multiple images to the panel.
As you can see I have a for loop which is going through each item in the array and passing the value to the draw component. However it only seems to keep the last image on the screen eventhough each item in the array is being passed to it.
I have tried using repaint but it doesn't seem to work:
public boolean createNorthPanel() {
int[] array = {1, 8, 9, 10};
arrayLength = array.length;
int size = 0;
JPanel NORTH = new JPanel(new BorderLayout());
NORTH.setPreferredSize(new Dimension(100, 100));
NORTH.setBorder(BorderFactory.createLineBorder(Color.black));
mainPanel.add(NORTH, BorderLayout.NORTH);
for (int i = 0; i < arrayLength; i++) {
NORTH.add(new drawPanel(array[i], size, arrayLength));
size = size + 30;
//repaint();
}
return true;
}
public boolean createSouthPanel() {
JPanel SOUTH = new JPanel(new BorderLayout());
SOUTH.setPreferredSize(new Dimension(100, 100));
// SOUTH.add(new drawPanel(2, 0));
// SOUTH.add(new drawPanel(5, 30));
SOUTH.setBorder(BorderFactory.createLineBorder(Color.black));
mainPanel.add(SOUTH, BorderLayout.SOUTH);
SOUTH.add(buttonPanel, BorderLayout.EAST);
return true;
}
public boolean createWestPanel() {
JPanel WEST = new JPanel(new BorderLayout());
WEST.setPreferredSize(new Dimension(150, 100));
//WEST.add(new drawPanel(8, 0));
WEST.setBorder(BorderFactory.createLineBorder(Color.black));
mainPanel.add(WEST, BorderLayout.WEST);
return true;
}
public boolean createEastPanel() {
JPanel EAST = new JPanel(new BorderLayout());
EAST.setPreferredSize(new Dimension(150, 100));
EAST.setBorder(BorderFactory.createLineBorder(Color.black));
mainPanel.add(EAST, BorderLayout.EAST);
//EAST.add(new drawPanel(2, 0));
//EAST.add(new drawPanel(7, 60));
return true;
}
public static void main(String args[]){
new GameGUI();
}
}
Here is my class which draws the images on the screen
class drawPanel extends JPanel {
Image image = null;
int xPos;
public drawPanel(int x, int y, int length) {
xPos = y;
try {
File location = new File("src/Card_images/" + x + ".png");
image = ImageIO.read(location);
} catch (IOException e) {
System.out.println("Error: " + e);
}
}
/*public Dimension getPreferredSize() {
return new Dimension(71, 96);
}*/
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//draws image to screen at positions displayed
g.drawImage(image, xPos, 0, this);
}
}
You only have a single drawImage() statement that is executed in you paintCompent() method so you only ever see the last image drawn.
See Custom Painting Approaches for two different ways to draw multiple objects. You will obviously need to customize for your requirements, but the basic concepts will be the same.
Edit:
The above does not apply to your question, but is still good to know when you do need to do some custom painting.
Sorry, because of the custom painting I misread your question. You are making the code too complex.
The first problem is that you changed the layout manager of the "north" panel to a BorderLayout. You can only add a single component to any location of a BorderLayout. So that is why the last component added gets painted. Just use the default FlowLayout for the panel. Although your code still won't work because your components don't have a preferred size.
So the solution to your problem is:
a) create a panel using a FlowLayout
b) Use a JLabel to display your images. There is no need to do custom painting!. Add the labels to the panel, then add this panel to your frame.
Now the layout manager can do its job and you don't need to worry about the details.
Also, use standard Java naming conventions. Your code is too hard to read because you don't follow the standards.
NORTH is not a proper variable name. It should be "north". An upper cased name indicates a final static variable.
use proper class names. Classes should start with an upper case character. "drawPanel" should be "DrawPanel".

Creating a Panel of Button GUI

I'm trying to create a board for a game, i first made a frame then if the user //enters the rows and columns as numbers and pushes the start button, it should remove all //whats on frame and add a panel with a grid layout having buttons everywhere
Here is the code ( Problem is the frame gets cleared and nothing appears)
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class Frame extends JFrame implements ActionListener{
private static final long serialVersionUID = 1L;
JButton newButton;
JButton Start;
JTextArea row;
JTextArea col;
JLabel background;
JLabel rows;
JLabel columns;
JLabel Error;
JPanel myPanel;
JCheckBox box;
public Frame()
{
//adding frame
setTitle("DVONN Game");
setSize(1000, 700);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(null);
//making start button
Start = new JButton(new ImageIcon("Start"));
Start.setBounds(500, 30, 300, 300);
Start.setOpaque(true);
Start.addActionListener(this);
//make background
background = new JLabel();
background.setBounds(0, -300, 2000, 1500);
background.setIcon(Color.BLUE));
rows = new JLabel("Enter the rows");
columns = new JLabel("Enter the columns");
rows.setForeground(Color.WHITE);
columns.setForeground(Color.WHITE);
rows.setBounds(10,10,100,30);
columns.setBounds(10,45,105,30);
row = new JTextArea();
col = new JTextArea();
row.setBounds(120,10,100,30);
col.setBounds(120,45,100,30);
Error = new JLabel("Enter numbers plz!");
Error.setBounds(10, 100, 400, 30);
Error.setForeground(Color.RED);
Error.setVisible(true);
box = new JCheckBox("Enable Random Filling");
box.setBounds(10, 200, 150, 20);
box.setVisible(true);
myPanel = new JPanel();
myPanel.setBounds(30, 30, 700, 500);
myPanel.setVisible(true);
newButton = new JButton();
newButton.setOpaque(true);
getContentPane().add(box);
getContentPane().add(rows);
getContentPane().add(columns);
getContentPane().add(row);
getContentPane().add(col);
getContentPane().add(Start);
getContentPane().add(background);
this.validate();
this.repaint();
}
public static void main(String[]args)
{
new Frame();
}
//adding actions for start button
public void actionPerformed(ActionEvent e) {
boolean flag = true;
String r1 = row.getText();
String c1 = col.getText();
int x = 0,y = 0;
try{
x = Integer.parseInt(r1);
y = Integer.parseInt(c1);
} catch(NumberFormatException l) {
flag = false;
}
int size = x * y;
if (flag == true) {
this.getContentPane().removeAll();
this.validate();
this.repaint();
myPanel.setLayout(new GridLayout(x, y));
while(size != 0)
{
myPanel.add(newButton);
size --;
}
this.getContentPane().add(myPanel);
} else {
this.getContentPane().add(Error);
}
}
}
There are several issues with this code
Is it really needed to post that much code. A simple UI with one button to press, and then another component which should appear would be sufficient for an SSCCE
The use of null layout's. Please learn to use LayoutManagers
Each Swing component can only be contained once in the hierarchy. So this loop is useless since you add the same component over and over again (not to mention that a negative size would result in an endless loop)
while(size != 0){
myPanel.add(newButton);
size --;
}
Have you tried debugging to see whether size is actually >0. Since you silently ignore ParseExceptions you might end up with a size of 0 which will clean the content pane and add nothing
Then do as goldilocks suggests and call validate after adding the components. See the javadoc of the Container#add method
This method changes layout-related information, and therefore, invalidates the component hierarchy. If the container has already been displayed, the hierarchy must be validated thereafter in order to display the added component.
Call validate() and repaint() after the new elements have been added instead of after the old ones have been removed.
You don't need to be calling setVisible() on individual components, call it after pack() on the Frame itself, and you shouldn't use validate() and repaint() in the constructor. Ie, replace those with:
pack();
setVisible(true);
or you can do that on the object after the constructor is called.
Try to replace
public static void main(String[]args)
{
new Frame();
}
by
public static void main(String[]args)
{
new Frame().setVisible(true);
}
Remove the call to this.setVisible in the constructor and make this your main method.
public static void main(String[] args) {
final Frame fr = new Frame();
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
fr.setVisible(true);
}
});
}
This will make sure that the frame elements will be in place before it becomes visible.

Categories