Calling repaint for specific JPanel in JFrame repaints also JDialog - java

Below is a small example for what i describe
Well, I have a simple JFrame with a JPanel as its contentPane that i repaint with a SwingWorker every 20 ms with panel.repaint().
Also, I have a JDialog opened that shows its own graphics with opengl (I use a AWTGLCanvas from LWJGL library) that swaps buffers and repaint() every time it does paint its content (faster than the 20ms).
The big problem is that in some way repainting my JPanel also affects the JDialog meaning that if i remove the panel.repaint() (which i have done) it works fine! When i have the panel.repaint() the JDialog shows some strange lines like i have cut the graphics in 2 and try to move them together with no success. I dont know if they call it flicker but it may be that problem.
public testFrame(){
panel = new JPanel(){
protected void paintComponent(Graphics g){
//do the painting for the JPanel here
}
}
setContentPane(panel);
updateTime();
}
public void updateTime(){
SwingWorker worker = new SWingWorker(){
#Override
protected Object doInBackground() throws Exception {
while(stopTimer == false){
Thread.sleep(20);
if(isFocused() == true){
timer += 0.2f;
panel.repaint();
}
}
return null;
}
};
worker.execute();
}
And here is my AWTGLCanvas.paint method in my JDialog contentPane
#Override
public void paintGL() {
//do some painting with classic opengl
swapBuffers(); //awtglcanvas built-in method, meaning i cant control it
repaint(); // it actually runs paintGL() again
}
Thanks in advance!

Related

Why does super.repaint() not call paint method?

SOLUTION: I needet to add the JPanel into a JFrame. The calling of the paint method is bound to the visibility of the JFrame. thanks #matt
This is my first question so I am sorry if something is missing.
My Problem:
I want to create a class which extends JPanel so that I can draw things onto my JFrame. I also want it to be able to repaint itself. So far the run method works but super.repaint() does not. "test1" is being printed and "test2" not.
Thanks in advance :)
public class Main {
public static GUI gui;
public static void main(String[] args) {
gui = new GUI();
}
}
public class GUI extends JFrame{
public Draw draw;
public Thread drawT;
public GUI() {
draw = new Draw();
drawT = new Thread(draw);
drawT.start();
}
}
public class Draw extends JPanel implements Runnable{
#Override
public void run() {
while (true) {
System.out.println("test1");
super.repaint();
}
}
#Override
public void paint(Graphics graphics) {
System.out.println("test2");
Graphics2D g = (Graphics2D)graphics;
}
}
repaint schedules you component to be painted. If the component is not visible then paint will not get called.
A simplified way to look at it. Swing/AWT has the EventDispatchThread which continuously runs, processes events and paints components. When you call "repaint" it tells swing to schedule a paint event for your component. It doesn't call paint. When the EDT gets around to it, it will paint your component.
In your 'run' method you have a loop that prints out a statement to a terminal, "test1" and then it calls repaint(), and then it repeats. The slowest thing here is the System.out.println.
You have a really fast loop swamping AWT with repaints requests but that loop will run faster than the EDT loop will run paint events. So maybe you print test1 and call repaint 100 times before the EDT actually performs a single paint. I suspect if you look close, test2 is buried in your output somewhere.
import java.awt.*;
public class MainS{
int paints = 0;
int tags = 0;
public void runGui(){
JFrame frame = new JFrame("testing");
JPanel panel = new JPanel(){
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
paints++;
}
};
frame.add(panel, BorderLayout.CENTER);
JLabel label = new JLabel("times painted ...");
frame.add(label, BorderLayout.NORTH);
frame.setSize(200, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Timer t = new Timer( 1000, evt->{
label.setText(paints + "/" + tags);
paints = 0;
tags = 0;
});
t.start();
new Thread( ()->{
while(true){
tags++;
panel.repaint();
}
}).start();
}
public static void main(String[] args){
EventQueue.invokeLater( new MainS()::runGui );
}
}
Here is a simple example, where the component actually gets repainted. It will display the number of times paintComponent was called and the number of times repaint was called at the top of the frame.

Paint Method is not Called

