【问题标题】:Repainting a JPanel in a JScrollPane在 JScrollPane 中重新绘制 JPanel
【发布时间】:2012-08-05 04:46:23
【问题描述】:

我正在进行的项目是一个用于玩桌面游戏的计算机间实用程序,其中一个关键部分是交互式网格/地图。好吧,我已经内置了用于自定义地图创建的功能,并且希望屏幕能够显示非常大的地图以及非常小的地图。为此,我创建了一系列类,我可以使用它们显示任意大小的网格。但是我想做的是将完成此操作的 JPanel 放入 JScrollPane 中,从而为地图创造无限空间。当我尝试这样做时,完全显示在 JScrollPane 之外的地图根本不会绘制。我怀疑它与 Graphics 上下文有关,但到目前为止还没有找到解决方案。

在下面的代码中,注释滚动线并在初始化时切换 grid.draw 方法调用,看看我希望它是什么样子。

完整代码如下:

package pac;

import javax.swing.*;
import javax.swing.text.*;

import java.awt.*;

public class MainScreen extends JFrame
{
int width, height, x, y;
Grid grid;

public static void main(String[] args) 
{
    MainScreen mainScreen = new MainScreen();
    mainScreen.setVisible(true);        
}

public MainScreen()
{
    Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
    float factor = 1.25f;
    width = (int)(dim.width / factor);
    height = (int)(dim.height / factor);
    x = (int)(dim.width / (factor * 8));
    y = (int)(dim.height / (factor * 8));

    setBounds(x,y,width,height);
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setTitle("DnD Main Screen");
    setVisible(true);
    grid = new Grid();

    JScrollPane scrollPane = new JScrollPane(grid);
    scrollPane.setBounds(212,0,650,500);
    scrollPane.setViewportView(grid);
    add(scrollPane);
    this.setLayout(null);

    Thread draw = new Thread()
    {
        public void run()
        {
            while(true)
            {
                Graphics g = getGraphics();

                grid.repaint();
                //grid.paintComponents(getGraphics());

                g.setColor(Color.blue);
                g.fillRect(0 + 8, 0 + 30, 212, 200);
                g.fillRect(863 + 8, 0 + 30, 212, 200);

                g.setColor(Color.red);
                g.fillRect(0 + 8, 200 + 30, 212, 375);
                g.fillRect(863 + 8, 200 + 30, 212, 375);

                try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
                Thread.yield();
            }
        }
    };
    draw.start();
}

public class Grid extends JPanel
{
    Tile[][] tiles;
    public int x, y, across, down;
    public int topBar = 30;
    public int skinnyBar = 8;
    public Grid()
    {
        this.setPreferredSize(new Dimension(600,600));

        x=212 + skinnyBar;
        y=0+topBar;
        across = 13;
        down = 9;
        tiles = new Tile[across][down];
        for(int i = 0; i<across; i++)
            for(int j = 0; j<down; j++)
            {
                tiles[i][j] = new Tile(x+(i*50),y+(j*50),50);
            }
        this.setVisible(true);
    }
    public void paintComponents(Graphics g)
    {
        //super.paintComponents(g);
        draw(g);
    }
    public void draw(Graphics g)
    {
        for(int i = 0; i<across; i++)
            for(int j = 0; j<down; j++)
            {
                g.setColor(Color.black);
                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);
            }
    }
    private class Tile
    {
        int x, y, side;
        public Tile(int inX, int inY, int inSide)
        {
            x=inX;
            y=inY;
            side=inSide;
        }
    }
}

}

我已经为此工作了一段时间,但未能找到与我的足够相似的问题来找到修复程序。有什么建议吗?请和谢谢。

【问题讨论】:

  • Swing 的第一条规则,从不,从不,从不画任何与Event Dispatching Thread 之外的 UI 相关的东西,永远

标签: java swing jpanel jscrollpane paintcomponent


【解决方案1】:

1.JScrollPane不要使用空布局,使用一些标准的LayoutManager

2.不要使用getGraphics(),此方法用于打印到打印机或将GUI保存为文件图像的快照

