我正在为自己开发一个游戏项目,以提高自己的Java技能,而今天我遇到了一个似乎无法借助互联网解决的问题。这个问题大部分已经解决了,但是最后一点仍然存在挑战!

我有一个可扩展Canvas并创建要显示的地图的对象。我已将此画布添加到JPanel,然后将其添加到JScrollPane的视口(已添加到游戏的JFrame)。将一个ChangeListener添加到JScrollPane的视口中,这将在JFrame上执行validate方法,并在视口上执行repaint方法。
JFrame本身具有JMenuBar,在菜单结构中有一个“新游戏”选项(除尚未实现的JMenuItems外),它将创建一个新的Canvas对象并替换旧的Canvas对象并验证(使用validate方法)重新绘制JScrollPane 。

结果是,当我按下New Game JMenuItem时,会在我的scollpane中绘制一个新地图。它将正确显示地图(画布对象),并显示滚动条。菜单位于“画布”和滚动窗格的顶部,这是正确的。
但是,当我略微改变滚动条的位置(水平或垂直,使用条或条中的按钮)时,会导致Canvas对象在JFrame上平移。这意味着它可能会与滚动条,菜单栏(也将立即绘制在打开的菜单上)重叠在一起,而Canvas对象的一部分不会显示出来。仅当滚动条实际击中其极限点(不能再进一步)时,它才会重新绘制并验证整个场景。但是很明显,当我只是轻推滚动条或类似的东西时,我也希望它也这样做。

我的问题是:如何使这项工作按预期进行?
我想我需要对ChangeListener做些事情,但是我似乎自己找不到解决方案。

我注意到大多数人都要求提供源代码,因此我为您提供了此代码:

打包otherFiles;

导入java.awt.BorderLayout;
导入java.awt.Color;
导入java.awt.Graphics;
导入java.awt.Graphics2D;
导入java.awt.event.ActionEvent;
导入java.awt.event.ActionListener;
导入java.awt.geom.Ellipse2D;
导入java.awt.geom.Line2D;
导入java.awt.geom.Rectangle2D;
导入java.util.ArrayList;

导入javax.swing.JComponent;
导入javax.swing.JFrame;
导入javax.swing.JMenu;
导入javax.swing.JMenuBar;
导入javax.swing.JMenuItem;
导入javax.swing.JPanel;
导入javax.swing.JScrollPane;