After making an instance in a previous frame, I'm trying to the background image on the next frame but as a result, I just saw the debugged result and found out that the paint method was not called. From what I know, the paint method is inherited by the JFrame class and with this logic, I've made it overrided. As I guess, the reason happen the logical error is from what I used the event handler and made the instance in the EventHandlerClass.
if(e.getActionCommand().equals(ButtonTo))
if(idString.equals("USER"))
{
{
if("1234".equals(pwSt))
{
System.out.println("Wellcome");
if(gs==null)
{
gs=new GameStart();
}
}
else
{
System.out.println("Confirm your password");
}
}
}
This is a code that If an action is performed it will make an instance(gs). After doing this, I noticed that the instance has been used as to make a new console frame.
class GameStart extends JFrame {
private Image screenImage;
private Graphics screenGraphic;
private Image introBackgroundImage;
private ImageIcon img;
GameStart()
{
JFrame jf=new JFrame("Game Set");
jf.setBounds(300, 300, 400, 200);
jf.setLayout(new BorderLayout());
JButton bt1=new JButton("Start");
JButton bt2=new JButton("Exit");
JPanel panel1=new JPanel();
panel1.add(bt1);panel1.add(bt2);
setContentPane(panel1);
jf.add(panel1, BorderLayout.SOUTH);
bt1.addActionListener(new Choice());
bt2.addActionListener(new Choice());
jf.setVisible(true);
img=new ImageIcon("./Images/backGroundImage.jpg");
System.out.println("1");
}
public void paint(Graphics g) {
screenImage=createImage(200, 200);
screenGraphic=screenImage.getGraphics();
screenDraw(screenGraphic);
g.drawImage(screenImage, 0, 0, null);
System.out.println("2");
}
public void screenDraw(Graphics g)
{
this.repaint();
System.out.println("3");
}
Now, With making a frame and some buttons, I expect to show all the numbers(1, 2, 3) that indicate the result but Just did number 1.
There are some errors in your code that I can see at first glance:
You're extending JFrame, but you're not adding any extra functionality to it, see: Extends JFrame vs. creating it inside the program. Instead, build your GUI towards the use of JPanels and override their paintComponent(...) method and not the paint(...) one.
You're breaking the paint-chain: After doing the above point, in paintComponent(), call super.paintComponent(...)
Maybe there are others but I'm currently busy and can't test your code, but the ones above should help with your issue.

Java Graphics only overlaying new shapes?

I'm working on a program that displays an animation using the MVC architecture. The model contains shapes and tells them to mutate themselves. The view in question is a class that extends JPanel. It accepts the shapes from the controller and places them on the Graphics object in paintComponent. The controller has a while loop in its run method that tells the model to mutate all of the shapes, pushes those shapes to the view, and then sleeps the thread for a certain amount of time.
However, the problem I'm running into is this: the Graphics object seems to be simply overlaying the new shapes for every paintComponent call, so that you can see the trail of the shapes throughout the whole run of the program. This is only a problem when the view extends JPanel (program ran fine for the previous implementation, which was a JFrame that had an anonymous JPanel class), and only seems to be a problem on my Linux machine (my project partner uses a macbook -- tagging Linux since it could be tied to Linux platform). Also, I've tried it with Oracle jdk8, open-jdk8, and open-jdk10.
I'm sure it's just a bug in my code, since other programs work. Could it be a bug that the Linux jdk finds but not macOS?
I'm going to do my best to do pseudo code so I don't get docked for plagiarism
current code:
public class MyVisualView extends JPanel implements MyViews {
// store my shapes with name shapes
public MyVisualView() {
JFrame frame = new JFrame();
frame.setSize(width, height);
frame.setResizable(false);
frame.getContentPane().add(this);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
// tried these individually -- didn't work
this.setOpaque(true);
this.setBackground(Color.WHITE);
}
#Override
public void paintComponent(Graphics g) {
for (all of my shapes) {
g.setColor(shape color);
if (oval) g.fillOval(...);
else g.fillRect(...);
}
}
}
previous code, which worked:
public class MyVisualView extends JFrame implements MyViews {
public void run() {
shapes = getShapes();
JPanel panel = new JPanel() {
#Override
public void paintComponent(Graphics g) {
// same paintComponent as above
}
};
while (true) {
panel.repaint();
// wait for a certain amount of time
}
}
}
Edit: Solved it by repainting the JFrame instead of the JPanel
However, the problem I'm running into is this: the Graphics object seems to be simply overlaying the new shapes for every paintComponent call
#Override
public void paintComponent(Graphics g) {
for (all of my shapes) {
The code should be:
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (all of my shapes) {
You need to invoke the default painting of the panel to clear the background first before repainting all your shapes.
Also:
while (true) {
panel.repaint();
Don't use a while (true) loop for animation. Instead you should be using a Swing Timer to schedule the animation.
Take a look at the Swing Tutorial. There are sections on:
Custom Painting - (ie. you should also be overriding the getPreferredSize() method)
How to Use Swing Timer
to get you started with the basics of each of these functions of Swing.

Why doesn't repaint() work when called by a method of an object of the same class?

In a program I'm building for my class I have the same class extending a Swing JPanel and implementing MouseListener, for which I use two instantiations - one to function as a JPanel, and the other as a mouse listener for that JPanel.
But when I click in the window, repaint() the MouseClicked method in the mouse listener fails to call the first object's paintComponent() method. For example:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class TestPanel extends JPanel implements MouseListener{
static boolean black;
static TestPanel test = new TestPanel();
public void mouseExited(MouseEvent e){}
public void mousePressed(MouseEvent e){}
public void mouseReleased(MouseEvent e){}
public void mouseEntered(MouseEvent e){}
public void mouseClicked(MouseEvent e){ //Expected behavior: the square turns black immediately
System.out.println("CLICK!");
black = true;
test.repaint(); //this fails
try{
Thread.sleep(3000);
}catch(Exception ex){}
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
System.out.println("Painting...");
g2d.setColor(Color.white);
if(black){
g2d.setColor(Color.black);
}
g2d.fillRect(0, 0, 200, 200);
}
public static void main(String[] args) throws InterruptedException{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
test.addMouseListener(new TestPanel());
test.setPreferredSize(new Dimension(200, 200));
frame.add(test);
frame.pack();
frame.setVisible(true);
while (true){
black = false;
test.repaint();
Thread.sleep(100);
}
}
}
If you watch what happens on a click, the screen stays white for the 3 seconds after the click is registered, until the loop starts up again, i.e., the repaint() call in the mouse listener didn't work. Why does this happen?
I'm guessing it would work if I made different classes for the objects, but I'm mostly curious as to why it doesn't work this way.
for which I use two instantiations - one to function as a JPanel, and the other as a mouse listener for that JPanel.
There is no need to do that. All you need is a single instance of the TestPanel class.
In the constructor of your TestPanel class you just add:
addMouseListener( this);
The get rid of the static variable for the TestPanel class.
Then the code in your main method should look something like:
//test.addMouseListener(new TestPanel());
//test.setPreferredSize(new Dimension(200, 200));
//frame.add(test);
frame.add( new TestPanel() );
Also, the TestPanel class should override the getPreferredSize() method to return the Dimension of your panel.
Read the section from the Swing tutorial on Custom Painting for a working example with a MouseListener.
The AWT thread is responsible for calling MouseListener and for repaint.
Inside the repaint(); method, the AWT thread is told to call the paint();
Just call it using a different thread. In general, it is a bad idea to do anything intensive with the AWT thread. It already does a lot, taking too much of its time will mess your GUI up.
Depending on your needs, this might work:
new Thread(()->{repaint();}).start();

Drawing Image on JPanel as Background

im trying to insert a gif as a background for my app. I cut all frames and renamed them f1/f2/f3/f4/f5/f6/..... I would use a timer to change the frame so it looks like an animation.
There is a total of 42 frames, so f42.png is the last frame. The code seems to be fine, but there is no result. Any help?
Global variables:
private String backgroundFile;
public JPanel backgroundPanel, areaImage;
private BufferedImage background;
private javax.swing.Timer timerBackground;
Constructor where the Timer is initialized:
public Game()
{
entryWindow();
this.setLayout(null);
timerBackground = new javax.swing.Timer(100,this);
timerBackground.stop();
}
Animation method code:
private void backgroundAnimation()
{
backgroundFile = "f"+backgroundNum+".png";
try{
background=ImageIO.read(new File(backgroundFile));
}
catch(IOException e)
{
}
backgroundPanel = new JPanel()
{
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(background, 0, 0, 1100,800,null);
}
};
backgroundPanel.setBackground(Color.BLACK);
backgroundPanel.setBounds(0, 0, 1100, 800);
if (backgroundNum>42)backgroundNum++;
else backgroundNum=1;
add(backgroundPanel);
backgroundPanel.setVisible(true);
}
Action Listener for timer:
if (ae.getSource() == timerBackground)
{
backgroundAnimation();
}
In order to show JPanel, you need to add it to something like JFrame with an BorderLayout for instance, then you need to show the JFrame. JFrame is a application window, the JPanel can be only added and drawn on Window, it can't be viewed without something on which it can draw (like app Window). Beside that you don't need to create new JPanel each time the animation changes, just make a setter for the current image to show, and after assigning the image call repaint(), the ImagePanel could be like this:
public class ImagePanel extends JPanel {
private volatile BufferedImage image;
public void showImage(BufferedImage image) {
this.image=image;
repaint();
}
public void paintComponent(Graphics g) {
g.drawImage(image, 0,0,getWidth(),getHeight(),null);
}
}
add it to your JFrame at application start, also set the LayoutManager of JFrame to BorderLayout preferably, because without that your panel will have size(0,0) since you didn't set it, and it could be one of reasons why you don't see it (you can't see something which is 0 pixel in size, can you?).
Then in your timer just call the ImagePanel method public void showImage(BufferedImage image) with the image to show. If that's don't solve your problem, then post your entire code. As without that i'm just guessing, but those are common problems, so there's big chance you hit something from this.
I can see a few issues here
1. Assuming your Game class is extending JFrame, You need to add the JPanel to the ContentPane of the JFrame. Use one of the approaches setContentPane(backgroundPanel); or getContentPane().add(backgroundPanel)
You are not using a LayoutManager. So either use a LayoutManager or set the Size of the 'JFrame' and 'JPanel' explicitly using setBounds() method. I would recommend using a LayoutManager.
The JPanel or any Component for that matter does not automatically refresh itself. Once you change the image, you need to call repaint() on your JPanel.
You dont need to create a new JPanel every time you change the image. Just extend the JPanel and override the paintComponent()like you have done. Use the Timer to change the image of that single instance and call repaint() with every change.
The complete example, with hat output you are seeing will help understand the problem better and give you a solution. Please see How to create a Minimal, Complete, and Verifiable example
There are multiple problems here, but first let me answer your question:
You are creating a new JPanel and add it to the Game on every run through. That is wrong, since you add infinite panels to your Game
Also in your if/else you have a wrong condition. You increase the iterator when it is greater 42. You probably mean lesser than 42.
Here is how I would do it:
public class BackgroundPanel extends JPanel {
private int currImage = 0;
private BufferedImage[] backgroundImages;
public BackgroundPanel() {
int numberOfImages = 42;
backgroundImages = new BufferedImage[42];
for(int i = 1; i <= numberOfImages; i++) {
String backgroundFile = "f" + i + ".png";
backgroundImages[i] = ImageIO.read(new File(backgroundFile));
}
}
public void nextImage() {
/*if(currImage <= 42) currImage++;
else currImage = 1;*/
if(currImage++ > 42) currImage = 1;
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(backgroundImages[currImage], 0, 0, getWidth(), getHeight(), null);
}
}
You need to add this panel ONCE to your "Game":
//Somewhere in your Game
private BackgroundPanel backgroundPanel;
...
...
public Game() {
entryWindow();
this.setLayout(null);
backgroundPanel = new backgroundPanel();
backgroundPanel.setSize(getWidth(), getHeight());
add(backgroundPanel);
timerBackground = new javax.swing.Timer(100,this);
timerBackground.stop();
}
Your timer:
if (ae.getSource() == timerBackground) {
backgroundPanel.nextImage();
}
It's easier to put the background on JLabel. It requires only 3 lines of code and works fine! :) Hope it helps for anyone that will have the same problem :)
All you have to do is copy this code, change the name (i have all pictures in a folder called "Images") with any kind of Java supported picture/video/.... (just change the suffix .gif to your file format) and at last the size. Good luck! :)
public JLabel backgroundGIF;
backgroundGIF = new JLabel(new ImageIcon(getClass().getResource("Images/background.gif")));
backgroundGIF.setBounds(0,0,1100,800);
add(backgroundGIF);

Categories

Resources