I am changing the bounds of a JPanel using a Timer to get a sliding effect when someone hovers over a particular rectangular area, this is working as expected. There are buttons in the JPanel and they have different MouseEvent behaviors. But SOMETIMES it starts lagging or slides very slowly. Please, can anyone suggest what can I do, to ensure it performs well every time?
EDITED:
The buttonsPanel have button in it. buttonsPanel is added to a JLayeredPane which have bounds as rect. When JLayeredPane or JButton button is hovered mouse events are triggered. Mouse events then triggers the timer to slide the buttonsPanel and bring to it view.
class MyActionListener implements ActionListener {
private static final int frames = 50;
int count = 0;
private final Timer timer = new Timer(1, this);
public void start() {
timer.start();
}
#Override
public void actionPerformed(ActionEvent ae) {
if (count >= frames) {
timer.stop();
} else {
count = count + 1;
buttonsPanel.setBounds(hidden_width - hidden_width * count / frames, 0, hidden_width, frameHeight);
}
}
}
class MyMouseListener extends MouseAdapter {
private MyActionListener timerListener;
#Override
public void mouseEntered(final MouseEvent event) {
final Color color = new Color(50, 50, 50);
if (event.getSource() instanceof JButton) {
final JButton button = (JButton) event.getSource();
button.setBackground(color);
buttonsPanel.setVisible(true);
} else if (!buttonsPanel.isVisible()) {
buttonsPanel.setVisible(true);
timerListener = new MyActionListener();
timerListener.start();
}
}
#Override
public void mouseExited(final MouseEvent event) {
Point point = new Point(0, 0);
if (event.getSource() instanceof JButton) {
final JButton button = (JButton) event.getSource();
point = button.getLocation();
button.setBackground(Windows8GUI.color);
}
Rectangle rect = new Rectangle(0, 0, hidden_width, frameHeight);
if (!rect.contains(point.x + event.getX(), point.y + event.getY())) {
buttonsPanel.setVisible(false);
buttonsPanel.setBounds(0, 0, hidden_width, frameHeight);
}
}
}
The primary issue is you are assuming that you have control over the paint engine. You don't, in fact, the paint engine is at the mercy of the OS. This means that you can jump up and down all you like, but until the OS and Java's paint engine are ready, nothing will happen.
In fact, if you call repaint repeatedly in quick succession you can find yourself flooding the paint engine with requests, slowing it down even further.
While not a solution to the whole problem, it will make your life vastly simpler take a look at the TimingFramework or Trident.
They are "animation" frameworks designed to take out the guess work and make your life easier. I personal use TimingFramework for all my animation and it works well.
The timing frameworks work on a % over time. That is, you give it a time for a cycle and the frameworks will return a % value based on how far through that cycle they are. You can apply interpolations, allowing you to effect how fast a portion of the animation will play.
Trident has also been designed to provide the means to call other methods on your behalf, so it could set the bounds of the button pane for you, based on values you supply it.
In fact, if you call repaint repeatedly in quick succession you can find yourself flooding the paint engine with requests, slowing it down even further.
I think this is the real problem. Try increasing the delay between the timer events from 1 to 10-50 and it should run a lot better.
For a lot of how-tos for building those (and a lot of other) effects, I can only recommend the Book Filthy Rich Clients. On this page you also find a lot of examples for those effects handled in the book.
Related
I would like to draw some circles every second and delete them all (or at least one) from the panel.
Here is the exisiting code:
public class DrawShape {
ShapePanel panel;
public DrawShape(ShapePanel panel) {
this.panel = panel;
Timer t = new Timer();
t.schedule(new TimerTask() {
long startTime = System.currentTimeMillis();
int secondsToRun = 3;
#Override
public void run() {
if (System.currentTimeMillis() - startTime > secondsToRun * 1000) {
panel.deleteCircle();
System.out.println("\t" + panel.circles.size());
cancel();
} else {
panel.addCircle(new Circle((int) (Math.random() * 200), (int) (Math.random() * 200)));
System.out.println(panel.circles.size());
}
}
}, 0, 1000);
}
}
If the time is greater than 3 seconds, delete all circles, else continue to draw circles on screen.
Here is the ShapePanel class:
public class ShapePanel extends JPanel {
public List<Circle> circles = new LinkedList<Circle>();
public ShapePanel() {
// Setting up the frame
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setVisible(true);
frame.setBackground(Color.black);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(this); // adding the panel to the frame
}
public void addCircle(Circle circle) {
circles.add(circle);
this.repaint();
}
public void deleteCircle() {
// circles.remove(circles.get(0));
circles.clear(); //remove them all
this.repaint();
}
#Override
public void paint(Graphics g) {
for (Circle c : circles) {
g.setColor(Color.GREEN);
g.drawOval(c.x, c.y, Circle.radius * 2, Circle.radius * 2);
}
}
}
When I call deleteCircle(), the circles should be removed from the list and repaint. I should end up with a blank screen with no circles in it. I think repaint doesn't work in this case.
P.S.: first time asking a question, so sorry if it's long one :D
So, two things jump out at me immediately.
Using java.util.Timer
Swing is NOT thread safe and since java.util.Timer runs in it's own thread, updating the UI (or in this case, something the UI relies on) could cause you random issues.
I'd consider using a javax.swing.Timer instead, as it is triggered inside the Event Dispatching Queue (and is generally simpler to use)
Not calling super.paint
You've failed to take into consideration what paint does and failed to take over it's responsibilities, which, in this case, would be to "prepare" the Graphics context for painting.
Graphics is a shared resource, which is passed to all the components been updated in the paint pass. This means it will contain what ever was previously painted to it.
Two recommendations:
As a general recommendation, prefer overriding paintComponent instead of paint (paint does a lot of important jobs, so unless you're willing to do them, it's generally to high up in the paint chain)
Call super.paintComponent first, before doing any custom painting.
I would, highly, recommend reading:
Performing Custom Painting
Painting in AWT and Swing
and
Concurrency in Swing
How to Use Swing Timers
sorry in advance if that question did get asked before somewhere here. I really tried to find something that helps me. Sadly I had no luck.
Here is my situation:
I have a Frame class with a button and a slider. And I have a thread class. With the button in the frame class, I create instances of the thread class. One instance of a thread class represents an picture. All the subthreads(pictures) will be displayed in the same frame and move around with a certain interval.
Here is my problem:
The slider controles the interval of the moving pictures. So they move faster or slower. All pictures should get faster or slower simultaneously. Unluckily only the first created thread gets affected by the slider. Same problem is with my ComponentAdapter that has his componentResized function. All the threads(or the pictures) should get bigger or smaller simultaneously relatively to the frame size.
Here I will try to show you roughly how I am doing it:
frame class:
//The actionPerformed for the button that keeps creating threads
//Every thread is a picture (ant in this case) that
// moves through the same field of buttons
public void actionPerformed(ActionEvent e){
a = new AmeiseThread(this);
a.start();
}
// The slider in the frame.When it changes, also the picture movement speed changes
sliderKind.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider) e.getSource();
if (!source.getValueIsAdjusting()) {
int speed = source.getValue();
a.sleeptime = 1000 / speed;
}
}
});
// Whenever the Frame gets resized, it will change the Pictures
// on its buttons relatively to its new size
class MyComponentAdapter extends ComponentAdapter {
public void componentResized(ComponentEvent e) {
xScale = jbArray.get(1).getSize().width;
yScale = jbArray.get(1).getSize().height;
a.ameisen.clear();
a.ameisen.add(new ImageIcon(new ImageIcon("ameise.gif").getImage()
.getScaledInstance(xScale, yScale, Image.SCALE_SMOOTH)));
a.ameisen.add(new ImageIcon(new ImageIcon("ameise90.gif").getImage()
.getScaledInstance(xScale, yScale, Image.SCALE_SMOOTH)));
a.ameisen.add(new ImageIcon(new ImageIcon("ameise180.gif").getImage()
.getScaledInstance(xScale, yScale, Image.SCALE_SMOOTH)));
a.ameisen.add(new ImageIcon(new ImageIcon("ameise270.gif").getImage()
.getScaledInstance(xScale, yScale, Image.SCALE_SMOOTH)));
}
}
I can't think of anything that is written in my thread class, that could be of interest. I think the problem lies within my already posted code somewhere. But if you do need more informations, comment it and I will post more informations right away.
Sorry again, if that question already exists. I feel like it does, because it sounds rather simple. Though I really tried to find a question similar to this but I had no luck.
Save all the threads you create
threadList.add(a);
itarate over it when the slider changed
for (AmeiseThread a: threadList)
if (!source.getValueIsAdjusting()) {
int speed = source.getValue();
a.sleeptime = 1000 / speed;
}
I'm attempting to code a simple animation or physics example in a Java Swing application. I have the actual windows application open and working, but I can't figure out how to actually draw my shapes, and how I'd format the code for calculations between frames, that sort of stuff.
I've read some stuff about over riding a paint method, but I don't know what that means, and I don't believe I'm using it in the code I'm using right now. This is my code:
public class Physics extends JFrame{
public Physics() {
initUI();
}
private void initUI() {
JPanel panel = new JPanel();
getContentPane().add(panel);
panel.setLayout(null);
final JLabel label = new JLabel("Hi, press the button to do something");
label.setBounds(20, 0, 2000, 60);
final JButton submitButton = new JButton("Start");
submitButton.setBounds(20, 150, 80, 20);
submitButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
//Put button code here later
}
});
panel.add(label);
panel.add(submitButton);
setTitle("Program");
setSize(300, 250);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Physics ex = new Physics();
ex.setVisible(true);
}
});
}
}
So I have some blank space above my button where I'd like to draw maybe a square or circle moving across the screen to start off with, once I get that down I can start getting into the more advanced stuff. Any hints on how to do that would be appriciated :D
Thanks!
"I've read some stuff about over riding a paint method, but I don't know what that means"
So you've overridden actionPerformed, so you know what an #Override is. As you'll notice from the ActionListener, you never actually explicitly call actionPerformed, but whatever you put in the there, still get's used. That's because the ActionListener implicitly call it for you.
The same is true with painting. In the Swing painting process, there is a paint chain that Swing uses to paint components. Along the way paint is called somewhere. So just like actionPerformed, you can override paint and it will get implicitly called for you.
#Override
public void paint(Graphics g) {
super.paint(g);
}
The Graphics object passed to the method is the graphics context that Swing will use for the painting. You can look at the Graphics API to see the methods you can use. You can use drawOval to draw a circle
#Override
public void paint(Graphics g) {
super.paint(g);
g.drawOval(x, y, width, height);
}
Now here's the thing. You don't actually want to override paint. In the tutorials linked above, some of the examples will use applets and override paint, but you shouldn'y paint on top level containers like JFrame or JApplet. Instead paint on a JPanel or JComponent and just add it the JFrame. When you do paint on JPanel or JComponent, you'll instead override paintComponent (which also gets called along the paint chain), instead of paint
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawOval(x, y, width, height);
}
You see how I used variables for the drawOval method. The x is the x location from the top-let of the screen, and y and the y point. width and height are width and height of the circle. The great thing about using variables is that their values can be changed at runtime.
That's where the animation comes to play. As pointed out, you an use a javax.swing.Timer
The basic construct is
public Timer(int delay, ActionListener listener) {
}
The delay is the milliseconds to delay each call to the listener. The listener will have your actionPerformed call back that will do what's inside, every delay milliseconds. So what you can do, is just change the x from the drawOval and repaint(), and it will animate. Something like
Timer timer = new Timer(40, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
x += 5;
repaint();
}
});
timer.start();
The timer code you can just put in the constructor. That's probably simplest explanation I can give. Hope it helps.
Don't forget the to see Custom Painting and Grapics2D for more advance topics on graphics. Also see some example of timers and animation here and here and here and here and here
Also avoid using null layouts. See Laying out Components Within a Container to learn how to use layout managers, as should be done with Swing apps.
Take a look at the Swing tutorial on Custom Painting.
The example shows you how to do painting. If you want animation, then you would use a Swing Timer to schedule the animation. The tutorial also has a section on How to use a Swing Timer.
Put the two tutorial together and you have a solution.
There are any number of ways to achieve this.
Start by taking a look at:
Performing Custom Painting
2D Graphics
For details about how painting in Swing is done.
Animation is not as simple as just pausing a small period of time and then repainting, theres acceleration and deceleration and other concepts that need to be considered.
While you could write your own, that's not a small task, a better solution might be to use a pre-existing engine, for example...
Then take a look at:
Timing Framework
Trident
java-universal-tween-engine
Which are all examples of animation engines in Swing. While I prefer the Timing Framework as it provides me with a lower level API, this is a personal opinion. Both Trident and the JUWE seem to be geared more towards component/property based animation (which the Timing Framework can do if you want to build some of the feature sets up)
I created a simple animation with two rockets blasting off. The full eclipse project is here: https://github.com/CoachEd/JavaExamples/tree/master/RaceToSpace. Here's a screenshot:
Recently, I have been trying to make my gui's look good and part of it is implementing my own titlebar, maximize button, minimize button, etc.
I am having issues on the dragging, I want to be able to drag the JFrame by clicking anywhere in it.
I attempted to create a mousemotionlistener to handle it however it slips when I use it. As in I attempt to drag the window to a location however it appears to be skipping calls to mouseDragged and attempting to call mouseMoved instead.
Here is my code
public class MouseWindowDragManager implements MouseMotionListener{
private JFrame frame;
int prevPosX = -1;
int prevPosY = -1;
public MouseWindowDragManager(JFrame frame){
this.frame = frame;
}
#Override
public void mouseDragged(MouseEvent e) {
int newX = (frame.getLocation().x + (e.getXOnScreen() - prevPosX));
int newY = (frame.getLocation().y + (e.getYOnScreen() - prevPosY));
frame.setLocation(newX, newY);
prevPosX = e.getXOnScreen();
prevPosY = e.getYOnScreen();
}
#Override
public void mouseMoved(MouseEvent e) {
prevPosX = e.getXOnScreen();
prevPosY = e.getProperty("apple.awt.draggableWindowBackground", true);tYOnScreen();
}
}
Recently, I have been trying to make my gui's look good and part of it is implementing my own titlebar, maximize button, minimize button, etc.
Gee, Microsoft and Apple spend millions of dollars to develop GUI's that users can use. They will be disappointed to hear this :)
I want to be able to drag the JFrame by clicking anywhere in it.
Check out Moving Windows for a class that allows you to drag a window around. The example code shows how to use the class by just dragging on your custom title bar. However, if you really want to be able to drag the frame by clicking anywhere then you can register the frame's root pane with the ComponentMover.
Okay, so in this program I'm making, users will be able to create shortcuts to their favorite apps on their computer. My program will be kind of like a hub, I guess, for apps. I have a small problem though, which involves two classes: AppButton and AppButtonContainer. They both implement MouseListener, but AppButton extends JComponent and AppButtonContainer extends JPanel. Basically, when an AppButton is clicked, it sets a draws the border in a different color to make it look selected. Otherwise, it sets the border to the background color. When you double click it, it opens up the application specified. I have a method in AppButton to remove the focus, and therefore setting the border to the background color. In AppButtonContainer, there is a bit of code so that when, the panel is clicked, it removes the focus from the AppButton.
That's my problem, though. The AppButtonContainer doesn't realize that it's clicked. I'm thinking it has something to do with top level containers or something, but I'm not sure. Can anybody help?
EDIT: I found out that I didn't put the addMouseListener(this) in the constructor of the AppButtonContainer. Thank you for everyone who helped me clear up this problem and give me tips along the way :)
AppButtonContianer:
public class AppButtonContainer extends JPanel implements MouseListener {
private static final long serialVersionUID = 6485231881729120957L;
public List<AppButton> appButtons;
private static final Color BACKGROUND_COLOR = new Color(18, 18, 18);
public AppButtonContainer(List<AppButton> buttons) {
this.appButtons = buttons;
setLayout(new GridLayout(5, 5, 20, 20));
addButtonsToPane();
}
private void addButtonsToPane() {
List<AppButton> buttons = this.appButtons;
for (int i = 0; i < buttons.size(); i++) {
this.add(buttons.get(i));
}
}
private void removeAllButtonFocus() {
for (int i = 0; i < this.appButtons.size(); i++) {
this.appButtons.get(i).removeFocus();
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
setBackground(BACKGROUND_COLOR);
repaint();
}
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("Pane Clicked");
removeAllButtonFocus();
}
...Other MouseEvent methods
You can solve the problem at hand by putting addMouseListener(this) in the constructor for your AppButtonContainer class. Otherwise, it'll never pick up mouse events.
Generally, though, it's not good to turn your classes into mouselisteners like that. Perhaps try making an inner class to listen for mouse events and pass them to the AppButtonContainer instead.