3.没有理由使用draw(),在之前准备好所有Objects并在paintComponent()里面绘制所有内容

4.大部分例子都太老了,基于Thread,不要用Thread,改用Swing Timer

5.最简单的方法

JFrame -> JScrollPane -> JPanel -> 通过使用GridLayout 将所需数量的JPanels 与所需的Color 放在一起,将这些JPanels 放入某种类型的数组中,然后通过使用Swing Timer 从数组中获取JPanel 并更改其Backgroung

例如,必须添加Swing Timer 才能在期间进行绘画

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.*;

public class TilePainter extends JPanel implements Scrollable {

    private static final long serialVersionUID = 1L;

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frame = new JFrame("Tiles");
                frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                frame.getContentPane().add(new JScrollPane(new TilePainter()));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
    private final int TILE_SIZE = 50;
    private final int TILE_COUNT = 100;
    private final int visibleTiles = 10;
    private final boolean[][] loaded;
    private final boolean[][] loading;
    private final Random random;

    public TilePainter() {
        setPreferredSize(new Dimension(TILE_SIZE * TILE_COUNT, TILE_SIZE * TILE_COUNT));
        loaded = new boolean[TILE_COUNT][TILE_COUNT];
        loading = new boolean[TILE_COUNT][TILE_COUNT];
        random = new Random();
    }

    public boolean getTile(final int x, final int y) {
        boolean canPaint = loaded[x][y];
        if (!canPaint && !loading[x][y]) {
            loading[x][y] = true;
            Timer timer = new Timer(random.nextInt(500),
                    new ActionListener() {

                        @Override
                        public void actionPerformed(ActionEvent e) {
                            loaded[x][y] = true;
                            repaint(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
                        }
                    });
            timer.setRepeats(false);
            timer.start();
        }
        return canPaint;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Rectangle clip = g.getClipBounds();
        int startX = clip.x - (clip.x % TILE_SIZE);
        int startY = clip.y - (clip.y % TILE_SIZE);
        for (int x = startX; x < clip.x + clip.width; x += TILE_SIZE) {
            for (int y = startY; y < clip.y + clip.height; y += TILE_SIZE) {
                if (getTile(x / TILE_SIZE, y / TILE_SIZE)) {
                    g.setColor(Color.GREEN);
                } else {
                    g.setColor(Color.RED);
                }
                g.fillRect(x, y, TILE_SIZE - 1, TILE_SIZE - 1);
            }
        }
    }

    @Override
    public Dimension getPreferredScrollableViewportSize() {
        return new Dimension(visibleTiles * TILE_SIZE, visibleTiles * TILE_SIZE);
    }

    @Override
    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
        return TILE_SIZE * Math.max(1, visibleTiles - 1);
    }

    @Override
    public boolean getScrollableTracksViewportHeight() {
        return false;
    }

    @Override
    public boolean getScrollableTracksViewportWidth() {
        return false;
    }

    @Override
    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
        return TILE_SIZE;
    }
}

【讨论】:

  • 非常感谢您查看我的代码和您的示例代码,我一定会在早上实现其中的一些。老实说,我一直在使用一些古老的示例进行教程,并且对 swing 或 java 的内部工作原理了解不多。我认为这是目前给出的最佳答案。
  • 超级简单和有效的例子,不错!我在“首秀”中因缺乏重绘而苦苦挣扎,这是通过计时器中对 repaint(...) 的内部调用解决的。谢谢!
  • @Benj 我不知道“简单”...这是出色的工作,但简单并不能完全做到公正...
【解决方案2】:

你确定吗?

public void paintComponents(Graphics g)

你的意思是不是

public void paintComponent(Graphics g)

