Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
I want to set the location of JButtons with coordinates but how to make each button don't "touch" the others? The Layout must be null !!! I tried the method contains and with Rectangle but it didn't work.
Thanks
You will want to use intersects(), not contains(). contains() will only return true if comp1 entirely contains comp2. Here is an example that continues to add new JButton's at a random position, as long as they don't intersect another Component. (click here for a preview)
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.Timer;
public class Example {
private final int BUTTON_WIDTH = 100;
private final int BUTTON_HEIGHT = 20;
public Example() {
JFrame frame = new JFrame();
frame.setLayout(null);
Timer timer = new Timer(100, new ActionListener() {
Random random = new Random();
#Override
public void actionPerformed(ActionEvent arg0) {
JButton button = new JButton("Button");
button.setBounds(random.nextInt(frame.getContentPane().getWidth() - BUTTON_WIDTH),
random.nextInt(frame.getContentPane().getHeight() - BUTTON_HEIGHT), BUTTON_WIDTH,
BUTTON_HEIGHT);
for (int tries = 0; tries < 50; tries++) {
if (intersectsComponent(button, frame.getContentPane().getComponents())) {
button.setBounds(random.nextInt(frame.getContentPane().getWidth() - BUTTON_WIDTH),
random.nextInt(frame.getContentPane().getHeight() - BUTTON_HEIGHT), BUTTON_WIDTH,
BUTTON_HEIGHT);
} else {
frame.add(button);
break;
}
}
frame.revalidate();
frame.repaint();
}
});
timer.start();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setVisible(true);
}
public boolean intersectsComponent(Component component, Component[] components) {
for (Component c : components) {
if (c.getBounds().intersects(component.getBounds())) {
return true;
}
}
return false;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Example();
}
});
}
}
Let's say you have a total space of H pixels by W pixels.
You can divide this space to a grid of 10 by 10.
Each "slot" is now H/10 by W/10.
Now you can create 100 buttons which do not collide using random setBounds(...) in each slot using a null layout.
Each JButton has its own space and you still get semi-randomness.
Note that MadProgrammer would prolly tell you it's a very bad idea to use null layouts.
EDIT:
Not only MadProgrammer.
Disclaimer:
I did once used a null layout for JDekstopPane.
I still have this line of code with me.
The rest 1M or so lines of code are free of such rebellious concepts.
Related
I need to create a 4 x 4 grid of rectangles in Java, I then need these rectangles to change color in a sequence.
I've never done any graphical work before, just things in the console.
I started by doing some research and created a 650 x 650 JFrame to put the rectangles in.
After that I used GridLayout and managed to create a 4 x 4 grid out of buttons using window.JButton which wasn't right.
How would I create rectangles instead? And would it be right to use for loops with ++ to time the animation?
I couldn't find anything that worked for my needs when searching on stackoverflow and google. Sorry if this is a stupid question. I'm new to this and I'm doing for an apprecticeship.
Here's how I would like it to look like, with each rectangle changing color in a time interval
From #Eng.Fouad answer (so give him credit and upvote his answer too), I made some changes, this example shows how to use a Swing Timer which changes color every second from green to red. I'm using a simple JLabel for demonstration purposes, take this logic into the GridLayout you have:
Here are some screen shots on how it looks:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.Color;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class SimpleTimer extends JFrame
{
private JLabel label;
private Timer timer;
private int counter = 3; // the duration
private int delay = 1000; // every 1 second
private static final long serialVersionUID = 1L;
private Color c = Color.RED;
private boolean red = true;
private boolean stop = false;
int i = counter;
public SimpleTimer()
{
super("Simple Timer");
setDefaultCloseOperation(EXIT_ON_CLOSE);
label = new JLabel("Wait for " + counter + " sec", JLabel.CENTER);
JPanel contentPane = (JPanel) getContentPane();
contentPane.add(label, BorderLayout.CENTER);
contentPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
pack();
timer = new Timer(delay, action);
timer.setInitialDelay(0);
timer.start();
setVisible(true);
}
ActionListener action = new ActionListener()
{
#Override
public void actionPerformed(ActionEvent event)
{
if(i == 0)
{
timer.stop();
stop = true;
i = counter;
timer = new Timer(delay, action);
timer.setInitialDelay(0);
timer.start();
}
else
{
c = red ? Color.GREEN : Color.RED;
red = !red;
label.setBackground(c);
label.setOpaque(true);
label.setText("Wait for " + i + " sec");
i--;
}
}
};
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
new SimpleTimer();
}
});
}
}
You could use jLabel, and set background color to it. How you do it, you can read here: How do I set a JLabel's background color?
Then just use for loop and Thred.sleep to change there color in animation.
I'm making an app where a user would be able to add a button to the screen or remove it (I don't have those options implemented yet). So, for now I'm manually populating it with a for() loop and manually removing one of the buttons. My problem is that after the button has been removed (the removal action in the main()), there's just a blank spot. I want to be able to repaint the screen after I remove one of those buttons. In this example, index 2 (block #3) has been removed, leaving an empty space, where it was before... and I have no idea how to repaint it. I've tried validating or repainting from different places in the program with no success.
Here's the code (P.S. I'm sure my code is not the most efficient way to accomplish what I'm trying to and I'm using setLayout(null), which is not a preferred method, but for now I'm just trying to learn certain things and then expand on that to better myself and my code):
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;
class TestApp extends JFrame{
JFrame frame = new JFrame("Test Program");
ArrayList<JButton> grid = new ArrayList<JButton>();
private int w = 14;
private static int amount = 102;
private static int counter = 0;
//Default Constructor (sets up JFrame)
TestApp(){
frame.setLayout(null);
frame.setPreferredSize(new Dimension(1186, 880));
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.setResizable(false);
paintGrid();
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public void newWindow()
{
JFrame select_win = new JFrame("Selected Frame");
JPanel select_panel = new JPanel();
select_panel.setPreferredSize(new Dimension(600, 800));
select_panel.setBackground(Color.ORANGE);
select_win.add(select_panel);
select_win.pack();
select_win.setResizable(false);
select_win.setVisible(true);
select_win.setLocationRelativeTo(null);
}
private void paintGrid()
{
for(int i = 0, y = 4; i < ((amount / w) + (amount % w)); i++, y += 104)
{
for(int j = 0, x = 4; j < w && (counter < amount); j++, x += 84)
{
addBlock(counter, x, y);
counter++;
}
}
}
//Adds a block
private void addBlock(int index, int x, int y){
int height = 100;
int width = 80;
grid.add(new JButton("counter: " + (counter + 1)));
(grid.get(index)).addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
newWindow();
}
});
}
});
(grid.get(index)).setBorder(new LineBorder(Color.BLACK));
(grid.get(index)).setBackground(Color.YELLOW);
(grid.get(index)).setVisible(true);
(grid.get(index)).setBounds(x, y, width, height);
frame.add(grid.get(index));
}
//Removes a block
private void removeBlock(int index){
frame.remove(grid.get(index));
grid.remove(index);
amount--;
counter--;
}
public static void main(String [] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
TestApp app = new TestApp();
//testing block removal
app.removeBlock(2);
}
});
}
}
The proper way would be: revalidate()
revalidate() method informs the layout manager that this component and all parents above it are marked as needing to be laid out. This means the Layout Manager will try to realign the components. Often used after removing components.
I would think that you will only know this if you are actually using Swing
As you said, is not good to use NullLayout. To fix your problem you only need to do two changes:
Change the layout to FlowLayout on the constructor, like this:
frame.setLayout(new FlowLayout());
Change the setBounds call to a setPreferredSize:
(grid.get(index)).setPreferredSize(new Dimension(width, height));
Now the FlowLayout will automatically align your items and you don't need to worry about it anymore.
A few days ago I posted a question about a program that caused text on screen to change color when the mousewheel was scrolled. It was unfortunately a badly put together question with too much code posted to be particularly useful.
I had several responses, one of which was from the user trashdog who posted something that fixed the problem (which can be found at the bottom of this page here: Window going blank during MouseWheelMotion event) , however having read the class descriptions of all the things I didn't know about in the program he posted and gone through its execution I don't understand why his achieves a different effect from mine.
His seems to log every mouse wheel movement where as mine only does the initial movement. Also several people commented that they couldn't replicate the effect of my program probably because it was so big.
Below is an extremely simplified version which still elicits the same effect (I hope).
Question: What is the fundamental difference between the two programs that fixes the screen going blank when the mouse wheel events are being processed?
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.LinkedList;
import javax.swing.JFrame;
public class WheelPrinter implements MouseWheelListener, Runnable {
JFrame frame;
LinkedList colorList;
int colorCount;
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
WheelPrinter w = new WheelPrinter();
w.run();
}
public WheelPrinter() {
frame = new JFrame();
frame.setSize(500, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.addMouseWheelListener(this);
frame.setVisible(true);
frame.setBackground(Color.WHITE);
colorList = new LinkedList();
colorList.add(Color.BLACK);
colorList.add(Color.BLUE);
colorList.add(Color.YELLOW);
colorList.add(Color.GREEN);
colorList.add(Color.PINK);
}
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
colorChange();
}
#Override
public void run() {
while(true) {
draw(frame.getGraphics());
try {
Thread.sleep(20);
} catch (Exception ex) {
}
}
}
public void draw(Graphics g) {
g.setColor(frame.getBackground());
g.fillRect(0,0,frame.getWidth(),frame.getHeight());
g.setFont(new Font("sansserif", Font.BOLD, 32));
g.setColor(frame.getForeground());
g.drawString("yes", 50, 50);
}
public void colorChange() {
colorCount++;
if (colorCount > 4) {
colorCount = 0;
}
frame.setForeground((Color) colorList.get(colorCount));
}
}
(Try spinning your mouse wheel really hard if you try running my code and it will become even more obvious)
while(true) { is endless loop, without break; f.e.
use Swing Timer instead of Runnable#Thread delayed by Thread.Sleep()
paint to the JPanel or JComponent, not directly to the JFrame
all painting to the Swing JComponent should be done in paintComponent()
more in the 2D Graphics tutorial
edit
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseWheelEvent;
import java.util.LinkedList;
import java.util.Queue;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/**
* based on example by #trashgod
*
* #see http://stackoverflow.com/a/10970892/230513
*/
public class ColorWheel extends JPanel {
private static final int N = 32;
private static final long serialVersionUID = 1L;
private final Queue<Color> clut = new LinkedList<Color>();
private final JLabel label = new JLabel();
public ColorWheel() {
for (int i = 0; i < N; i++) {
clut.add(Color.getHSBColor((float) i / N, 1, 1));
}
//clut.add(Color.BLACK);
//clut.add(Color.BLUE);
//clut.add(Color.YELLOW);
//clut.add(Color.GREEN);
//clut.add(Color.PINK);
label.setFont(label.getFont().deriveFont(36f));
label.setForeground(clut.peek());
label.setText("#see http://stackoverflow.com/a/10970892/230513");
setBackground(Color.white);
add(label);
label.addMouseWheelListener(new MouseAdapter() {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
label.setForeground(clut.peek());
clut.add(clut.remove());
}
});
}
#Override
public Dimension getPreferredSize() {
int w = SwingUtilities.computeStringWidth(label.getFontMetrics(
label.getFont()), label.getText());
return new Dimension(w + 20, 80);
}
private void display() {
JFrame f = new JFrame("ColorWheel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new ColorWheel().display();
}
});
}
}
The fundamental difference is that you are trying to interact with the Graphics object from the wrong thread and without knowing anything about the state the Graphics object is in at the time.
The generally correct way to interact with a Graphics object in Swing is by making a custom component which overrides the paintComponent(Graphics) method. You do your drawing while inside that method.
Your colorChange() method can tell your component to re-draw itself by calling repaint(), which will eventually lead to a call to paintComponent(Graphics) on the correct thread at the correct time.
See tutorial here
I want to set the location of a JPopupMenu depending of the y location of the button that opens the menu. My code works fine on my first monitor, but fails on my second monitor, wich has a different height.
The problem is getLocationOnScreen() delivers the location relative to the main screen, not the actual screen on which the component is shown.
My code:
// screenSize represents the size of the screen where the button is
// currently showing
final Rectangle screenSize = dateButton.getGraphicsConfiguration().getBounds();
final int yScreen = screenSize.height;
int preferredY;
// getLocationOnScreen does always give the relative position to the main screen
if (getLocationOnScreen().y + dateButton.getHeight() + datePopup.getPreferredSize().height > yScreen) {
preferredY = -datePopup.getPreferredSize().height;
} else {
preferredY = getPreferredSize().height;
}
datePopup.show(DateSpinner.this, 0, preferredY);
How can I get the location of a component on its actual monitor?
I got a solution for this using the bounds of the second screen, it's quite simple:
public static Point getLocationOnCurrentScreen(final Component c) {
final Point relativeLocation = c.getLocationOnScreen();
final Rectangle currentScreenBounds = c.getGraphicsConfiguration().getBounds();
relativeLocation.x -= currentScreenBounds.x;
relativeLocation.y -= currentScreenBounds.y;
return relativeLocation;
}
Thanks for your answers!
Usually when you call "getLocationOnScreen()" it gets the location of the component "this" (from the code I don't quite understand who "this" is).
Maybe you can try to get location of the button by using "button.getLocationOnScreen()".
Here is a small snippet that shows how to position elements relatively to another one. It displays a popup menu below the button, and a JDialog to its left. I tested it on a multi-screen environment where secondary screen is on the right of the main one.
Also, use getSize(), getWidth() and getHeight() instead of getPreferredSize(). getSize(), getWidth and getHeight return the actual dimensions of the component, while getPreferredSize() is only an indicator to the LayoutManager to what the component wishes to have.
If you use the method JPopupMenu.show() make sure to use coordinates and sizes relative to the invoker component.
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
public class Test2 {
public static void main(String[] args) {
final JFrame frame = new JFrame();
final JButton button = new JButton("Hello");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JPopupMenu popupMenu = new JPopupMenu();
popupMenu.add(new JMenuItem("Some test"));
System.err.println(button.getLocationOnScreen());
popupMenu.show(button, 0, button.getHeight());
JDialog dialog = new JDialog(frame);
dialog.setSize(100, 30);
Point locationOnScreen = button.getLocationOnScreen();
locationOnScreen.x += button.getWidth();
dialog.setLocation(locationOnScreen);
dialog.setVisible(true);
}
});
frame.addComponentListener(new ComponentListener() {
#Override
public void componentShown(ComponentEvent e) {
}
#Override
public void componentResized(ComponentEvent e) {
info(button);
}
private void info(final JButton button) {
if (button.isShowing()) {
System.err.println(button.getLocationOnScreen());
System.err.println(button.getGraphicsConfiguration().getBounds());
}
}
#Override
public void componentMoved(ComponentEvent e) {
info(button);
}
#Override
public void componentHidden(ComponentEvent e) {
}
});
button.setPreferredSize(new Dimension(200, 60));
frame.add(button);
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
}
I have an array of JButtons which form a keypad interface. After six numbers are entered I want to disable the keypad so that no further numbers can be entered by the user.
I have written the code and the buttons do disable until the mouse hovers above any of them, then the buttons seem to re-enable themselves and run actionEvents added to them.
The full code is available here.
Possible things that I think are wrong.
There is some sort of MouseListener which is ignoring when I set button.setEnabled(false);
I haven't separated attributes from the buildGUI(); correctly, I only did this anyway so that the inner class could access them.
Possibly something to do with the gridLayout as disabling the buttons seems to work for my services JPanel buttons.
The problem lies in how you instantiated your Frame (CashMachine), not (directly) with its implementation.
You are calling buildGUI twice, one in the object's constructor, and then in the Driver class that instantiates the object. As a result, you are creating (and laying out) two sets of buttons.
When the buttons of the first set were eventually disabled, your mousing activity was revealing the second set of buttons. And a flaw in your ActionListener implementation can cause inputCount to take on values greater than 6, so buttons in the second set were not eventually disabled like those from the first set.
buildGUI should be private; it should be called in the CashMachine constructor, and not by your Driver class.
Conversely, in my opinion, CashMachine.setVisible should be called by the Driver class, and not by the CashMachine constructor.
The code works just fine I guess.
One possible source of confusion in your program is mixing number keys with control keys, Clear and Enter. Consider handling number keys separately with a single listener, as suggested in the NumberButton class shown below. Then you can handle the Clear and Enter buttons as desired. Also, using a List<NumberButton> makes the enable and disable loops easier.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
public class KeyPadPanel extends JPanel implements ActionListener {
private static final int MAX = 6;
private final List<NumberButton> numbers = new ArrayList<NumberButton>();
private final JTextArea text = new JTextArea(1, MAX);
private final JButton clear = new JButton("Clear");
private final JButton enter = new JButton("Enter");
public KeyPadPanel() {
super(new BorderLayout());
JPanel display = new JPanel();
text.setEditable(false);
display.add(text);
this.add(display, BorderLayout.NORTH);
JPanel pad = new JPanel(new GridLayout(4, 4));
for (int i = 0; i < 10; i++) {
NumberButton n = new NumberButton(i);
numbers.add(n);
if (i > 0) {
pad.add(n);
}
}
pad.add(clear);
pad.add(numbers.get(0));
pad.add(enter);
clear.addActionListener(this);
enter.addActionListener(this);
this.add(pad, BorderLayout.CENTER);
}
#Override
public void actionPerformed(ActionEvent e) {
text.setText("");
enableButtons();
}
private void enableButtons() {
for (NumberButton n : numbers) {
n.setEnabled(true);
}
}
private void disableButtons() {
for (NumberButton n : numbers) {
n.setEnabled(false);
}
}
private class NumberButton extends JButton implements ActionListener {
public NumberButton(int number) {
super(String.valueOf(number));
this.addActionListener(this);
}
#Override
public void actionPerformed(ActionEvent e) {
NumberButton b = (NumberButton) e.getSource();
if (text.getText().length() < MAX) {
text.append(b.getText());
}
if (text.getText().length() == MAX) {
disableButtons();
}
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new KeyPadPanel());
f.pack();
f.setVisible(true);
}
});
}
}
Examining the class files was helpful! The problem is in the Driver class:
the buildGUI() method is being called 2 times: once in the constructor of CashMachine and second in the main method after calling the constructor.
public static void main(String args[])
{
CashMachine cashmachine = new CashMachine();
cashmachine.buildGUI();
}
This way you end up with the double number of buttons, that is, a pair of buttons at each position. But only one of each is being disabled.
Just remove the call to buildGUI from main (or from the constructor).
(I would change buildGUI to private as it should not be called from outside the class...)