使用计时器和paintComponent进行渲染不会导致渲染发生 [英] Rendering with a timer and paintComponent is causing no rendering to happen

查看:79
本文介绍了使用计时器和paintComponent进行渲染不会导致渲染发生的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在尝试开发游戏,并且我正处于使所有单独的组件都能正常工作的初期阶段(因此,我的代码很糟糕),并且在尝试渲染移动方块时,我遇到了问题.paintComponent方法将绘制正方形,但是当我使用计时器时,我无法使其工作.

我尝试过移动计时器方法,在不同的位置使用重新绘制,然后在某一位置废弃所有内容并从头开始重新进行着色.

  import java.awt.Color;导入java.awt.Graphics;导入java.awt.Toolkit;导入java.awt.event.KeyEvent;导入java.awt.event.KeyListener;导入java.time.Clock;导入java.time.Duration;导入java.util.Timer;导入java.util.TimerTask;导入javax.swing.JFrame;公共类InputTest扩展MyPanel实现KeyListener {私有静态int x;私有静态整数公共静态void main(String [] args){JFrame f =新的JFrame();InputTest p =新的InputTest();f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);f.setSize(500,500);p.setSize(500,500);f.add(p);f.addKeyListener(new InputTest());计时器timer = new Timer();timer.schedule(new TimerTask(){@Override公共无效run(){x ++;y ++;如果(x> 500){x = 0;}如果(y> 500){y = 0;}System.out.println("Tick" + x +," + y);}},0,500);p.setVisible(true);f.setVisible(true);}@Override公共无效keyPressed(KeyEvent e){}@Override公共无效keyReleased(KeyEvent e){//TODO自动生成的方法存根}@Override公共无效keyTyped(KeyEvent e){//TODO自动生成的方法存根}公共无效update(){}@Override受保护的void paintComponent(Graphics g){g.setColor(Color.BLACK);while(x!= 501&&y!= 501){g.fillRect(x,y,30,30);repaint();}}} 

正方形应从左上角到右下角缓慢移动.相反,我没有任何图像.

解决方案

Swing是单线程的

当Swing触发绘制周期时,直到层次结构中的所有组件都完成其操作,结果才会显示在屏幕上.

这意味着当您执行类似的操作...

  @Override受保护的void paintComponent(Graphics g){g.setColor(Color.BLACK);而(x!= 501& y!= 501){g.fillRect(x,y,30,30);repaint();}} 

在您退出 paintComponent 方法之前,将不会发生任何事情,这意味着在这种情况下,您最后绘制的内容是将要呈现的内容(或者在这种情况下很漂亮).

作为旁注,永远不要从paint方法中直接或间接调用 repaint ,这是一种消耗所有CPU周期并使系统瘫痪的好方法./p>

相反,绘画应该简单地绘制当前/期望的状态.另外,除非您真的知道自己在做什么,否则应始终先调用 super.paintComponent ,例如...

  @Override受保护的void paintComponent(Graphics g){super.paintComponent(g);g.setColor(Color.BLACK);g.fillRect(x,y,30,30);} 

有关更多详细信息,请参见 Swing中的并发 >

Swing不是线程安全的

您应始终避免从事件调度线程外部更新UI.这还包括更新UI依赖的状态.这样做会导致各种难以诊断的故障.

由于 java.util.Timer 使用其自己的 Thread 进行调度,因此在Swing中使用它的可能性很低.相反,您应该使用 javax.swing.Timer ,它将在EDT之外等待,但是会触发为其分配了 ActionListener 的EDT上下文

例如...

  Timer timer = new Timer(500,new ActionListener(){@Override公共无效actionPerformed(ActionEvent e){x ++;y ++;如果(x> 500){x = 0;}如果(y> 500){y = 0;}//触发新的绘画周期...repaint();}});timer.start(); 

有关更多详细信息,请参见如何使用Swing计时器

所有其他"东西

出于多种原因,最好避免使用

KeyListener .使用键绑定API ,它将为您节省很多的头发.

组件的可用大小始终是窗口的大小减去其边框装饰的大小.这意味着使用 f.setSize(500,500)将使可用上下文区域变小(取决于运行代码的平台).

更好地重写组件的 getPreferredSize 方法并返回一个大小调整提示.然后,您可以使用 JFrame#pack 将窗口包装"在内容周围,以使窗口大于内容的大小,但是您不会浪费时间去弄清楚为什么事情不会这样做.不要在他们期望的地方停下来.

可运行的示例

我还修改了代码,以消除对 static 的依赖,这从来都不是一个好主意

  import java.awt.Color;导入java.awt.Dimension;导入java.awt.Graphics;导入java.awt.event.ActionEvent;导入java.awt.event.ActionListener;导入javax.swing.JFrame;导入javax.swing.JPanel;导入javax.swing.Timer;公共类InputTest扩展了JPanel {私人诠释x;私人内部公共静态void main(String [] args){JFrame f =新的JFrame();InputTest p =新的InputTest();f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);f.add(p);f.pack();f.setLocationRelativeTo(null);f.setVisible(true);}公共InputTest(){计时器计时器=新计时器(500,新ActionListener(){@Override公共无效actionPerformed(ActionEvent e){x ++;y ++;如果(x> 500){x = 0;}如果(y> 500){y = 0;}//触发新的绘画周期...repaint();}});timer.start();}@Override公共维度getPreferredSize(){返回新的Dimension(500,500);}@Override受保护的void paintComponent(Graphics g){g.setColor(Color.BLACK);g.fillRect(x,y,30,30);}} 

I've been trying to develop a game, and I'm in the preliminary stages of getting all the seperate components to work (hence my terrible code) and while trying to render a moving square, I've run into an issue. The paintComponent method will draw squares, but I cant get it to work when I'm using a timer.

I've tried moving the timer method around, using repaint at different points, and at one point scrapped everything and redid it from the ground up.

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.time.Clock;
import java.time.Duration;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JFrame;

public class InputTest extends MyPanel implements KeyListener {
    private static int x;
    private static int y;

    public static void main(String[] args) {
        JFrame f = new JFrame();
        InputTest p = new InputTest();
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        f.setSize(500, 500);
        p.setSize(500, 500);
        f.add(p);
        f.addKeyListener(new InputTest());
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                x++;
                y++;
                if (x > 500) {
                    x = 0;
                }
                if (y > 500) {
                    y = 0;
                }
                System.out.println("Tick"+x+","+y);

            }
        }, 0, 500);

        p.setVisible(true);
        f.setVisible(true);
    }

    @Override

    public void keyPressed(KeyEvent e) {

    }

    @Override
    public void keyReleased(KeyEvent e) {
        // TODO Auto-generated method stub

    }

    @Override
    public void keyTyped(KeyEvent e) {
        // TODO Auto-generated method stub

    }

    public void update() {

    }

    @Override
    protected void paintComponent(Graphics g) {
        g.setColor(Color.BLACK);
        while(x!=501&&y!=501) {
            g.fillRect(x, y, 30, 30);
            repaint();
        }
    }
}

The square should be moving slowly from the top left to the bottom right. Instead, I'm not getting any image.

解决方案

Swing is single threaded

When Swing triggers a paint cycle, the results will not be rendered to the screen until all components in the hierarchy have completed their operations.

This means that when you do something like...

@Override
protected void paintComponent(Graphics g) {
    g.setColor(Color.BLACK);
    while (x != 501 && y != 501) {
        g.fillRect(x, y, 30, 30);
        repaint();
    }
}

Nothing will happen until you exit the paintComponent method, meaning, in this case, the last thing you painted is what will be rendered (or in this a nice streak).

As a side note, you should NEVER call repaint directly or indirectly from within a paint method, this is a really good way to consume all the CPU cycles and bring your system to its knees.

Instead, painting should simply paint the current/desired state. Also, unless you really know what you're doing, you should always call super.paintComponent first, for example...

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.setColor(Color.BLACK);
    g.fillRect(x, y, 30, 30);
}

See Concurrency in Swing for more details

Swing is not thread safe

You should always avoid updating the UI from out side the Event Dispatching Thread. This also includes updating states which the UI relies on. Doing so can cause all sorts of difficult to diagnose glitches.

Since java.util.Timer uses its own Thread to perform its scheduling, it is a poor candidate for using in Swing. Instead, you should be using javax.swing.Timer, which will wait outside the EDT, but trigger it's assigned ActionListener which the context of the EDT

For example...

Timer timer = new Timer(500, new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        x++;
        y++;
        if (x > 500) {
            x = 0;
        }
        if (y > 500) {
            y = 0;
        }

        // Trigger a new paint cycle...
        repaint();
    }
});
timer.start();

See How to Use Swing Timers for more details

All the "other" stuff

KeyListener is best avoided, for a number of reasons. Use the Key Bindings API, it will save you a lot of hair pulling.

The available size of your component is always the size of the window MINUS its border decorations. This means that using f.setSize(500, 500) will make the available context area smaller (depending on the platform you are running your code).

Better to overriding the component's getPreferredSize method and return a sizing hint. You can then use JFrame#pack to "pack" the window around the content, so that the window is larger then the content size, but you won't waste time trying to figure out why things don't stop where you expect them to.

Runnable example

I also modified the code to remove the reliance on static, which is never a great idea

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.Timer;

public class InputTest extends JPanel {

    private int x;
    private int y;

    public static void main(String[] args) {
        JFrame f = new JFrame();
        InputTest p = new InputTest();
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        f.add(p);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public InputTest() {
        Timer timer = new Timer(500, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                x++;
                y++;
                if (x > 500) {
                    x = 0;
                }
                if (y > 500) {
                    y = 0;
                }

                // Trigger a new paint cycle...
                repaint();
            }
        });
        timer.start();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(500, 500);
    }

    @Override
    protected void paintComponent(Graphics g) {
        g.setColor(Color.BLACK);
        g.fillRect(x, y, 30, 30);
    }
}

这篇关于使用计时器和paintComponent进行渲染不会导致渲染发生的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