【讨论】:

    【解决方案3】:

    除了永远不要在Event Dispatching Thread 之外更新 UI,我还建议以下几点:

    确保Grid 窗格调用super.paintComponent(...) 这很重要,而且你真的、真的、真的必须有充分的理由不这样做。

    如果您希望面板透明,请改用setOpaque(false)

    我还建议您熟悉Graphics.drawLine 方法。我觉得这样会更有效率

    您必须记住,您无法控制绘画过程。就接受吧。您可以向要更新的图形管道提供请求(invalidaterevalidaterepaint),仅此而已。其余的由操作系统和重绘管理器决定。

    this.setLayout(null); 是一个非常非常糟糕的主意,这可能只是我的看法,但这确实是一个坏主意。如果没有其他人了解GridBagLayout 或使用复合面板和布局管理器,它只会为您省去很多麻烦

    【讨论】:

    • 感谢您的建议,我很抱歉我的技术不好,但我还没有任何正式的 Java 类。我只是从教程中自学了尽可能多的 Java,并且经常尝试将我的 C# 经验与 Java 结合起来。我会尝试执行您的建议。
    • @bluesawdust 不要为有勇气征求意见而道歉;)。结帐docs.oracle.com/javase/tutorial/uiswing它有一个关于自定义绘画的部分,你可能还想看看docs.oracle.com/javase/tutorial/2d/index.html
    • 在搞砸了一点之后,似乎主要问题是 setlayout(null)。我想它比我想象的更重要,谢谢你这么强调它。
    • @zIronManBox 你最好重新验证实际视图而不是JScrollPane,因为这需要它自己布局,JViewport 和视图
    • @MadProgrammer 是的,你是对的,我应该重新验证框架,在我的例子中是一个 JInternalFrame。因此jInternalFrame.revalidate()。感谢您的提醒。
    【解决方案4】:

    嗯,我自己做了一个小例子,它演示了重新绘制一个JPanel,它包装在一个JScrollPane中:

    面板有一个自定义方法setScrollPane(JSCrollPane jsp),它将在面板和JScrollPane被初始化后被调用,这将允许JPanelscrollPanes实例上调用repaint()

    PanelPaintTest.java:

    import javax.swing.JFrame;
    import javax.swing.JScrollPane;
    import javax.swing.SwingUtilities;
    
    public class PanelPaintTest extends JFrame {
    
        public PanelPaintTest() {
            createUI();
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
    
                @Override
                public void run() {
                    new PanelPaintTest().setVisible(true);
                }
            });
        }
    
        private void createUI() {
            //create frame
            setTitle("JPanel and JScrollPane Paint Test");
            setSize(500, 500);
            setResizable(false);
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            //create custom panel
            Panel panel = new Panel(500, 500);    
            //create scrollpane to hold JPanel
            JScrollPane scrollPane = new JScrollPane();
            scrollPane.setViewportView(panel);    
            panel.setScrollPane(scrollPane);
            //custom method to set the panels JScrollPane which will be repainted when ever the panel is    
            getContentPane().add(scrollPane);//add scrollpane to the frame
        }
    }
    

    Panel.java

    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.JPanel;
    import javax.swing.JScrollPane;
    import javax.swing.Timer;
    
    class Panel extends JPanel {
    
        private int width, height, across, down, x = 0, y = 0;
        ;
        private Panel.Tile[][] tiles;
        private Color color = Color.black;
        private JScrollPane scrollPane;
    
        public Panel(int width, int height) {
            this.setPreferredSize(new Dimension(width * 2, height * 2));//simple to make the scrollbars visible
    
            this.width = width * 2;
            this.height = height * 2;
    
            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() {
            scrollPane.repaint();
        }
    
        void setScrollPane(JScrollPane scrollPane) {
            this.scrollPane = scrollPane;
        }
    
        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(x + (i * 50), y + (j * 50), 50);//took out x and y values didnt know what 
                }
            }
        }
    
        //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;
            }
        }
    }
    

    面板将绘制您显示的图块,它们会每 2 秒闪烁一次红色或黑色。

    HTH

    【讨论】:

    • @mKorbel 谢谢你帮助我快速学习了新的挥杆计时器,从现在开始就不能使用了:)
    猜你喜欢
    • 2013-11-12
    • 1970-01-01
    • 2014-05-20
    • 2011-05-22
    • 1970-01-01
    • 2013-06-22
    • 1970-01-01
    • 1970-01-01
    • 2014-04-26
    相关资源
    最近更新 更多