How to create a border of JPanel, which will be able to handle MouseEvents?
I tried to do something like that:
abstract public class MyBorder extends LineBorder implements MouseListener
But after implementing virtual methods I cannot assign mouseListener to my class. I guess, that I have to assign it into some JComponent.
So, how can I create some sort of border with mouseListener?
A MouseListener must be added to a Component, not a Border. So to use your class the code would need to be something like:
Border border = new MyBorder();
panel.setBorder( border );
panel.addMouseListener( border );
Here is an SSCCE that supports that borders get mouse events on the component to which they are applied.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.LineBorder;
class BorderListener {
private void initGui() {
final JPanel gui = new JPanel();
gui.setBackground(Color.green);
gui.setPreferredSize(new Dimension(300,50));
gui.setBorder(new LineBorder(Color.blue, 10));
gui.addMouseListener( new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent me) {
System.out.println(me.getPoint());
}
});
JOptionPane.showMessageDialog(null, gui);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
BorderListener bl = new BorderListener();
bl.initGui();
}
});
}
}
Typical Output
When clicking in the wide border assigned to this panel, you might see output along these lines.
java.awt.Point[x=8,y=3]
java.awt.Point[x=3,y=26]
java.awt.Point[x=1,y=43]
java.awt.Point[x=15,y=6]
java.awt.Point[x=101,y=5]
java.awt.Point[x=220,y=4]
java.awt.Point[x=287,y=5]
java.awt.Point[x=295,y=3]
Press any key to continue . . .
The border is 10px wide, so if (x||y < 10), it is within the line border.
Update
(Comment to camickr, which also applied to my answer)
Yes but then this mouseListener will be added for the whole JPanel. Not only for my Border. Am I wrong?
Just ignore the event if it happens in the non-border area of the panel.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.LineBorder;
class BorderListener {
private void initGui() {
final JPanel gui = new JPanel();
gui.setBackground(Color.yellow);
gui.setPreferredSize(new Dimension(300,50));
gui.setBorder(new LineBorder(Color.orange, 15));
gui.addMouseListener( new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent me) {
int w = gui.getWidth();
int h = gui.getHeight();
int x = me.getPoint().x;
int y = me.getPoint().y;
Insets ins = gui.getInsets();
boolean inBorder =
( x<ins.left ||
x>w-ins.right ||
y<ins.top ||
y>h-ins.bottom);
if (inBorder) {
System.out.println(me.getPoint());
} else {
System.out.println("Ignore!");
}
}
});
JOptionPane.showMessageDialog(null, gui);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
BorderListener bl = new BorderListener();
bl.initGui();
}
});
}
}
Output
java.awt.Point[x=168,y=7]
Ignore!
java.awt.Point[x=164,y=41]
java.awt.Point[x=297,y=39]
java.awt.Point[x=297,y=21]
Ignore!
Ignore!
java.awt.Point[x=2,y=21]
Press any key to continue . . .
Related
I have 20 JLabels and all of them have to change their background color when mouse enters and change back to original color when mouse outs.
Do I have to individually bind 2 Event-listeners of MouseEntered and MouseExited with all JLabels separately, or is there any work around so I can make just 2 events kind of monitoring all JLabels?
Like in the image below: there are about 6 JLabels and I want each one to change its background color whenever the mouse enters the scene and change back to original color when the mouse outs.
So, do I have to individually set event listeners on all JLabels, or there can be a single event listener for all JLabels?
You can register all 20 JLabels with the same mouse listener. You would do something like this:
MouseListener m = new MouseAdapter() // create our own mouse listener
{
#Override
public void mouseEntered(MouseEvent e)
{
e.getComponent().setBackground(Color.RED);; // this method changes the colours of all the labels
}
#Override
public void mouseExited(MouseEvent e)
{
e.getComponent().setBackground(Color.GREEN); // this method changes the colours back to normal
}
};
for (JLabel label: labels) // iterate over all the labels
{
label.addMouseListener(m); // give them all our mouse listener
}
Where "labels" is some collection (List, Set, array...) of your JLabels, and changeLabelColours() and changeLabelColoursBack() are two methods that you define to change the colours.
hope this helps!
EDIT: reading your edited question, I think I should point out that this code will cause ALL labels to change colour when ANY of the labels is moused-over. I think that's what you mean.
You don't "make events". You make eventListeners. And yes, you can do just 2 event listeners and bound them to all JLabels.
Or, if you prefer, you can extends Jlabel into MyJlabel, that has the event listener built in, and save yourself from the repeated binding, if it bothers you.
You can use one MouseListener reference.
You should differentiate event sources based on references or on component name.
Here is an example
final JLabel label1 = new JLabel("label1");
label1.setName("label1");
final JLabel label2 = new JLabel("label2");
label2.setName("label2");
final JLabel label3 = new JLabel("label3");
label3.setName("label3");
MouseListener mouseListener = new MouseAdapter() {
#Override
public void mouseExited(MouseEvent e) {
// you can check references
if(e.getSource() == label1) {
System.out.println("exited " + label1.getName());
} else if (e.getSource() == label2) {
System.out.println("exited " + label2.getName());
} else if (e.getSource() == label3) {
System.out.println("exited " + label3.getName());
}
}
#Override
public void mouseEntered(MouseEvent e) {
String name = ((JComponent)e.getSource()).getName();
// or you can check name of component
switch (name) {
case "label1":
case "label2":
case "label3":
System.out.println("entered " + name);
break;
}
}
};
for (JLabel l : Arrays.asList(label1, label2, label3)) {
l.addMouseListener(mouseListener);
}
Note that labels need to be opaque in order to change their bg color.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class CrazyLabels extends JFrame {
public CrazyLabels() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new BorderLayout());
JPanel content = new JPanel();
add(content);
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
MouseAdapter listener = new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
JLabel label = (JLabel)e.getSource();
label.setBackground(Color.red);
}
#Override
public void mouseExited(MouseEvent e) {
JLabel label = (JLabel)e.getSource();
label.setBackground(UIManager.getColor("Label.background"));
}
};
for (int i = 0; i < 5; i++) {
JLabel label = new MyLabel(listener);
label.setText("---- label ----");
content.add(label);
}
pack();
setLocationRelativeTo(null);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new CrazyLabels().setVisible(true);
}
});
}
private static class MyLabel extends JLabel {
public MyLabel(MouseListener listener) {
setOpaque(true);
addMouseListener(listener);
}
}
}
In this example a single listener is used to enforce behavior in all labels.
Ok so I have my main class which got some buttons one for a triangle and the other for an
oval and boxes.
And i got a button for ColorChooser I want to click on it and ColorChooser show up. I have
the class for the oval and triangles and ColorChooser and i set each one of them to a mode
in my main program.
So this is my main program with only the boxes and ColorChooser:
As you can see im using modes for each button and i set mode 4 for ColorChooser
If you want me to add the box class or the ColorChooser class i will if it is not making
sense. I just dont want it to be any longer.
This is my main program:
import java.awt.*;
import java.util.ArrayList;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
public class Kaleidescope extends JFrame implements MouseListener, ActionListener,
MouseMotionListener
{
Box b;
ArrayList<Box> boxes; // list of boxes
ColorChooser oo;
ColorChooser[] colors;
int colorCount;
// Buttons
JButton boxButton;
JButton ColorButton;
int x1, y1; // mousePressed
int w1, z1; // mouseEntered
int mode =1; // 1 = line, 2= boxes, 3 = oval, 4= text, 5 = SG, twoLines = 7.
public static void main( String[] args )
{
System.out.println("hi there.");
new Kaleidescope();
}
public Kaleidescope()
{
setDefaultCloseOperation( EXIT_ON_CLOSE );
addMouseListener(this);
addMouseMotionListener(this);
boxes = new ArrayList<Box>();
colors = new ColorChooser[20];
colorCount = 0;
setLayout( new FlowLayout() );
boxButton = new JButton("Boxes");
add(boxButton);
boxButton.addActionListener( this );
ColorButton = new JButton("Color Chooser");
add(ColorButton);
ColorButton.addActionListener( this);
setSize( new Dimension(500,500) );
setVisible(true);
}
// returns a random color
public Color randomColor()
{
int red = (int)(Math.random()*255);
int green = (int)(Math.random()*255);
int blue = (int)(Math.random()*255);
return new Color(red,green,blue);
}
public void mouseClicked( MouseEvent e )
{
// box
if ( mode == 2)
{
boxes.add(new Box(e.getX(), e.getY(), randomColor()));
}
repaint();
}
//action performed
public void actionPerformed( ActionEvent e )
{
if ( e.getSource()==TriangleButton ) { mode = 1;}
else if ( e.getSource()==boxButton ) { mode = 2;}
else if ( e.getSource()==ovalButton) { mode = 3;}
else if ( e.getSource()==ColorButton) { mode = 4;}
//clear all
else if (e.getSource() == clearButton)
{
boxes.clear();
triangles.clear();
ovals.clear();
}
repaint();
}
public void mouseEntered( MouseEvent e ) { }
public void mousePressed( MouseEvent e ) { }
public void mouseExited( MouseEvent e ) { }
public void mouseReleased( MouseEvent e ) {}
public void mouseMoved( MouseEvent e ) {}
public void mouseDragged( MouseEvent e ){ }
}
public void paint( Graphics g )
{
//draw/paint box triangle and oval
super.paint(g);
for (Box box : boxes)
{
box.drawMe(g);
}
}
}
here is my colorChooser class:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.colorchooser.*;
public class ColorChooser extends JPanel implements ChangeListener
{
public static final long serialVersionUID = 1L;
public JColorChooser tcc;
public JLabel banner;
public ColorChooser()
{
super(new BorderLayout());
banner = new JLabel("",JLabel.CENTER);
banner.setOpaque(true);
banner.setPreferredSize(new Dimension(100, 65));
JPanel bannerPanel = new JPanel(new BorderLayout());
bannerPanel.add(banner, BorderLayout.CENTER);
bannerPanel.setBorder(BorderFactory.createTitledBorder("Banner"));
//Set up color chooser for setting text color
tcc = new JColorChooser(banner.getForeground());
tcc.getSelectionModel().addChangeListener(this);
tcc.setBorder(BorderFactory.createTitledBorder("Choose Text Color"));
add(bannerPanel, BorderLayout.CENTER);
add(tcc, BorderLayout.PAGE_END);
}
public void stateChanged(ChangeEvent e)
{
Color newColor = tcc.getColor();
banner.setForeground(newColor);
}
private static void createAndShowGUI()
{
//Create and set up the window.
JFrame frame = new JFrame("ColorChooserDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Create and set up the content pane.
JComponent newContentPane = new ColorChooser();
newContentPane.setOpaque(true); //content panes must be opaque
frame.setContentPane(newContentPane);
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args)
{
javax.swing.SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
}
Start by taking a look at How to Write an Action Listener and How to Use Color Choosers
Basically, attach a ActionListener to the JButton you want to activate the JColorChooser and when the actionPerformed method is called, use inbuilt functionality to show the default chooser window, for example (from the linked tutorial)...
Color newColor = JColorChooser.showDialog(
ColorChooserDemo2.this,
"Choose Background Color",
banner.getBackground());
Updated
Start by adding a Color instance variable to Kaleidescope, this will allow you to maintain a reference to the last chossen color
private Color currentPaintColor = Color.BLACK;
Next, when the ColorButton is pressed, you will want to create some kind of dialog to show the chooser in, this will allow you to wait until the user chooses a color and get the resulting color...
} else if (e.getSource() == ColorButton) {
ColorChooser chooser = new ColorChooser();
int result = JOptionPane.showConfirmDialog(this, chooser, "Color Chooser", JOptionPane.OK_CANCEL_OPTION);
if (result == JOptionPane.OK_OPTION) {
currentPaintColor = chooser.getChoosenColor();
}
} //clear all
You'll also want to change the stateChanged method in ColorChooser to make it more meaningful...
public void stateChanged(ChangeEvent e) {
Color newColor = tcc.getColor();
banner.setBackground(newColor);
}
Now, the question is, what do you want to do with this new color? Do you want to apply it to all the current shapes you are painting? If so you need to set the color before painting the shapes...
public void paint(Graphics g) {
//draw/paint box triangle and oval
super.paint(g);
g.setColor(currentPaintColor);
for (Box box : boxes)
{
box.drawMe(g);
}
}
Or do you only want to apply the color to new objects added after the change?
You should avoid overriding paint of top level containers, lots of reason, they aren't double buffered, which will cause flickering when they are updated and you will be painting over the top of everything else on the frame and you can potentially paint under the frame borders...
Instead you should use something JPanel and override it's paintComponent method, take a look at Performing Custom Painting for more details
You need to clarify just what you expect a mode = 4 will do with your program. I can understand using JButton presses to set shape modes (object state) that will alter the behavior of your MouseListener, but I don't see how a color chooser will fit into this model, and in fact believe that it likely won't. I don't have the rest of your code, nor do I have the specifics of your assignment, but I'm going to make some recommendations based on guesses, and thus these will necessarily be weak recommendations.
I'm guessing that you want to have a JColorChooser dialog displayed when the color button is pressed.
And that the user then can select a Color that will be the color of the drawn shapes.
If so, then likely you shouldn't have the color chooser button (which should be named colorChooserButton) set a numeric node. Rather, it should open your color choosing dialog, and after the user has selected a color, then you should set a Color field in your drawing class, not the mode value.
Rather the modes should only be used to select an appropriate shape that the MouseListener will use to determine what shape to draw.
Myself, I wouldn't use numeric modes but rather an enum to define this state, but that is probably something you'll learn later in your programming education.
If my assumptions are incorrect, then please clarify them.
As an aside, note that you should not draw in a paint(Graphics g) method, and should not draw directly in the JFrame. Rather you should draw in the paintComponent(Graphics g) method override of a JPanel or JComponent.
Here's a runnable example of how JColorChooser can be used to set the color for different tasks. Feel free to ask questions if you are unclear about anything.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ColorChooserPaintExample {
private Color color = Color.BLUE;
private ColorPanel colorPanel = new ColorPanel();
public ColorChooserPaintExample() {
final JFrame frame = new JFrame("My Color Chooser Demo");
JButton chooseColor = new JButton("Change Color of Panel");
chooseColor.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
color = JColorChooser.showDialog(frame, "Choose a Color for Panel", color);
colorPanel.repaint();
}
});
frame.add(colorPanel);
frame.add(chooseColor, BorderLayout.SOUTH);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private class ColorPanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(color);
g.fillRect(0, 0, getWidth(), getHeight());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
public void run() {
new ColorChooserPaintExample();
}
});
}
}
I'm still very new to java programming, so please help me to correct any mistakes I might have overlooked or give tips on how to improve this program.
Okay, so a lot of problems have been solved, and now I have a CardLayout, but I still have questions about how I should make my pipes show inside it.
When I tried to add in my refresh rate timer and my speed timer, I have problems about how I need to declare and initialize boolean variables.
Also, when I compile and run this game, I get files such as Game$1.class. Is there a way for me to clean this up, and could someone explain why this happens? Do these have an affect on the finished product? (When the game is compiled and packaged into a JAR.)
I want to set playerIsReady to true when the play button is clicked. And from there, when the if statement is true, then switch to a panel that displays the pipes, and start moving the pipe across the screen. Preferably 3 instances of that pipe, each starting at different times, but whatever you can help with is fine.
Some of this code needs work, so I have commented some parts out and left notes.
My other questions about this game can be found here.
This is my current code
Game.java
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
import javax.swing.SwingUtilities;
public class Game {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
// the GUI as seen by the user (without frame)
final CardLayout cl = new CardLayout();
final JPanel gui = new JPanel(cl);
// remove if no border is needed
gui.setBorder(new EmptyBorder(10,10,10,10));
JPanel menu = new JPanel(new GridBagLayout());
JButton playGame = new JButton("Play!");
ActionListener playGameListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
cl.show(gui, "game");
}
};
playGame.addActionListener(playGameListener);
Insets margin = new Insets(20, 50, 20, 50);
playGame.setMargin(margin);
menu.add(playGame);
gui.add(menu);
cl.addLayoutComponent(menu, "menu");
final JPanel pipes = new Pipes();
gui.add(pipes);
cl.addLayoutComponent(pipes, "game");
JFrame f = new JFrame("Pipes Game");
f.add(gui);
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See https://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
/*if (playerIsReady) {
Timer speed = new Timer(10, new ActionListener() { //pipe speed
#Override
public void actionPerformed(ActionEvent e) {
pipes.move();
}
});
speed.start();
Timer refresh = new Timer(30, new ActionListener() { //refresh rate
#Override
public void actionPerformed(ActionEvent e) {
pipes.repaint();
}
});
refresh.start();
}*/
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
Pipes.java
// What import(s) do I need for ArrayList?
public class Pipes {
List<Pipe> pipes = new ArrayList<Pipe>();
public Pipes() {
pipes.add(new Pipe(50, 100));
pipes.add(new Pipe(150, 100));
pipes.add(new Pipe(250, 100));
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for ( Pipe pipe : pipes ){
pipe.drawPipe(g);
}
}
}
PipeObject.java
import java.awt.Graphics;
public class PipeObject {
//Declare and initialiaze variables
int x1 = 754; //xVal start
int x2 = 75; //pipe width
//total width is 83
int y1 = -1; //yVal start
int y2 = setHeightVal(); //pipe height
int gap = 130; //gap height
public void drawPipe(Graphics g) {
g.clearRect(0,0,750,500); //Clear screen
g.drawRect(x1,y1,x2,y2); //Draw part 1
g.drawRect(x1-3,y2-1,x2+6,25); //Draw part 2
g.drawRect(x1-3,y2+25+gap,x2+6,25); //Draw part 3
g.drawRect(x1,y2+25+gap+25,x2,500-y2-49-gap); //Draw part 4
}
public void move() {
x1--;
}
public int getMyX() { //To determine where the pipe is horizontally
return x1-3;
}
public int getMyY() { //To determine where the pipe is vertically
return y2+25;
}
public int setHeightVal() { //Get a random number and select a preset height
int num = (int)(9*Math.random() + 1);
int val = 0;
if (num == 9)
{
val = 295;
}
else if (num == 8)
{
val = 246;
}
else if (num == 7)
{
val = 216;
}
else if (num == 6)
{
val = 185;
}
else if (num == 5)
{
val = 156;
}
else if (num == 4)
{
val = 125;
}
else if (num == 3)
{
val = 96;
}
else if (num == 2)
{
val = 66;
}
else
{
val = 25;
}
return val;
}
}
The best way to approach this is using a CardLayout.
Notes
A button with an ActionListener is far better than a MouseListener over a rectangle.
The button will show focus when the mouse is pointed at it, or the component is tabbed to via the keyboard.
The button is keyboard accessible.
The button has facility to support multiple icons built in (e.g. for 'initial look', focused, pressed etc.)
White space in the GUI is provided around the menu panel and game by adding an EmptyBorder
The button is made larger by setting a margin.
Adjust margins, borders and preferred size according to need. These sizes were set by me so as not to make the screenshots too large.
See more tips in the code comments.
Code
Here is the MCTaRE (Minimal Complete Tested and Readable Example) that produced the above screenshots.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class PipesGame {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
// the GUI as seen by the user (without frame)
final CardLayout cl = new CardLayout();
final JPanel gui = new JPanel(cl);
// remove if no border is needed
gui.setBorder(new EmptyBorder(10,10,10,10));
JPanel menu = new JPanel(new GridBagLayout());
JButton playGame = new JButton("Play!");
ActionListener playGameListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
cl.show(gui, "game");
}
};
playGame.addActionListener(playGameListener);
Insets margin = new Insets(20, 50, 20, 50);
playGame.setMargin(margin);
menu.add(playGame);
gui.add(menu);
cl.addLayoutComponent(menu, "menu");
JPanel pipes = new Pipes();
gui.add(pipes);
cl.addLayoutComponent(pipes, "game");
JFrame f = new JFrame("Pipes Game");
f.add(gui);
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See https://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
class Pipes extends JPanel {
Pipes() {
setBackground(Color.BLACK);
setForeground(Color.WHITE);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawString("Pipes game appears here..", 170, 80);
}
#Override
public Dimension getPreferredSize() {
// adjust to need
return new Dimension(500,150);
}
}
"Is there a way for me to add my GameMenu jpanel to my jframe, and then replace it with the Pipes jpanel?"
As other have suggested, for this you want a CardLayout. It is very simple to you. Personally, I always wrap my CardLayout in a JPanel rather than the JFrame, just force of habit.
What you want to do is have a mainPanel that will have the CardLayout
CardLayout card = new CardLayout();
JPanel mainPanel = new JPanel();
Then you want to add your panels to the mainPanel. What the CardLyaout does is layer the panels, making just one visible at a time. The first one you add, will the in the foreground. Also when you add the panel, you'll also want to issue it a key it can be called from. The key, can be any String you like.
mainPanel.add(gameMenu, "menu");
mainPnael.add(pipes, "pipe");
Now gameMenu is the only panel shown. To show pipes, all you do is use this method
public void show(Container parent, String name) - Flips to the parent that was added to this layout with the specified name, using addLayoutComponent. If no such component exists, then nothing happens.
So you'd use, card.show(mainPanel, "pipes");
Whatever even you want to trigger the showing of pipes, just add that line in that event handler. You could add a button or something to the GameMenu that will allow movement to the Pipes panel.
This works with a mouse click on the menu. You can change it later, to a click on some button or whatever you want.
I added a MouseListener to the Game class. When the user presses the mouse on the menu JPanel, it adds the Pipes JPanel to JFrame and calls the pack method.
Game.java:
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Game {
GameMenu menu = new GameMenu();
Pipes game;
boolean start = false;
JFrame f;
Rectangle2D menuRect = new Rectangle2D.Double(20, 20, 60, 40);
public Game() {
f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(menu);
f.setTitle("Pipe Game");
f.setResizable(false);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
menu.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
Point click = new Point(e.getX(), e.getY());
System.out.println("Clicked on the Panel");
if(menuRect.contains(click))
{
System.out.println("Clicked inside the Rectangle.");
start = true;
menu.setVisible(false);
game = new Pipes();
f.add(game);
f.pack();
Timer timer = new Timer(10, new ActionListener() { //pipe speed
#Override
public void actionPerformed(ActionEvent e) {
game.move();
}
});
timer.start();
Timer refresh = new Timer(30, new ActionListener() { //refresh rate
#Override
public void actionPerformed(ActionEvent e) {
game.repaint();
}
});
refresh.start();
}
}
#Override
public void mouseReleased(MouseEvent e) {
}
});
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Game();
}
});
}
}
I'm trying to do a simple piece of homework, where I display a line of text displaying whether a door object is open or not. Underneath that, I visually represent it (using the drawRect) method. And at the bottom I have two buttons, which can open or close the door, thus changing the text and rectangle.
Edit: List of code that can be compiled given now:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class Test {
public static void main(String[] args) {
// Creates new JFrame called frame, with title "Door"
// (displayed at top of screen).
JFrame frame = new JFrame ("Door");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
TempDoorPanel panel = new TempDoorPanel();
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}
class Door {
private String state;
private String message;
Door (String state) {
this.state = state;
message = "The door is currently closed.";
}
public boolean isOpen() {
return state.equals ("open");
}
public boolean isClosed() {
return state.equals ("closed");
}
public void setState(String state) {
this.state = state;
}
public String getMessage() {
return message;
}
public void open() {
if (state.equals("open")) {
message = "The door is already open.";
}
else {
state = "open";
message = "The door has been opened.";
}
}
public void drawOpenDoor (Graphics page) {
page.drawRect(100, 100, 100, 100);
}
}
class TempDoorPanel extends JPanel {
private Door door;
private JTextField currentStateOfDoor;
private JButton openDoor;
public TempDoorPanel() {
super.setLayout(new BorderLayout());
door = new Door("closed");
super.setBackground(Color.blue);
super.setPreferredSize(new Dimension (360, 400));
currentStateOfDoor = new JTextField(14);
currentStateOfDoor.setText(door.getMessage());
super.add(currentStateOfDoor, BorderLayout.NORTH);
openDoor = new JButton("Open Door");
class openDoorListener implements ActionListener {
public void actionPerformed (ActionEvent event) {
door.open();
repaintText();
}
}
openDoorListener openlistener = new openDoorListener();
openDoor.addActionListener(openlistener);
JPanel holder = new JPanel();
holder.add(openDoor);
super.add(holder, BorderLayout.SOUTH);
}
private void repaintText() {
currentStateOfDoor.setText(door.getMessage());
// These methods are from Door class.
}
public void paintComponent (Graphics page) {
super.paintComponent(page);
if (door.isOpen())
door.drawOpenDoor(page);
// isOpen is a boolean method from Door class.
}
}
What works:
Buttons appear at right place on screen, at BorderLayout.SOUTH, one after the other.
The JTextField appears at right place, at BorderLayout.NORTH
Finally, the blue area appears in the right place in the centre of the screen.
What I'm trying to fix:
I have no idea how to display the rectangle properly in the middle of that blue area. I've tried changing the coordinates and size of the rectangle, which doesn't change the size of it at all. I can make it drawRect(100, 100, 100, 100) and it changes nothing.
I'm also aware that the rectangle is currently hidden behind the top left corner of the JTextField, but I can't figure out how to move it into the BorderLayout.
Questions:
How do you place a rectangle in a BorderLayout?
How do you adjust the size of a rectangle, drawn via drawrect(), in such a layout?
Because you add components to the JPanel you draw on the JTextField is covering your drawing.
Solution:
1) Either compensate for this by checking the JTextField height in your drawRect(..) method
or better
2) Dont add components to the same JPanel which you are drawing on unless it cant be helped.
So basically I made your TempDoorPanel add a new JPanel to BorderLayout.CENTER which is the drawing panel we can now use drawRect(0,0,10,10) and it will show in the top left hand corner of JPanel drawingPanel.
Also dont call setPreferredSize on JPanel rather override getPreferredSize() and return Dimensions which fit your drawings.
To invoke paintComponent outside of the class simply call repaint() its instance
See this example which uses point no.2:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Door");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
TempDoorPanel panel = new TempDoorPanel();
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
});
}
}
class Door {
private String state;
private String message;
public Door(String state) {
this.state = state;
message = "The door is currently closed.";
}
public void drawOpenDoor(Graphics page) {
page.setColor(Color.GREEN);
page.drawRect(0, 0, 10, 10);
}
}
class TempDoorPanel extends JPanel {
private Door door;
private JTextField currentStateOfDoor;
private JButton openDoor;
public TempDoorPanel() {
super.setLayout(new BorderLayout());
door = new Door("closed");
currentStateOfDoor = new JTextField(14);
//AcurrentStateOfDoor.setText(door.getMessage());
super.add(currentStateOfDoor, BorderLayout.NORTH);
openDoor = new JButton("Open Door");
final JPanel drawingPanel = new JPanel() {
#Override
protected void paintComponent(Graphics grphcs) {
super.paintComponent(grphcs);
// if (door.isOpen()) {
door.drawOpenDoor(grphcs);
// }
// isOpen is a boolean method from Door class.
}
};
drawingPanel.setBackground(Color.blue);
add(drawingPanel);
class openDoorListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
//door.open();
repaintText();
drawingPanel.repaint();//so paint component of drawing panel is called
}
}
openDoorListener openlistener = new openDoorListener();
openDoor.addActionListener(openlistener);
JPanel holder = new JPanel();
holder.add(openDoor);
super.add(holder, BorderLayout.SOUTH);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
private void repaintText() {
// currentStateOfDoor.setText(door.getMessage());
// These methods are from Door class.
}
}
When you handler the door opening event with your listener;
class openDoorListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
door.open();
repaintText();
}
}
you don't actually include a call to repaint the panel; hence the panel's paintComponent() method isn't called and door.drawOpenDoor() isn't called. You can test this by clicking the button and then resizing the frame. When you resize, the panel is automatically repainted and bingo, your door appears.
You can fix this by adding a call to repaint() in your ActionListener;
class openDoorListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
door.open();
repaintText();
repaint(); // requests that the panel be repainted
}
}
I have a Swing app with a large panel which is wrapped in a JScrollPane. Users normally move between the panel's subcomponents by tabbing, so when they tab to something out view, I want the scroll pane to autoscroll so the component with input focus is always visible.
I've tried using KeyboardFocusManager to listen for input focus changes, and then calling scrollRectToVisible.
Here's an SSCCE displaying my current strategy (just copy/paste and run!):
import java.awt.KeyboardFocusManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
public class FollowFocus {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final int ROWS = 100;
final JPanel content = new JPanel();
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
content.add(new JLabel(
"Thanks for helping out. Use tab to move around."));
for (int i = 0; i < ROWS; i++) {
JTextField field = new JTextField("" + i);
field.setName("field#" + i);
content.add(field);
}
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.addPropertyChangeListener("focusOwner",
new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (!(evt.getNewValue() instanceof JComponent)) {
return;
}
JComponent focused = (JComponent) evt.getNewValue();
if (content.isAncestorOf(focused)) {
System.out.println("Scrolling to " + focused.getName());
focused.scrollRectToVisible(focused.getBounds());
}
}
});
JFrame window = new JFrame("Follow focus");
window.setContentPane(new JScrollPane(content));
window.setSize(200, 200);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
});
}
}
If you run this example, you'll notice it doesn't work very well. It does get the focus change notifications, but the call to scrollRectToVisible doesn't appear to have any effect. In my app (which is too complex to show here), scrollRectToVisible works about half the time when I tab into something outside of the viewport.
Is there an established way to solve this problem? If it makes any difference, the Swing app is built on Netbeans RCP (and most of our customers run Windows).
My comment to the other answer:
scrollRectToVisible on the component itself is the whole point of that
method ;-) It's passed up the hierarchy until a parent doing the
scroll is found
... except when the component itself handles it - as JTextField does: it's implemented to scroll horizontally to make the caret visible. The way out is to call the method on the field's parent.
Edit
just for clarity, the replaced line is
content.scrollRectToVisible(focused.getBounds());
you have to take Rectangle from JPanel and JViewPort too, then compare, for example
notice (against down-voting) for final and nice output required some work for positions in the JViewPort
import java.awt.KeyboardFocusManager;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
//http://stackoverflow.com/questions/8245328/how-do-i-make-jscrollpane-scroll-to-follow-input-focus
public class FollowFocus {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final int ROWS = 100;
final JPanel content = new JPanel();
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
content.add(new JLabel(
"Thanks for helping out. Use tab to move around."));
for (int i = 0; i < ROWS; i++) {
JTextField field = new JTextField("" + i);
field.setName("field#" + i);
content.add(field);
}
final JScrollPane scroll = new JScrollPane(content);
KeyboardFocusManager.getCurrentKeyboardFocusManager().
addPropertyChangeListener("focusOwner", new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (!(evt.getNewValue() instanceof JComponent)) {
return;
}
JViewport viewport = (JViewport) content.getParent();
JComponent focused = (JComponent) evt.getNewValue();
if (content.isAncestorOf(focused)) {
System.out.println("Scrolling to " + focused.getName());
Rectangle rect = focused.getBounds();
Rectangle r2 = viewport.getVisibleRect();
content.scrollRectToVisible(new Rectangle(rect.x, rect.y, (int) r2.getWidth(), (int) r2.getHeight()));
}
}
});
JFrame window = new JFrame("Follow focus");
window.setContentPane(new JScrollPane(content));
window.setSize(200, 200);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
});
}
}
Here my short summary.
Add this to your Tools class:
public static void addOnEnter(Component c, Consumer<FocusEvent> onEnter) {
FocusListener fl = new FocusListener() {
#Override
public void focusGained(FocusEvent e) {
onEnter.accept(e);
}
#Override
public void focusLost(FocusEvent e) { }
};
c.addFocusListener(fl);
}
public static void scrollToFocus(FocusEvent e) {
((JComponent) e.getComponent().getParent()).scrollRectToVisible(
e.getComponent().getBounds());
}
and use it like this:
Tools.addOnEnter(component, Tools::scrollToFocus);
component can be JTextField, JButton, ...
One major issue in your code is:
focused.scrollRectToVisible(focused.getBounds());
You are calling scrollRectToVisible on the component itself! Presumably a typo.
Make your JScrollPane a final variable and call
scrollPane.getViewport().scrollRectToVisible(focused.getBounds());
Here jtextbox is the component you want to focus and jscrollpane is your scrollpane:
jScrollpane.getVerticalScrollBar().setValue(jtextbox.getLocation().x);