我正在为自己开发一个游戏项目来提高我的Java技能,今天我遇到了一个我无法通过互联网解决的问题。大部分问题已经解决,但最后一点仍然是挑衅!
我有一个扩展Canvas的Object并创建一个我想要显示的地图。我已将此Canvas添加到JPanel,然后将其添加到JScrollPane的视口(添加到游戏的JFrame中)。将一个ChangeListener添加到JScrollPane的视口中,该视口将在JFrame上执行validate方法并在视口上执行重绘方法。
JFrame本身有JMenuBar,在菜单结构中有一个'New Game'选项(以及其他尚未实现的JMenuItems),它将创建一个新的Canvas对象并替换旧的对象并验证(使用validate方法)重新绘制JScrollPane
结果是当我按下New Game JMenuItem时,我的scollpane中会绘制一张新地图。它正确显示地图(Canvas对象),并显示scollbars。菜单位于Canvas和scrollpane的顶部,这是正确的 但是当我稍微改变scollbar的位置(水平或垂直,使用条形图或条形图中的按钮)时,会导致Canvas对象在JFrame上进行转换。这意味着它可能会重叠一个scollbar,菜单栏(并且也会立即被打开菜单上方)与Canvas对象的一块未被显示的视图。只有当滚动条实际击中它的极端点(不能再进一步)时,它才能重新绘制并验证整个场景。但显然,当我只是轻推一个滚动条或类似的东西时,我也希望它也这样做。
我的问题是:如何按预期进行此项工作? 我想我需要对ChangeListener做一些事情,但我似乎无法自己找到解决方案。
正如我注意到大多数人都要求提供源代码,我已经为您提供了这个:
package otherFiles;import java.awt.BorderLayout; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Ellipse2D; import java.awt.geom.Line2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList;
import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JScrollPane;
public class ProblemDemonstrator { private static JScrollPane dispArea = null; private static JPanel mapPanel = null; private static 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); }
}
答案 0 :(得分:4)
您的问题可能是由于您在同一程序中混合了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();
}
});
}
}