公共类ProblemDemonstrator {
        私有静态JScrollPane dispArea = null;
    私有静态JPanel mapPanel = null;
    私有静态JFrame thisFrame = null;

private static final int FRAME_WIDTH = 300;private static final int FRAME_HEIGTH = 300;private static boolean mode = false;public static void main(String[] args){ JFrame mainMenu = myMenuFrame(); mainMenu.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); mainMenu.setVisible(true);}public static JFrame myMenuFrame(){ thisFrame = new JFrame("Problem Demonstrator"); thisFrame.setSize(FRAME_WIDTH, FRAME_HEIGTH); thisFrame.setLayout(new BorderLayout()); //Create the menu bar and corresponding menu's. JMenuBar menuBar = new JMenuBar(); thisFrame.setJMenuBar(menuBar); menuBar.add(createFileMenu(),BorderLayout.NORTH); //Create a scroll-able game display area dispArea = new JScrollPane(); dispArea.setSize(thisFrame.getSize()); thisFrame.getContentPane().add(dispArea, BorderLayout.CENTER); return thisFrame;}private static JMenu createFileMenu(){ JMenu menu = new JMenu("File"); menu.add(createFile_NewGameItem()); //New game button return menu;}/** * The button for the creation of a new game. * @return The JMenuItem for starting a new game. */private static JMenuItem createFile_NewGameItem(){ JMenuItem item = new JMenuItem("New Game"); class MenuItemListener implements ActionListener { @Override public void actionPerformed(ActionEvent arg0) { System.out.println("actionPerformed - New Game [JMenuItem]"); if(mapPanel == null){ mapPanel = createGameMap(mode); dispArea.setViewportView(mapPanel); }else{ dispArea.getViewport().remove(mapPanel); mapPanel = createGameMap(mode); dispArea.setViewportView(mapPanel); } //'flip' mode if(mode){ mode = false; }else{ mode = true; } thisFrame.pack(); thisFrame.setSize(FRAME_WIDTH, FRAME_HEIGTH); dispArea.repaint(); thisFrame.validate(); } } ActionListener l = new MenuItemListener(); item.addActionListener(l); return item;}/** * This creates the displayable map that has to go in the JScrollPane * @param mode Just a variables to be able to create 'random' maps. * @return The displayable map, a JPanel */private static JPanel createGameMap(boolean mode){ /** * This is a quick version of the planets I generate for the map. * Normally this is a another class object, using another class called Planet * to set parameters like the location, size and color in it's constructor. * x = the x location on the map (center of the planet!) * y = the y location on the map (also center) * diam = the diameter of the planet */ @SuppressWarnings("serial") class myPlanetComponent extends JComponent{ private int x,y,diam; public myPlanetComponent(int x, int y, int diam){ this.x = x; this.y =y; this.diam = diam; } //Paint a circle on with the centre on (x,y) public void paintComponent(Graphics g){ Graphics2D g2 = (Graphics2D) g; g2.setColor(Color.BLUE); Ellipse2D.Double circle = new Ellipse2D.Double((x-diam/2), (y-diam/2), diam, diam ); g2.fill(circle); g2.setColor(Color.DARK_GRAY); g2.draw(circle); //I want a border around my planet } public int getX(){ return this.x; } public int getY(){ return this.y; } } /** * This is also a quick way version of how I create the map display * I intend to use in my game. It's a collection of planets with lines * between them, on a black background. */ @SuppressWarnings("serial") class myMap extends JPanel{ private boolean modeOne; private int sizeX, sizeY; public myMap(boolean mode){ this.sizeX = 500; this.sizeY = 500; this.modeOne = mode; //JPanel map = new JPanel(); this.setSize(this.sizeX, this.sizeY); } public int getSizeX(){ return this.sizeX; } public int getSizeY(){ return this.sizeY; } public void paintComponent(Graphics g){ Graphics2D g2 = (Graphics2D) g; //Create the black background plane //this.setBackground(Color.BLACK); //Tried it with this, but won't give any bakcground int heightBG = this.getSizeX(); int widthBG = this.getSizeY(); Rectangle2D.Double SpaceBackGround = new Rectangle2D.Double(0,0, heightBG, widthBG); g2.setColor(Color.BLACK); g2.fill(SpaceBackGround); //Normally, I import this list from somewhere else, but the result //is very similar. ArrayList<myPlanetComponent> planetsList = new ArrayList<myPlanetComponent>(5); //Need to be able to generate at least 2 different maps to demonstrate //the effects of using the New game button. Normally this list is randomly //generated somewhere else, but idea stays the same. if(modeOne){ planetsList.add(new myPlanetComponent(20,20,20)); planetsList.add(new myPlanetComponent(70,30,20)); planetsList.add(new myPlanetComponent(130,210,20)); planetsList.add(new myPlanetComponent(88,400,20)); planetsList.add(new myPlanetComponent(321,123,20)); }else{ planetsList.add(new myPlanetComponent(40,40,20)); planetsList.add(new myPlanetComponent(140,60,20)); planetsList.add(new myPlanetComponent(260,420,20)); planetsList.add(new myPlanetComponent(176,200,20)); planetsList.add(new myPlanetComponent(160,246,20)); } //for all planets for(int i=0; i<planetsList.size()-1; i++){ //planet 1 coordinates myPlanetComponent p1 = planetsList.get(i); if(i == 0){ p1.paintComponent(g2); //Only draw all planets once } //start coordinates of the line int x1 = p1.getX(); int y1 = p1.getY(); //Be smart, and don't do things double! for(int j=i+1; j<planetsList.size(); j++){ myPlanetComponent p2 = planetsList.get(j);; if( i == 0){ p2.paintComponent(g2); //Only Draw all planets once } //planet 2 coordinates, endpoint of the line int x2 = p2.getX(); int y2 = p2.getY(); Line2D.Double tradeRoute = new Line2D.Double(x1, y1, x2, y2); g2.setColor(Color.GREEN); g2.draw(tradeRoute); } } } } return new myMap(mode);}

}

最佳答案

您的问题可能是由于在同一个程序中混合使用了AWT和Swing(重和轻)组件,这是不应该做的事情,除非您有明确的需求,并且知道自己在做什么。我怀疑您是否需要一个ChangeListener,因为JScrollPanes应该能够开箱即用地处理这种事情。您是否尝试过让类扩展JPanel或JComponent而不是Canvas?

另外,在发布代码时,请考虑创建和发布SSCCE,这是一个小型可编译的可运行程序,我们可以运行该程序,对其进行测试,修改和希望更正。如果您创建并发布这种类型的代码,则可能会很快获得一个体面而完整的解决方案。

编辑:我创建了一个测试程序,以查看Canvas对JScrollPanes的影响,就像我想的那样-Canvas覆盖了滚动条以及其他所有内容。若要亲自查看,请编译并运行此代码,然后通过单击并拖动来调整JFrame的大小。 Canvas是蓝色的,位于左侧的JScrollPane中,而JPanel是红色的,位于右侧的JScrollPane中。

import java.awt.*;

import javax.swing.*;

@SuppressWarnings("serial")
public class CanvasInScrollPane extends JPanel {
    private static final Dimension CANVAS_SIZE = new Dimension(300, 300);
    private static final Dimension APP_SIZE = new Dimension(500, 250);
    Canvas canvas = new Canvas();
    JPanel panel = new JPanel();

    public CanvasInScrollPane() {
        canvas.setPreferredSize(CANVAS_SIZE);
        canvas.setBackground(Color.blue);

        panel.setPreferredSize(CANVAS_SIZE);
        panel.setBackground(Color.red);

        setPreferredSize(APP_SIZE);
        setLayout(new GridLayout(1, 0, 5, 0));
        add(new JScrollPane(canvas));
        add(new JScrollPane(panel));
    }

    private static void createAndShowUI() {
        JFrame frame = new JFrame("CanvasInScrollPane");
        frame.getContentPane().add(new CanvasInScrollPane());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                createAndShowUI();
            }
        });
    }
}

09-10 05:04
查看更多