在 JScrollPane 中重绘 JPanel



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

在下面的代码中,注释滚动线并在初始化中切换 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;
        }
    }
}

}

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

1.不要对 JScrollPan e 使用空布局,使用一些标准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;
    }
}

你确定吗?

public void paintComponents(Graphics g)

你的意思是相反

public void paintComponent(Graphics g)

除了从不对Event Dispatching Thread之外的UI执行更新外,我还建议如下:

确保Grid窗格调用super.paintComponent(...)这很重要,并且您真的,真的,真的必须有一个很好的理由不这样做。

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

我还建议您熟悉Graphics.drawLine方法。 我认为它会更有效率

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

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

嗯,

我做了我自己的小例子,演示了重新绘制包裹在JScrollPane中的JPanel

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

面板油漆测试.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
    }
}

面板.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 秒闪烁一次红色或黑色。

最新更新