I have to do Java Project about elevator management system.
My problem is in 2 JPanel's. First is for JLabel's and Button's, one for each floor - I use layout because i want it to be responsive. Before program start I have dialog which I can set first and last floor level. Here's code:
public void setFloors() {
floorNavigation = new JPanel();
floorNavigation.setLayout(new BoxLayout(floorNavigation, BoxLayout.Y_AXIS));
floorNavigation.add(Box.createVerticalGlue());
for(JPanel p : floorPanels){
floorNavigation.add(p);
}
floorNavigation.add(Box.createVerticalGlue());
frame.add(floorNavigation, BorderLayout.LINE_START);
}
public void createFloorPanel() {
JPanel floorPanel;
for(JLabel l : floorLabels){
floorPanel = new JPanel(new FlowLayout());
floorPanel.add(l);
floorPanel.add(floorButtons.get(floorLabels.indexOf(l)));
floorPanels.add(floorPanel);
}
}
I want to make a elevator move animation to Y of each JLabel in the other JPanel. Any ideas how to do that? Things like getY() don't work.
If I understood correctly both the panel that contains the buttons and the panel that contains the animation are going to be positioned side by side, and thus going to have the same height. In this situation you can calculate the Y coordinate positions simply by knowing the current height of the panel and the number of buttons you have. Simply divide the height with number of buttons and multiply by number of floors you want to reach (note that this will give you the floors in reverse order, top down).
After you calculate those positions you can use simple linear animation to move your "elevator" (I am guessing an imported icon or image) to the corresponding positions of the buttons. Here is a simple template for the animation (the small circle represents your elevator image):
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class SimpleAnimation {
static int x;
static int y = 150;
static int floor_distance = 130;
static int animation_speed = 10; //less is faster
static MyDrawPanel panel = new MyDrawPanel();
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JButton buttonUp = new JButton("Floor Up");
final JButton buttonDown = new JButton("Floor Down");
buttonUp.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
(new AnimatorUp()).execute();
}
});
buttonDown.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
(new AnimatorDown()).execute();
}
});
frame.getContentPane().add(BorderLayout.NORTH, buttonUp);
frame.getContentPane().add(BorderLayout.SOUTH, buttonDown);
frame.getContentPane().add(panel);
frame.setSize(300,300);
frame.setVisible(true);
}
static class AnimatorUp extends SwingWorker<String, Object> {
#Override
public String doInBackground() {
try {
x = (int)(panel.getWidth() / 2);
for(int i = 0; i < floor_distance; i++) {
y--;
panel.repaint();
try{ Thread.sleep(animation_speed); } catch(Exception ex) { ex.printStackTrace(); }
}
} catch (Exception ignore) {
}
return "";
}
}
static class AnimatorDown extends SwingWorker<String, Object> {
#Override
public String doInBackground() {
try {
x = (int)(panel.getWidth() / 2);
for(int i = 0; i < floor_distance; i++) {
y++;
panel.repaint();
try{ Thread.sleep(animation_speed); } catch(Exception ex) { ex.printStackTrace(); }
}
} catch (Exception ignore) {
}
return "";
}
}
static class MyDrawPanel extends JPanel {
public void paintComponent(Graphics g) {
g.fillRect(0, 0, this.getWidth(), this.getHeight());
g.setColor(new Color(215,215,215));
x = (int)(panel.getWidth() / 2);
g.fillOval(x, y, 50, 50);
}
}
}
You can modify this code so the buttons trigger the animation which moves the "elevator" to a specific floor. By calculating the difference between current Y position of the "elevator" and the Y position of wanted floor, you can set the direction and range of animation to execute.
Related
I need a little help with java, I am new to the language hence I have no idea on how to implement such.
I have already made a basic 15;40 grid JLabel Image, thanks to this site as well, what I need help with is about how to make a player(Supposed to be an Image, also shown on the grid) move around using either WASD(I don't know if Ascii-approach works on java) or Arrow Keys.
Here's my code for the Grid
public class GUI {
static Scanner cns = new Scanner(System.in);
JFrame frame = new JFrame();
ImageIcon ImageIcon = new ImageIcon("Grass.png");
JLabel[][] grid;
public GUI(int width, int length) {
Container pane = frame.getContentPane();
frame.setLayout(new GridLayout(width,length));
grid = new JLabel[width][length];
for(int y = 0; y < length; y++) {
for(int x = 0; x < width; x++) {
grid[x][y] = new JLabel();
grid[x][y].setBorder(BorderFactory.createLineBorder(Color.black));
grid[x][y].setBorder(BorderFactory.createEmptyBorder());
}
}
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
for(int y = 0; y < length; y++) {
for(int x = 0; x < width; x++) {
pane.add(grid[x][y]= new JLabel(new ImageIcon("Grass.png")));
grid[x][y].setBorder(BorderFactory.createLineBorder(Color.black));
grid[x][y].setBorder(BorderFactory.createEmptyBorder());
frame.add(grid[x][y]);
}
}
}
public static void main(String[] args) {
new GUI(15, 40);
}
}
Couple of suggestions here:
1 - Make GUI extend JFrame and implement KeyListener.
You have to override KeyListener methods (keyPressed, keyReleased, and keyTyped) and you should override repaint() from JFrame.
The repaint method should call super.repaint() at the end to update frames.
2 - Have the fields of GUI store data about the things that need to be drawn.
You probably should store the grid, or what's in the grid as fields.
3 - The constructor should initialize, not render.
Rendering should be done in repaint(). The constructor should do something like this:
super();
JPanel frame = new JPanel();
add(frame);
pack();
addKeyListener(this);
repaint();
This answer is kinda unfinished but I'll look more into it and update this. Particularly, the JPanel is an element in the JFrame.
UPDATE: Here is a small working example. Use WASD to move the rectangle around the screen.
This is the JFrame.
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
public class MyJFrame extends JFrame implements KeyListener {
private MyJPanel frame;
public MyJFrame() {
super();
frame = new MyJPanel();
add(frame);
pack();
addKeyListener(this);
repaint();
}
public static void main(String[] args) {
MyJFrame window = new MyJFrame();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
window.setTitle("Test");
}
#Override
public void repaint() {
super.repaint();
}
#Override
public void keyPressed(KeyEvent e) {
frame.keyPressed(e);
repaint();
}
#Override
public void keyTyped(KeyEvent e) { }
#Override
public void keyReleased(KeyEvent e) { }
}
This is the JPanel.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import javax.swing.JPanel;
public class MyJPanel extends JPanel {
private int x = 0;
private int y = 0;
public MyJPanel() {
setPreferredSize(new Dimension(200,200));
}
#Override
public void update(Graphics g) {
paint(g);
}
#Override
public void paint(Graphics g) {
g.setColor(Color.red);
g.drawRect(x,y,20,20);
}
public void keyPressed(KeyEvent e) {
int k = e.getKeyCode();
switch (k) {
case KeyEvent.VK_D:
x++;
break;
case KeyEvent.VK_A:
x--;
break;
case KeyEvent.VK_W:
y--;
break;
case KeyEvent.VK_S:
y++;
break;
}
}
}
Good luck!
I'm wondering why my game objects are not showing. I am able to draw to the JPanel if I use g.drawRect outside of the for-each loop, but calling PipeObject inside of the loop doesn't seem to work for me. Am I doing something wrong here? Thanks for any help or solutions.
This is an updated version of my old question, which can be found here.
UPDATE - The pipes are drawing correctly, but not moving to the left by calling pipe.move().
Game
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.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("PipeGame");
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);
}
}
Pipes
import java.util.*;
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Pipes extends JPanel {
boolean gameNotOver = true;
int x1 = 754;
int y1 = setHeightVal();
int y2 = setHeightVal();
int y3 = setHeightVal();
List<PipeObject> pipes = new ArrayList<PipeObject>();
public Pipes() {
pipes.add(new PipeObject(x1, y1));
pipes.add(new PipeObject(x1 + 300, y2));
pipes.add(new PipeObject(x1 + 600, y3));
}
public void drawEndlessPipes() {
if (gameNotOver) {
Timer pipeSpeed = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (PipeObject pipe : pipes) {
pipe.move();
}
}
});
pipeSpeed.start();
}
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (PipeObject pipe : pipes) {
pipe.drawPipe(g);
}
}
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;
}
public Dimension getPreferredSize() {
return new Dimension(751,501);
}
}
PipeObject
import java.awt.Graphics;
public class PipeObject {
//Declare and initialiaze variables
int x1;
int x2 = 75; //pipe width, total is 83
int y1 = -1; // Y should be -1
int y2;
int gap = 130; //gap height
public PipeObject(int x, int y) {
this.x1 = x;
this.y2 = y;
}
public void drawPipe(Graphics g/*, int x1, int y2*/) {
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;
}
}
You are painting the pipes beyond the visual bounds of the panel. The dimension of Pipes panel is 751x501, but the pipes begin at x1 = 754. Try changing x1 to 1 in Pipes and you should see the three pipes. Or, you can maximize the frame, and you should see the missing pipes far on the right side.
As CyberStorm said, you never called drawEndlessPipes() to start the swing timer at all.
Also I don't see a repaint() call to Pipes panel inside your timer, just changing the x value won't magically move anything, you need to call repaint() in Pipes.java to make it repaint the screen and show animation.
So here it goes:
Modified Runnable in Game.java
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("PipeGame");
f.add(gui);
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See http://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);
((Pipes) pipes).drawEndlessPipes();
}
};
Modified drawEndlessPipes() in Pipes.java
public void drawEndlessPipes() {
if (gameNotOver) {
Timer pipeSpeed = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (PipeObject pipe : pipes) {
pipe.move();
Pipes.this.repaint();
}
}
});
pipeSpeed.start();
}
}
I'm about to embark on writing a (I think it will be) quick program involving a pulling headlines from a stock website and inserting them into an ArrayList of a class I'm going to create called Funds/Tickers/WhatHaveYou. That's not the huge problem. The main problem I have is this:
I want to have just one window, and that window will just continuously scroll the headlines. You can click on the headlines and that will bring up a browser to the article to read.
I initially thought you could do this with JLabels, but I did a few experiments and I could only get Strings to move accross the screen, not JLabels/JButtons/clickable things. Is there a way I can have JLabels or hyperlinks scroll accross the window in Java?
Cheers,
David
I want to have just one window, and that window will just continuously scroll the headlines.
You can use the Marquee Panel for this.
You can click on the headlines and that will bring up a browser to the article to read.
The above class doesn't support clicking on the components. However, I have been playing around a little with this concept. It seems to work OK when using a JLabel but I don't think it works for other components.
1) First you need to add a couple of methods to the MarqueePanel class that will translate the mouse point to map to the real component on the panel (in case you are using the wrapping option):
#Override
public Component getComponentAt(int x, int y)
{
Point translated = getTranslatedPoint(x, y);
for (Component c: getComponents())
{
if (c.getBounds().contains(translated))
return c;
}
return null;
}
public Point getTranslatedPoint(int x, int y)
{
int translatedX = x + scrollOffset;
if (isWrap())
{
int preferredWidth = super.getPreferredSize().width;
preferredWidth += getWrapAmount();
translatedX = translatedX % preferredWidth;
}
return new Point(translatedX, y);
}
2) Then you can add a MouseListener to the panel. With code like the following you can now access the label that was clicked:
marquee.addMouseListener( new MouseAdapter()
{
#Override
public void mousePressed(MouseEvent e)
{
Component c = marquee.getComponentAt(e.getPoint());
if (c == null) return;
if (c insstanceof JLabel)
{
JLabel label = (JLabel)c;
System.out.println(label.getText());
}
}
});
In this example, entries from an RSS feed are added to a JTextPane. A HyperlinkListener can be added to receive events representing clicks on hyerlinks.
Maybe what you need is a a EditorPane:
http://docs.oracle.com/javase/tutorial/uiswing/components/editorpane.html
Hope it helps.
I was working on this yesterday, when you first posted your question. I got tired and stopped messing with it. Basically what I did was use JPanel, and set their locations with each timer tick. I doubt it's the most optimal way, and I got really tired of trying to fix some of the problem, so I just stopped. You're free to play around with it, see if you can get any better results from it. The most frustrating part is when you click the panel, everything shifts for a quick second. Hopefully you can do something with it, or at least get some ideas. I just didn't want to scrap it, in case maybe you could make something of it.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class TestLabelMove {
List<MovingLabel> labels;
private int count = 1;
private JLabel statusLabel;
private final int SEPARATION = 100;
private final int SCREEN_W = 800;
int XLOC = SCREEN_W;
public TestLabelMove() {
statusLabel = new JLabel("Status");
statusLabel.setHorizontalAlignment(JLabel.CENTER);
JFrame frame = new JFrame("Test Labels");
frame.add(statusLabel, BorderLayout.CENTER);
frame.add(new LabelPanel(), BorderLayout.SOUTH);
frame.setSize(800, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private class LabelPanel extends JPanel {
private static final int INC = 5;
public LabelPanel() {
labels = new LinkedList<>();
for (int i = 0; i < 8; i++) {
MovingLabel label = new MovingLabel(XLOC);
labels.add(label);
XLOC -= SEPARATION;
add(label);
}
Timer timer = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (MovingLabel label : labels) {
if (isWrap(label.getXLoc())) {
label.setXLoc(SCREEN_W);
label.setLocation(label.getXLoc(), 0);
} else {
label.setLocation(label.getXLoc(), 0);
label.setXLoc(label.getXLoc() - INC);
}
}
}
});
timer.start();
}
}
public boolean isWrap(int x) {
return x <= -40;
}
private class MovingLabel extends JPanel {
int xLoc;
String phrase;
public MovingLabel(int xLoc) {
this.xLoc = xLoc;
phrase = "Panel " + count;
count++;
this.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
statusLabel.setText(phrase);
repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Font font = new Font("Helvetica", Font.BOLD, 14);
FontMetrics fm = g.getFontMetrics(font);
int w = fm.stringWidth(phrase);
int h = fm.getAscent();
g.setFont(font);
g.drawString(phrase, getWidth()/2 - w/2, getHeight()/2 + h/2);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 20);
}
public void setXLoc(int xLoc) {
this.xLoc = xLoc;
}
public int getXLoc() {
return xLoc;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TestLabelMove();
}
});
}
}
I'm not sure how I would fix the errors in my program and how I would highlight the option the user is hovering on. I want it to highlight the code for each position, i.e position 1 would be highlighted(as a different color) to start game,etc. and up/down would change position and I would change the position with up ,down, left, right. This is what I have so far. At the moment its bugged and when compiled with my window it comes out as:
Which works for the main game and altered for this titleboard, what am I doing wrong and how do I fix it?
TitleBoard class
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.ArrayList;
//sound + file opening
import java.io.*;
import javax.sound.sampled.*;
public class TitleBoard extends JPanel implements ActionListener{
private ArrayList<String> OptionList;
private Image background;
private ImageIcon bgImageIcon;
private String cheatString;
private int position;
private Timer timer;
public TitleBoard(){
setFocusable(true);
addKeyListener(new TAdapter());
bgImageIcon = new ImageIcon("");
background = bgImageIcon.getImage();
String[] options = {"Start Game","Options","Quit"};
OptionList = new ArrayList<String>();
optionSetup(options);
position = 1;
timer = new Timer(8, this);
timer.start();
/*
1 mod 3 =>1 highlight on start
2 mod 3 =>2 highlight on options
3 mod 3 =>0 highlight on quit
*/
try{
Font numFont = Font.createFont(Font.TRUETYPE_FONT,new File("TwistedStallions.ttf"));
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
ge.registerFont(numFont);
setFont(numFont.deriveFont(24f)); //adjusthislater
}catch(IOException|FontFormatException e){
e.printStackTrace();
}
}
private void optionSetup(String[] s){
for(int i=0; i<s.length;i++) {
OptionList.add(s[i]);
}
}
public void paint(Graphics g){
super.paint(g);
Graphics g2d = (Graphics2D)g;
g2d.drawImage(background,0,0,this);
for (int i=0;i<OptionList.size();i++){
g2d.drawString(OptionList.get(i),200,120+120*i);
}/*
g2d.drawString(OptionList.get(1),400,240);
g2d.drawString(OptionList.get(2),400,360);
//instructions on start screen maybe??
//800x500
//highlighting*/
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
public void actionPerformed(ActionEvent e){
repaint();
}
public class TAdapter extends KeyAdapter {
public void keyPressed(KeyEvent e){
if(e.getKeyCode() == KeyEvent.VK_UP||
e.getKeyCode() == KeyEvent.VK_RIGHT){
position++;
}
if(e.getKeyCode() == KeyEvent.VK_DOWN||
e.getKeyCode() == KeyEvent.VK_LEFT){
position--;
}
}
}
}
Window Class
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Window extends JFrame{
public Window(){
int width = 800, height = 600;
//TO DO: make a panel in TITLE MODE
///////////////////////////////////
//panel in GAME MODE.
add(new TitleBoard());
//set default close
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(width,height);
//centers window
setLocationRelativeTo(null);
setTitle("Title");
setResizable(false);
setVisible(true);
}
public static void main(String[] args){
new Window();
}
}
There are any number of ways you might achieve this, for example, you could use some kind of delegation model.
That is, rather then trying to mange of each element in a single method (or methods), you could devise a delegate which provide a simple interface method that the paint method would call and it would know how to do the rest.
For example, Swing uses this type of concept with it's cell renderers for JList, JTable and JTree.
For example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class MyAwesomeMenu {
public static void main(String[] args) {
new MyAwesomeMenu();
}
public MyAwesomeMenu() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private List<String> menuItems;
private String selectMenuItem;
private String focusedItem;
private MenuItemPainter painter;
private Map<String, Rectangle> menuBounds;
public TestPane() {
setBackground(Color.BLACK);
painter = new SimpleMenuItemPainter();
menuItems = new ArrayList<>(25);
menuItems.add("Start Game");
menuItems.add("Options");
menuItems.add("Exit");
selectMenuItem = menuItems.get(0);
MouseAdapter ma = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
String newItem = null;
for (String text : menuItems) {
Rectangle bounds = menuBounds.get(text);
if (bounds.contains(e.getPoint())) {
newItem = text;
break;
}
}
if (newItem != null && !newItem.equals(selectMenuItem)) {
selectMenuItem = newItem;
repaint();
}
}
#Override
public void mouseMoved(MouseEvent e) {
focusedItem = null;
for (String text : menuItems) {
Rectangle bounds = menuBounds.get(text);
if (bounds.contains(e.getPoint())) {
focusedItem = text;
repaint();
break;
}
}
}
};
addMouseListener(ma);
addMouseMotionListener(ma);
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "arrowDown");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "arrowUp");
am.put("arrowDown", new MenuAction(1));
am.put("arrowUp", new MenuAction(-1));
}
#Override
public void invalidate() {
menuBounds = null;
super.invalidate();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (menuBounds == null) {
menuBounds = new HashMap<>(menuItems.size());
int width = 0;
int height = 0;
for (String text : menuItems) {
Dimension dim = painter.getPreferredSize(g2d, text);
width = Math.max(width, dim.width);
height = Math.max(height, dim.height);
}
int x = (getWidth() - (width + 10)) / 2;
int totalHeight = (height + 10) * menuItems.size();
totalHeight += 5 * (menuItems.size() - 1);
int y = (getHeight() - totalHeight) / 2;
for (String text : menuItems) {
menuBounds.put(text, new Rectangle(x, y, width + 10, height + 10));
y += height + 10 + 5;
}
}
for (String text : menuItems) {
Rectangle bounds = menuBounds.get(text);
boolean isSelected = text.equals(selectMenuItem);
boolean isFocused = text.equals(focusedItem);
painter.paint(g2d, text, bounds, isSelected, isFocused);
}
g2d.dispose();
}
public class MenuAction extends AbstractAction {
private final int delta;
public MenuAction(int delta) {
this.delta = delta;
}
#Override
public void actionPerformed(ActionEvent e) {
int index = menuItems.indexOf(selectMenuItem);
if (index < 0) {
selectMenuItem = menuItems.get(0);
}
index += delta;
if (index < 0) {
selectMenuItem = menuItems.get(menuItems.size() - 1);
} else if (index >= menuItems.size()) {
selectMenuItem = menuItems.get(0);
} else {
selectMenuItem = menuItems.get(index);
}
repaint();
}
}
}
public interface MenuItemPainter {
public void paint(Graphics2D g2d, String text, Rectangle bounds, boolean isSelected, boolean isFocused);
public Dimension getPreferredSize(Graphics2D g2d, String text);
}
public class SimpleMenuItemPainter implements MenuItemPainter {
public Dimension getPreferredSize(Graphics2D g2d, String text) {
return g2d.getFontMetrics().getStringBounds(text, g2d).getBounds().getSize();
}
#Override
public void paint(Graphics2D g2d, String text, Rectangle bounds, boolean isSelected, boolean isFocused) {
FontMetrics fm = g2d.getFontMetrics();
if (isSelected) {
paintBackground(g2d, bounds, Color.BLUE, Color.WHITE);
} else if (isFocused) {
paintBackground(g2d, bounds, Color.MAGENTA, Color.BLACK);
} else {
paintBackground(g2d, bounds, Color.DARK_GRAY, Color.LIGHT_GRAY);
}
int x = bounds.x + ((bounds.width - fm.stringWidth(text)) / 2);
int y = bounds.y + ((bounds.height - fm.getHeight()) / 2) + fm.getAscent();
g2d.setColor(isSelected ? Color.WHITE : Color.LIGHT_GRAY);
g2d.drawString(text, x, y);
}
protected void paintBackground(Graphics2D g2d, Rectangle bounds, Color background, Color foreground) {
g2d.setColor(background);
g2d.fill(bounds);
g2d.setColor(foreground);
g2d.draw(bounds);
}
}
}
For here, you could add ActionListener
When a GUI needs a button, use a JButton! The JButton API allows the possibility to add icons for many different circumstances. This example shows different icons for the standard icon, the hover icon, and the pressed icon. Your GUI would obviously use icons with text on them for the required effect.
The icons are pulled directly (hot-linked) from Example images for code and mark-up Q&As.
Standard
Hover over triangle
Press triangle
Code
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.net.URL;
public class IconHoverFocusIndication {
// the GUI as seen by the user (without frame)
// swap the 1 and 0 for single column
JPanel gui = new JPanel(new GridLayout(1,0,50,50));
public static final int GREEN = 0, YELLOW = 1, RED = 2;
String[][] urls = {
{
"http://i.stack.imgur.com/T5uTa.png",
"http://i.stack.imgur.com/IHARa.png",
"http://i.stack.imgur.com/wCF8S.png"
},
{
"http://i.stack.imgur.com/gYxHm.png",
"http://i.stack.imgur.com/8BGfi.png",
"http://i.stack.imgur.com/5v2TX.png"
},
{
"http://i.stack.imgur.com/1lgtq.png",
"http://i.stack.imgur.com/6ZXhi.png",
"http://i.stack.imgur.com/F0JHK.png"
}
};
IconHoverFocusIndication() throws Exception {
// adjust to requirement..
gui.setBorder(new EmptyBorder(15, 30, 15, 30));
gui.setBackground(Color.BLACK);
Insets zeroMargin = new Insets(0,0,0,0);
for (int ii = 0; ii < 3; ii++) {
JButton b = new JButton();
b.setBorderPainted(false);
b.setMargin(zeroMargin);
b.setContentAreaFilled(false);
gui.add(b);
URL url1 = new URL(urls[ii][GREEN]);
BufferedImage bi1 = ImageIO.read(url1);
b.setIcon(new ImageIcon(bi1));
URL url2 = new URL(urls[ii][YELLOW]);
BufferedImage bi2 = ImageIO.read(url2);
b.setRolloverIcon(new ImageIcon(bi2));
URL url3 = new URL(urls[ii][RED]);
BufferedImage bi3 = ImageIO.read(url3);
b.setPressedIcon(new ImageIcon(bi3));
}
}
public JComponent getGUI() {
return gui;
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
try {
IconHoverFocusIndication ihfi =
new IconHoverFocusIndication();
JFrame f = new JFrame("Button Icons");
f.add(ihfi.getGUI());
// 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);
} catch (Exception ex) {
ex.printStackTrace();
}
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
My component is bigger than the screen and parts of it are not shown (I will use scrollbars).
When I receive a call in paintComponent(g) how do I know what area should I paint?
I'm not sure if this is what you mean, but the problem is you will have to call repaint() on the JScrollPane each time you get a call in paintComponent(Graphics g) of the JPanel or else updates on the JPanel will not be visible in the JScrollPane.
Also I see you want to use JScrollBar (or maybe you confused the terminology)? I'd recommend a JScrollPane
I made a small example which is a JPanel with a grid that will change its colour every 2 seconds (Red to black and vice versa). The JPanel/Grid is larger then the JScrollPane; regardless we have to call repaint() on the JScrollPane instance or else the grid wont change colour:
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.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) throws Exception {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Test().createAndShowUI();
}
});
}
private void createAndShowUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initComponents(frame);
frame.setPreferredSize(new Dimension(400, 400));
frame.pack();
frame.setVisible(true);
}
private void initComponents(JFrame frame) {
JScrollPane jsp = new JScrollPane();
jsp.setViewportView(new Panel(800, 800, jsp));
frame.getContentPane().add(jsp);
}
}
class Panel extends JPanel {
private int across, down;
private Panel.Tile[][] tiles;
private Color color = Color.black;
private final JScrollPane jScrollPane;
public Panel(int width, int height, JScrollPane jScrollPane) {
this.setPreferredSize(new Dimension(width, height));
this.jScrollPane = jScrollPane;
createTiles();
changePanelColorTimer();//just something to do to check if its repaints fine
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < across; i++) {
for (int j = 0; j < down; j++) {
g.setColor(color);
for (int k = 0; k < 5; k++) {
g.drawRect(tiles[i][j].x + k, tiles[i][j].y + k, tiles[i][j].side - k * 2, tiles[i][j].side - 2 * k);
}
}
}
updateScrollPane();//refresh the pane after every paint
}
//calls repaint on the scrollPane instance
private void updateScrollPane() {
jScrollPane.repaint();
}
private void createTiles() {
across = 13;
down = 9;
tiles = new Panel.Tile[across][down];
for (int i = 0; i < across; i++) {
for (int j = 0; j < down; j++) {
tiles[i][j] = new Panel.Tile((i * 50), (j * 50), 50);
}
}
}
//change the color of the grid lines from black to red and vice versa every 2s
private void changePanelColorTimer() {
Timer timer = new Timer(2000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (color == Color.black) {
color = Color.red;
} else {
color = Color.black;
}
}
});
timer.setInitialDelay(2000);
timer.start();
}
private class Tile {
int x, y, side;
public Tile(int inX, int inY, int inSide) {
x = inX;
y = inY;
side = inSide;
}
}
}
In the Panel class if we comment the line updateScrollPane(); in paintComponent(Graphics g) we wont see the grid change colour.
You can find out the area that actually has to be painted by querying the clip bounds of the Graphics object.
The JavaDoc seems to be a bit out-dated for this method: It says, that it may return a null clip. However, this is obviously never the case (and other Swing classes also rely on the clip never being null!).
The follwing MCVE illustrates the difference between using a the clip or painting the whole component:
It contains a JPanel with a size of 800x800 in a scroll pane. The panel paints a set of rectangles, and prints how many rectangles have been painted.
One can use the "Use clip bounds" checkbox to enable and disable using the clip. When the clip is used, only the visible area of the panel is repainted, and the number of rectangles is much lower. (Note that the test whether a rectangle has to be painted or not is rather simple here: It only performs an intersection test of the rectangle with the visible region. For a real application, one would directly use the clip bounds to find out which rectangles have to be painted).
This example also shows some of the tricky internals of scroll panes: When the blinking is switched off, and the scroll bars are moved, one can see that - although the whole visible area changes - only a tiny area actually has to be repainted (namely the area that has become visible due to the scrolling). The other part is simply moved as-it-is, by blitting the previous contents. This behavior can be modified with JViewport.html#setScrollMode.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class PaintRegionTest
{
public static void main(String[] args) throws Exception
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final PaintRegionPanel paintRegionPanel = new PaintRegionPanel();
paintRegionPanel.setPreferredSize(new Dimension(800, 800));
final Timer timer = new Timer(1000, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
paintRegionPanel.changeColor();
}
});
timer.setInitialDelay(1000);
timer.start();
JScrollPane scrollPane = new JScrollPane(paintRegionPanel);
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
JPanel controlPanel = new JPanel(new FlowLayout());
final JCheckBox blinkCheckbox = new JCheckBox("Blink", true);
blinkCheckbox.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
if (blinkCheckbox.isSelected())
{
timer.start();
}
else
{
timer.stop();
}
}
});
controlPanel.add(blinkCheckbox);
final JCheckBox useClipCheckbox = new JCheckBox("Use clip bounds");
useClipCheckbox.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
paintRegionPanel.setUseClipBounds(
useClipCheckbox.isSelected());
}
});
controlPanel.add(useClipCheckbox);
frame.getContentPane().add(controlPanel, BorderLayout.SOUTH);
frame.setPreferredSize(new Dimension(400, 400));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
class PaintRegionPanel extends JPanel
{
private Color color = Color.BLACK;
private boolean useClipBounds = false;
void setUseClipBounds(boolean useClipBounds)
{
this.useClipBounds = useClipBounds;
}
void changeColor()
{
if (color == Color.BLACK)
{
color = Color.RED;
}
else
{
color = Color.BLACK;
}
repaint();
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setColor(color);
Rectangle clipBounds = g.getClipBounds();
Rectangle ownBounds = new Rectangle(0,0,getWidth(),getHeight());
System.out.println("clipBounds: " + clipBounds);
System.out.println(" ownBounds: " + ownBounds);
Rectangle paintedRegion = null;
if (useClipBounds)
{
System.out.println("Using clipBounds");
paintedRegion = clipBounds;
}
else
{
System.out.println("Using ownBounds");
paintedRegion = ownBounds;
}
int counter = 0;
// This loop performs a a simple test see whether the objects
// have to be painted. In a real application, one would
// probably use the clip information to ONLY create the
// rectangles that actually have to be painted:
for (int x = 0; x < getWidth(); x += 20)
{
for (int y = 0; y < getHeight(); y += 20)
{
Rectangle r = new Rectangle(x + 5, y + 5, 10, 10);
if (r.intersects(paintedRegion))
{
g.fill(r);
counter++;
}
}
}
System.out.println("Painted "+counter+" rectangles ");
}
}
An aside: For many application cases, such an "optimization" should hardly be necessary. The painted elements are intersected against the clip anyhow, so one will probably not gain much performance. When "preparing" the elements to be painted is computationally expensive, one can consider this as one option. (In the example, "preparing" refers to creating the Rectangle instance, but there may be more complicated patterns). But in these cases, there may also be more elegant and easier solutions than manually checking the clip bounds.
All answers are wrong. So I decided to answer the question despide the fact that the question is two years old.
I believe that the correct answer is calling g.getClipBounds() inside of paintComponent(Graphics g) method. It will return the rectangle in the control's coordinate system of the area which is invalidated and must be redrawn.