This question already has answers here:
Animation on Transparent-background JFrame Linux
(3 answers)
Closed 7 years ago.
I want to create a hud-like ui by using draggable JComponents on a transparent background. Minimal example of what I have so far:
import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class TransparentFrame
extends JFrame {
public TransparentFrame() {
setLayout(null);
setUndecorated(true);
setBackground(new Color(1, 1, 1, 0.0f));
setLocationRelativeTo(null);
setSize(600, 500);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JLabel label = new JLabel("ABC");
label.setBounds(100, 100, 60, 30);
label.setOpaque(true);
label.setBackground(Color.RED);
label.setForeground(Color.WHITE);
DraggableComponentListener listener = new DraggableComponentListener(label);
label.addMouseListener(listener);
label.addMouseMotionListener(listener);
add(label);
setVisible(true);
}
public static void main(String[] args) {
new TransparentFrame();
}
}
class DraggableComponentListener
extends MouseAdapter {
private Component component;
private volatile int screenX = 0;
private volatile int screenY = 0;
private volatile int componentX = 0;
private volatile int componentY = 0;
public DraggableComponentListener(Component component) {
this.component = component;
}
#Override
public void mousePressed(MouseEvent e) {
screenX = e.getXOnScreen();
screenY = e.getYOnScreen();
componentX = component.getX();
componentY = component.getY();
}
#Override
public void mouseDragged(MouseEvent e) {
int deltaX = e.getXOnScreen() - screenX;
int deltaY = e.getYOnScreen() - screenY;
component.setLocation(componentX + deltaX, componentY + deltaY);
}
}
When the label is dragged, it leaves traces behind: it is painted at all positions it has been at. I tried adding the label to a JPanel, the panel to the frame and overriding the panel's paintComponent method as described here: https://tips4java.wordpress.com/2009/05/31/backgrounds-with-transparency/, the result was the same.
Any suggestions?
Unfortunately, it is working properly on my machine (doesn't leave any traces). Try adding a frame.repaint() after the location changed of the component. Use the repaint variation that takes the paint area for performance.
Related
I'm making an application for creating convex
polygon.
I imagined it to be so that I first set the vertices of the polygon and then create it.
I was able to make the addition of points (vertices). Now I need help connecting the dots with a line.
This is what it looks like:
It looks like this
And I would like when I click the Draw Polygon button that these points connect and it looks like a polygon, like this:
and it should look like this when I click the button
Here's the code so you can run it yourself:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;
public class MyPaint{
public static void main(String[] args){
final PadDraw drawPad = new PadDraw();
JFrame frame = new JFrame("Draw Polygon");
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(100, 100, 450, 300);
Container contentPane = frame.getContentPane();
((JComponent) contentPane).setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
frame.setContentPane(contentPane);
JPanel buttonPanel = new JPanel();
buttonPanel.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));
contentPane.add(buttonPanel, BorderLayout.SOUTH);
JButton buttonDrawPolygon = new JButton("Draw Polygon");
buttonPanel.add(buttonDrawPolygon);
JButton buttonReset = new JButton("Reset");
buttonPanel.add(buttonReset);
contentPane.add(drawPad, BorderLayout.CENTER);
}
}
class PadDraw extends JComponent{
private Image image;
private Graphics2D graphics2D;
private int currentX , currentY , oldX , oldY ;
public PadDraw(){
addMouseListener(new MouseAdapter(){
public void mousePressed(MouseEvent e){
oldX = e.getX();
oldY = e.getY();
}
});
addMouseListener(new MouseAdapter(){
public void mousePressed(MouseEvent e){
currentX = e.getX();
currentY = e.getY();
if(graphics2D != null) {
graphics2D.drawLine(oldX, oldY, currentX, currentY);
repaint();
oldX = currentX;
oldY = currentY;
}
System.out.println(oldX + " " + oldY);
}
});
}
public void paintComponent(Graphics g){
if(image == null){
image = createImage(getSize().width, getSize().height);
graphics2D = (Graphics2D)image.getGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics2D.setStroke(new BasicStroke(5));
}
g.drawImage(image, 0, 0, null);
}
}
Introduction
Oracle has a helpful tutorial, Creating a GUI With Swing. Skip the Learning Swing with the NetBeans IDE section.
Here's your revised GUI
Here's the GUI with four points
Here's the GUI with a polygon
I'm not showing it, but the Reset button clears the drawing area.
Explanation
When I create a Swing GUI, I use the model-view-controller pattern. This pattern allows me to separate my concerns and focus on one part of the Swing application at a time.
A Swing application model consists of one or more plain Java getter/setter classes.
A Swing view consists of a JFrame and one or more JPanels.
Swing controllers are the listeners that are attached to JButtons and drawing JPanels.
Model
For this application, I created a PolygonModel class. The class holds a boolean that tells me whether or not to draw the polygon and a java.util.List of java.awt.Point instances. The Point class holds an X and Y int value.
View
All Swing applications must start with a call to the SwingUtilities invokeLater method. This method ensures that the Swing components are created and executed on the Event Dispatch Thread.
I broke up your main method into a couple of methods. I separate the creation of the JFrame from the creation of the JPanels. This allows me to separate my concerns and focus on one part of the GUI at a time.
The JFrame methods must be called in a specific order. This is the order I use for most of my Swing applications.
I changed your PadDraw class to extend a JPanel. I moved the MouseAdapter code to its own class. Your drawing panel should draw. Period. Nothing else.
The paintComponent method always starts with a call to the super.paintComponent method. This maintains the Swing paint chain and helps to eliminate unwanted drawing artifacts.
The drawing JPanel is cleared before every repaint. Therefore, you have to completely redraw your image each time. That's why we store the List of Point instances in the model.
Controller
I created three controller classes.
The PointListener class extends MouseAdapter. Notice how simple the mousePressed method becomes with an application model.
The two JButtons each have their own ActionListener. Since they are so simple, I made each of them lambdas.
Code
Here's the complete runnable code. I made all the additional classes inner classes so I could make them public and post the code as one block.
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.EtchedBorder;
public class PolygonImage implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new PolygonImage());
}
private final PolygonModel model;
private final PadDraw drawPad;
public PolygonImage() {
this.model = new PolygonModel();
this.drawPad = new PadDraw(this, model);
}
#Override
public void run() {
JFrame frame = new JFrame("Draw Polygon");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(drawPad, BorderLayout.CENTER);
frame.add(createButtonPanel(), BorderLayout.SOUTH);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createButtonPanel() {
JPanel buttonPanel = new JPanel();
buttonPanel
.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));
JButton buttonDrawPolygon = new JButton("Draw Polygon");
buttonDrawPolygon.addActionListener(event -> {
model.setConnectPoints(true);
repaint();
});
buttonPanel.add(buttonDrawPolygon);
JButton buttonReset = new JButton("Reset");
buttonReset.addActionListener(event -> {
model.setConnectPoints(false);
model.clearList();
repaint();
});
buttonPanel.add(buttonReset);
return buttonPanel;
}
public void repaint() {
drawPad.repaint();
}
public class PadDraw extends JPanel {
private static final long serialVersionUID = 1L;
private final PolygonModel model;
public PadDraw(PolygonImage view, PolygonModel model) {
this.model = model;
this.addMouseListener(new PointListener(view, model));
this.setPreferredSize(new Dimension(450, 300));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.black);
// Draw points
for (Point p : model.getPoints()) {
int radius = 6;
int diameter = radius + radius;
g2d.fillOval(p.x - radius, p.y - radius, diameter, diameter);
}
// Draw polygon
if (model.isConnectPoints()) {
g2d.setStroke(new BasicStroke(5));
List<Point> points = model.getPoints();
if (points.size() >= 1) {
Point old = points.get(0);
for (int index = 1; index < points.size(); index++) {
Point p = points.get(index);
g2d.drawLine(old.x, old.y, p.x, p.y);
old = p;
}
Point p = points.get(0);
g2d.drawLine(p.x, p.y, old.x, old.y);
}
}
}
}
public class PointListener extends MouseAdapter {
private final PolygonImage view;
private final PolygonModel model;
public PointListener(PolygonImage view, PolygonModel model) {
this.view = view;
this.model = model;
}
#Override
public void mousePressed(MouseEvent event) {
model.addPoint(event.getPoint());
view.repaint();
}
}
public class PolygonModel {
private boolean connectPoints;
private final List<Point> points;
public PolygonModel() {
this.points = new ArrayList<>();
this.connectPoints = false;
}
public void setConnectPoints(boolean connectPoints) {
this.connectPoints = connectPoints;
}
public boolean isConnectPoints() {
return connectPoints;
}
public void clearList() {
this.points.clear();
}
public void addPoint(Point point) {
this.points.add(point);
}
public List<Point> getPoints() {
return points;
}
}
}
I have the class with the Frame:
import java.awt.*;
import javax.swing.*;
public class Menu_Frame extends JFrame{
Menu_Panel menu_panel = new Menu_Panel();
public Menu_Frame(){
Toolkit kit = Toolkit.getDefaultToolkit();
Dimension screenSize = kit.getScreenSize();
int screenHeight = screenSize.height;
int screenWidth = screenSize.width;
setExtendedState(JFrame.MAXIMIZED_BOTH);
setPreferredSize(new Dimension(screenWidth, screenHeight));
setLocationByPlatform(true);
getContentPane().add(menu_panel);
}
}
Here i have the panel:
import java.awt.*;
import javax.swing.*;
public class Menu_Panel extends JPanel{
Play_Button play = new Play_Button();
public Menu_Panel() {
Toolkit kit = Toolkit.getDefaultToolkit();
Dimension screenSize = kit.getScreenSize();
int screenHeight = screenSize.height;
int screenWidth = screenSize.width;
setLayout(new BorderLayout());
setSize(screenWidth, screenHeight);
setLocation(0,0);
setBackground(Color.BLACK);
add(play);
}
}
And here i have the button class:
public class Play_Button extends JButton implements ActionListener{
private static final long serialVersionUID = -5408317246901761857L;
protected int size_x = 150;
protected int size_y = 75;
protected int location_x = 0;
protected int location_y = 0;
Font f= new Font("Helvetica", Font.BOLD, 30);
String title = "PLAY";
Menu_Frame frame;
Menu_Panel menu;
public Play_Button() {
setFont(new Font("Helvetica", Font.BOLD, 25));
setText(title);
Toolkit kit = Toolkit.getDefaultToolkit();
Dimension screenSize = kit.getScreenSize();
location_x = (screenSize.height/2)+180;
location_y = screenSize.width/5;
setBorder(new RoundedBorder(20));
setSize(size_x,size_y);
setLocation(location_x,location_y);
setBackground(Color.BLACK);
setForeground(Color.RED);
setFocusPainted(false);
setBorderPainted(false);
setContentAreaFilled(false);
setVisible(true);
JPanel panel = new JPanel();
panel.setSize(100,100);
panel.setLocation(100, 100);
panel.setBackground(Color.GREEN);
panel.setVisible(true);
addActionListener(this);
addMouseListener(new MouseAdapter() {
public void mouseEntered(MouseEvent e){
setBorderPainted(true);
setForeground(Color.RED);
}
public void mouseExited(MouseEvent e){
setBorderPainted(false);
}
});
}
#Override
public void actionPerformed(ActionEvent e) {
frame.getContentPane().remove(menu);
}
}
And Finally here i have the class for round the border of play_button:
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
import javax.swing.border.Border;
public class RoundedBorder implements Border {
private int radius;
RoundedBorder(int radius) {
this.radius = radius;
}
public Insets getBorderInsets(Component c) {
return new Insets(this.radius+1, this.radius+1, this.radius+2, this.radius);
}
public boolean isBorderOpaque() {
return true;
}
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
g.drawRoundRect(x, y, width-1, height-1, radius, radius);
}
}
Now, the panel doesn't disappear when i click the button, and i don't know why. I try with a CarLayout, with setVisible(false), but it doesn't work. How can i fix it?
Preambule...
I would advice against using Toolkit#getScreenSize this way, as it doesn't take into account additional OS elements, like the task bar or dock. As a general user recommendation, don't set the size of the main frame, instead simply allow the preferred size to be the packed size and expand the frame to it's OS maximum using setExtendedState as you have
setSize, setBounds, setLocation are all irrelevant while the components are under the control of a layout manager. Also, components are visible by default.
Core...
Removing a component is the opposite of adding one (no, seriously). Simply call remove on the container which contains the component, passing a reference of the component you want to remove. When you're done updating the UI, simply call revalidate and repaint on the container to trigger a layout and paint pass.
Having said that, generally speaking, it's much better to make use of a CardLayout, which provides you the ability to simply switch between different views.
See How to Use CardLayout for more details
The issue is with your actionPerformed method
public void actionPerformed(ActionEvent e)
{
frame.getContentPane().remove(menu);
}
frame and menu are declared fields of Play_Button, but are never set to anything
If you print out what they are, they should be null
When you create your button, make sure to set those two fields:
Menu_Panel menu_panel = new Menu_Panel();
menu_panel.play.menu = menu_panel;
menu_panel.play.frame = this;
While this may work, it is not great program flow
EDIT: leaving short code which shows issue:
Then label.setText("") is called and it has to change the text, i.e. scaler overcame 50 barrier the label dissapears for the mousewheelevent -> re-emerges on next event. Also a problem of the label not being present on the Load-up.
In actual program labels stack together in 1 spot if any of them has .setText() changing value or .setVisible changing (These two methods I need to be able to use without affecting labels positioning)
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.OverlayLayout;
public class brokenLabel extends JFrame
{
private JLayeredPane layerPane;
private JPanel breakingLabelPanel = new JPanel();
private JLabel label;
private int scaler = 50;
private int xstart = 200;
private int ystart = 200;
public static void main(String[] args)
{
#SuppressWarnings("unused")
brokenLabel program = new brokenLabel();
}
public brokenLabel()
{
// Frame setup used on the program
final JFrame frame = new JFrame("This is a slideshow");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
layerPane = new JLayeredPane();
frame.setContentPane(layerPane);
// Panel used (it's similar to what's added on the actual program)
breakingLabelPanel.setOpaque(true);
breakingLabelPanel.setLayout(new OverlayLayout(breakingLabelPanel));
breakingLabelPanel.setVisible(true);
breakingLabelPanel.setSize(getMaximumSize());
breakingLabelPanel.addMouseWheelListener(new MouseWheelListener(){
#Override
public void mouseWheelMoved(MouseWheelEvent arg0)
{
if (scaler > 5 || arg0.getWheelRotation() < 0)
scaler = scaler - 5*arg0.getWheelRotation();
setTexts();
}
});
//The testing label
label = new JLabel(new String("1"));
label.setLocation(xstart , ystart);
breakingLabelPanel.add(label);
frame.add(breakingLabelPanel,layerPane);
frame.setVisible(true);
//revalidate and repaint don't help make label visible on load-up
frame.revalidate();
frame.repaint();
}
public void setTexts(){
label.setLocation(xstart + scaler,ystart + 25);
if(scaler < 50)
{
label.setText("1");
}
else
{
label.setText("2");
}
}
}
What are several ways of detecting a key stroke without the need of focusing on the component that the event was implemented? Here's my idea on this:
Even without focusing on myComponent, upon pressing a key, the action should take part. ** Same question for the mousePressed event. A mouse click will be detected even when not clicking on the component.**
myComponent.addKeyListener( new KeyAdapter() {
#Override
public void keyPressed( KeyEvent e ){
// My action here
}
});
Upon answering Question1, can it also be done even if the application is running on background? Say I have a browser, every time I click or press a key, the given action will be executed.
I also accept suggestions to read as an answer. If your answer would be KeyBinding related, please do elaborate. All answer and comments will be greatly appreciated.
I used JNativeHooks examples here and it works perfectly fine. Any other method by just Java alone?
For the first question, regarding the KeyStroke thingy, I guess you can use KeyBinding instead of using KeyListener, that can give you the desired result, without the focus related issues of the component in question, though within the Java Dimensions.
In the example below, the focus is on the JTextField first, so if you will Press CTRL + D, then the paintAction thingy attached to the CustomPanel will work, even though the focus lies with the JTextField.
Though if you will use the setMnemonic() method for JButton, then the JButton will gain focus and will perform it's own action associated with it, which is to draw Ovals. This you can see by Pressing ALT + C, to see the desired effect. Again to perform the drawing related thingy, both the components in question don't need the focus, but still they respond to the KeyStrokes.
Here is the example code :
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;
public class SSCCE
{
private final int WIDTH = 500;
private final int HEIGHT = 500;
private CustomPanel customPanel;
private JButton circleButton;
private JTextField tfield;
private Random random;
private int mode;
private Action paintAction = new AbstractAction()
{
#Override
public void actionPerformed(ActionEvent ae)
{
mode = random.nextInt(3);
Color color = new Color(random.nextFloat(), random.nextFloat()
, random.nextFloat(), random.nextFloat());
customPanel.setValues(random.nextInt(WIDTH),
random.nextInt(HEIGHT), random.nextInt(WIDTH),
random.nextInt(HEIGHT), color, mode);
}
};
private ActionListener buttonAction = new ActionListener()
{
#Override
public void actionPerformed(ActionEvent ae)
{
Color color = new Color(random.nextFloat(), random.nextFloat()
, random.nextFloat(), random.nextFloat());
customPanel.setValues(random.nextInt(WIDTH),
random.nextInt(HEIGHT), random.nextInt(WIDTH),
random.nextInt(HEIGHT), color, 2);
}
};
public SSCCE()
{
random = new Random();
}
private void displayGUI()
{
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JPanel contentPane = new JPanel();
contentPane.setLayout(new BorderLayout(5, 5));
customPanel = new CustomPanel();
customPanel.getInputMap(
JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke(KeyEvent.VK_D
, InputEvent.CTRL_DOWN_MASK), "paintAction");
customPanel.getActionMap().put("paintAction", paintAction);
JPanel footerPanel = new JPanel();
circleButton = new JButton("Draw Circle");
circleButton.setMnemonic(KeyEvent.VK_C);
circleButton.addActionListener(buttonAction);
tfield = new JTextField(20);
tfield.setText("USELESS, just to get the focus for itself.");
tfield.requestFocusInWindow();
footerPanel.add(tfield);
footerPanel.add(circleButton);
contentPane.add(customPanel, BorderLayout.CENTER);
contentPane.add(footerPanel, BorderLayout.PAGE_END);
frame.setContentPane(contentPane);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String... args)
{
EventQueue.invokeLater(new Runnable()
{
#Override
public void run()
{
new SSCCE().displayGUI();
}
});
}
}
class CustomPanel extends JPanel
{
private final int WIDTH = 500;
private final int HEIGHT = 500;
private int mode = 0;
private Color colorShape;
private int x = 0;
private int y = 0;
private int width = 0;
private int height = 0;
public void setValues(int x, int y, int w, int h, Color color, int mode)
{
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.colorShape = color;
this.mode = mode;
repaint();
}
#Override
public Dimension getPreferredSize()
{
return (new Dimension(WIDTH, HEIGHT));
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(colorShape);
if (mode == 1)
g.fillRect(x, y, width, height);
else if (mode == 2)
g.fillOval(x, y, width, height);
}
}
Related to mousePressed() thingy, #mKorbel, had presented the whole thingy as usual in a delightful manner.
And regarding your second question, seems like you yourself had done some homework on that. Seems like either using what you showed in your question is the workaround for catching Operating System related events and transfer that to your Java Application or Java Native Interface, I guess might also can work for this.
all JComponent has method dispatchEvent,
you can to redirect mouse & key event from one JComponent to the another
for JButton to use doClick() instead
for example
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
public class LostMouseEvent {
private JPanel panel1;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new LostMouseEvent();
}
});
}
public LostMouseEvent() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
panel1 = new JPanel() {
private static final long serialVersionUID = 1L;
#Override
public Dimension getPreferredSize() {
return new Dimension(600, 400);
}
};
JPanel panel2 = new JPanel() {
private static final long serialVersionUID = 1L;
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 300);
}
};
JScrollPane pane = new JScrollPane(panel2);
panel1.setBorder(BorderFactory.createLineBorder(Color.blue));
panel2.setBorder(BorderFactory.createLineBorder(Color.green));
panel1.setLayout(new CircleLayout());
panel1.add(pane);
frame.add(panel1);
MouseListener rml = new RealMouseListener();
panel1.addMouseListener(rml);
MouseListener fml = new FakeMouseListener();
panel2.addMouseListener(fml);
frame.pack();
frame.setVisible(true);
}
});
}
private class RealMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent me) {
System.out.println(me);
Point point = me.getPoint();
System.out.println(me.getX());
System.out.println(me.getXOnScreen());
System.out.println(me.getY());
System.out.println(me.getYOnScreen());
}
}
private class FakeMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent me) {
JPanel panel2 = (JPanel) me.getSource();
MouseEvent newMe = SwingUtilities.convertMouseEvent(panel2, me, panel1);
System.out.println(newMe.getX());
System.out.println(newMe.getXOnScreen());
System.out.println(newMe.getY());
System.out.println(newMe.getYOnScreen());
panel1.dispatchEvent(me);
}
}
}
So I am creating a basic application that I want to have a JLabel at the bottom of the screen that starts at the left bottom corner and moves, animation style, to the right bottom corner in a set time, and a static image in the center. To do this, I created a JFrame with a JPanel using BorderLayout. There is a JLabel with an ImageIcon added to BorderLayout.CENTER and a JPanel at BorderLayout.SOUTH. My code, while hastily written and far from pretty, is:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.BorderFactory;
public class GameWindow extends JPanel{
private static JLabel mainWindow, arrowLabel, arrowBox;
protected static JFrame frame;
protected static JPanel arrows;
public static int x = 600;
public GameWindow(){
mainWindow = new JLabel("Center");
arrowLabel = new JLabel("Moving");
arrows = new JPanel();
arrows.setSize(600, 100);
arrows.setLayout(null);
arrowBox = new JLabel("");
arrowBox.setBounds(0, 0, 150, 100);
arrowBox.setPreferredSize(new Dimension(150, 100));
arrowBox.setBorder(BorderFactory.createLineBorder(Color.black));
arrows.add(arrowBox);
this.setSize(600,600);
this.setLayout(new BorderLayout());
this.add(mainWindow, BorderLayout.CENTER);
this.add(arrows, BorderLayout.SOUTH);
}
public static void main(String[] args)
{
GameWindow g = new GameWindow();
frame = new JFrame("Sword Sword Revolution");
frame.add(g);
frame.setSize(600,600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
Timer t = new Timer(1000, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
arrows.add(arrowLabel);
arrowLabel.setBounds(x, 100, 100, 100);
x-=50;
arrows.repaint();
frame.repaint();
}
});
t.start();
}
}
The ImageIcon in the center JLabel appears fine, and the empty JLabel with a border appears at the bottom, but I cannot get the second JLabel with the arrow image to show up on screen. Eventually I will change to scheduleAtFixedRate to continuously move the JLabel, but right now I can't even get the image to appear on screen.
I also understand that I will most likely not be able to use FlowLayout for this, as I understand it does not allow you to set the location of your components. I tried using null layout, but with null layout the empty JLabel with a border does not appear. I can barely make out the top of the border at the bottom edge of the frame, but even with setLocation I cannot get it to appear where I want it to.
Obviously, my thought process is flawed, so any help would be appreciated.
Your use of threading is all wrong for Swing applications. You should not be trying to add or remove components in a background thread but instead should use a Swing Timer to do this on the Swing event thread.
Also, what do you mean by:
I want to have a scrolling JLabel at the bottom of the screen
Please clarify the effect you're trying to achieve.
Also regarding,
I also understand that I will most likely not be able to use FlowLayout for this, as I understand it does not allow you to set the location of your components. I tried using null layout, but with null layout the empty JLabel with a border does not appear. I can barely make out the top of the border at the bottom edge of the frame, but even with setLocation I cannot get it to appear where I want it to.
No, don't use null layout for this situation. There are much better layout managers that can help you build your application in a cleaner more platform-independent manner.
Edit 3
Regarding:
To clarify, at the bottom of the screen I want a JLabel at the far right corner, then in the swing timer, the JLabel will gradually move to the left until it leaves the screen. If I could get setLocation to work, the basic premise would be to have a variable x set to 600, and then every second decrement x by say 50 and then redraw the JLabel at the new location on the screen. Basic animation.
I would create a JPanel for the bottom of the screen for the purposes of either holding your JLabel or displaying the image without a JLabel by overriding its paintComponent(...) method. If you use it as a container, then yes, its layout should be null, but the rest of the GUI should not be using null layout. The Swing Timer would simply change the JLabel's location and then call repaint() on its JPanel/container. If you go the latter route, you would draw the image in the JPanel's paintComponent(...) method using g.drawImage(myImage, x, y), and your timer would change x and/or y and call repaint() on the drawing JPanel.
Also, you likely do not want to keep adding a JLabel in your timer but rather simply moving the JLabel that's already displayed in the GUI.
Also, to avoid focus issues, don't use a KeyListener to capture keystroke input but rather use Key Bindings. Google will direct you to a great tutorial on this construct.
Edit 4
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.EnumMap;
import javax.imageio.ImageIO;
import javax.swing.*;
#SuppressWarnings("serial")
public class AnimateExample extends JPanel {
public static final String DUKE_IMG_PATH =
"https://duke.kenai.com/iconSized/duke.gif";
private static final int PREF_W = 800;
private static final int PREF_H = 800;
private static final int TIMER_DELAY = 20;
private static final String KEY_DOWN = "key down";
private static final String KEY_RELEASE = "key release";
public static final int TRANSLATE_SCALE = 3;
private static final String BACKGROUND_STRING = "Use Arrow Keys to Move Image";
private static final Font BG_STRING_FONT = new Font(Font.SANS_SERIF,
Font.BOLD, 32);
private EnumMap<Direction, Boolean> dirMap =
new EnumMap<AnimateExample.Direction, Boolean>(Direction.class);
private BufferedImage image = null;
private int imgX = 0;
private int imgY = 0;
private int bgStringX;
private int bgStringY;
public AnimateExample() {
for (Direction dir : Direction.values()) {
dirMap.put(dir, Boolean.FALSE);
}
try {
URL imgUrl = new URL(DUKE_IMG_PATH);
image = ImageIO.read(imgUrl);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
new Timer(TIMER_DELAY, new TimerListener()).start();
// here we set up our key bindings
int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition);
ActionMap actionMap = getActionMap();
for (final Direction dir : Direction.values()) {
// for the key down key stroke
KeyStroke keyStroke = KeyStroke.getKeyStroke(dir.getKeyCode(), 0,
false);
inputMap.put(keyStroke, dir.name() + KEY_DOWN);
actionMap.put(dir.name() + KEY_DOWN, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent arg0) {
dirMap.put(dir, true);
}
});
// for the key release key stroke
keyStroke = KeyStroke.getKeyStroke(dir.getKeyCode(), 0, true);
inputMap.put(keyStroke, dir.name() + KEY_RELEASE);
actionMap.put(dir.name() + KEY_RELEASE, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent arg0) {
dirMap.put(dir, false);
}
});
}
FontMetrics fontMetrics = getFontMetrics(BG_STRING_FONT);
int w = fontMetrics.stringWidth(BACKGROUND_STRING);
int h = fontMetrics.getHeight();
bgStringX = (PREF_W - w) / 2;
bgStringY = (PREF_H - h) / 2;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g.setFont(BG_STRING_FONT);
g.setColor(Color.LIGHT_GRAY);
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.drawString(BACKGROUND_STRING, bgStringX, bgStringY);
if (image != null) {
g.drawImage(image, imgX, imgY, this);
}
}
private class TimerListener implements ActionListener {
public void actionPerformed(java.awt.event.ActionEvent e) {
for (Direction dir : Direction.values()) {
if (dirMap.get(dir)) {
imgX += dir.getX() * TRANSLATE_SCALE;
imgY += dir.getY() * TRANSLATE_SCALE;
}
}
repaint();
};
}
enum Direction {
Up(KeyEvent.VK_UP, 0, -1), Down(KeyEvent.VK_DOWN, 0, 1), Left(
KeyEvent.VK_LEFT, -1, 0), Right(KeyEvent.VK_RIGHT, 1, 0);
private int keyCode;
private int x;
private int y;
private Direction(int keyCode, int x, int y) {
this.keyCode = keyCode;
this.x = x;
this.y = y;
}
public int getKeyCode() {
return keyCode;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
private static void createAndShowGui() {
AnimateExample mainPanel = new AnimateExample();
JFrame frame = new JFrame("Animate Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Which will create this GUI:
Three possibilities:
You can either use a library like SlidingLayout to create such transition with very few lines of code. You won't be able to tweak the animation but your life will be easier.
You can use an animation engine like the Universal Tween Engine to configure everything by hand and tweak the animation as much as you want (the first lib uses this one under the hood). Using such engine, you can animate what you want: positions, colors, font size, ...
You can code everything by hand and embrace the hell that is the animation world :)
In the end, you'll be able to quickly create animations like these (it's a tool I'm currently working on, used to configure eclipse projects for android dev using the LibGDX game framework):
I made these libraries to ease the pain that is UI animation (I love UI design :p). I released them open-source (free to use, license apache-2), hoping they may help some people too.
If you need help, there is a dedicated help forum for each library.
Very simple, look at this:
javax.swing.JLabel lb = new javax.swing.JLabel();
Image image = Toolkit.getDefaultToolkit().createImage("Your animated GIF");
ImageIcon xIcon = new ImageIcon(image);
xIcon.setImageObserver(this);
lb.setIcon(xIcon);