JScrollPane 中的画布仅在滚动条位于最极端点时重新绘制
我正在为自己开发一个游戏项目,以提高我的 Java 技能,今天我遇到了一个我似乎无法借助互联网解决的问题。这个问题大部分已经解决了,但最后一点仍然是挑衅!
我有一个对象,它扩展了 Canvas 并创建了我想要显示的地图。我已将此 Canvas 添加到 JPanel,然后将其添加到 JScrollPane(添加到游戏的 JFrame 中)的视口。将 ChangeListener 添加到 JScrollPane 的视口中,它将在 JFrame 上执行 validate 方法,并在视口上执行 repaint 方法。
JFrame 本身有 JMenuBar,其中菜单结构中有一个“新游戏”选项(以及其他尚未实现的 JMenuItems),它将创建一个新的 Canvas 对象并替换旧的对象并验证(使用验证方法)重新绘制 JScrollPane 。
结果是,当我按下 New Game JMenuItem 时,我的窗口窗格中会绘制出一张新地图。它正确显示地图(画布对象),并显示滚动条。菜单位于画布和滚动窗格的顶部,这是正确的。
但是,当我稍微更改滚动条的位置(水平或垂直,使用滚动条或滚动条中的按钮)时,会导致 Canvas 对象在 JFrame 上进行平移。这意味着它可能会与滚动条、菜单栏(并且立即绘制在打开的菜单上)重叠,而视图之外的 Canvas 对象部分不会显示。仅当滚动条实际到达其极值点(无法再进一步)时,它才会按应有的方式重新绘制并验证整个场景。但显然,当我只是轻推滚动条或类似的东西时,我也希望它这样做。
我的问题是:我怎样才能使这项工作按预期进行?
我认为我需要对 ChangeListener 做一些事情,但我自己似乎无法找到解决方案。
正如我注意到你们大多数人都要求源代码,我已经为您提供了:
package 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); }
}
I'm working on a game project for myself to improve my Java skills and I today I've hit a problem I cannot seem to solve with the aid of the internet. Most of this problem has been solved but this last little bit still remains defiant!
I've got an Object that extends Canvas and creates a map I want to display. I've added this Canvas to a JPanel, which is then added to the viewport of a JScrollPane (which is added to the JFrame of the game). A ChangeListener is added to the JScrollPane's viewport, which will perform the validate method on the JFrame and the repaint method on the viewport.
The JFrame itself has JMenuBar, where in the menu structure there is a 'New Game' option (among other not yet implemented JMenuItems) that will create a new Canvas object and replaces the old one and validates (using the validate methods) repaints the JScrollPane.
The result is that when I press the New Game JMenuItem, a new map is drawn out in my scollpane. It displays the map (the Canvas Object) correctly, with the scollbars showing up. The menu is on top of the Canvas and scrollpane, which is correct.
But when I slightly change the position of the scollbar (horizontal or vertical, using the bar or the buttons in the bar) it results in the Canvas Object being translated across the JFrame. Meaning it will overlap possibly a scollbar, the menubar (and also immediately is drawn over an opened menu) with the piece of the Canvas object out of view not being shown. Only when a scrollbar actually hits it's extreme points (cannot go any further), it repaints and validates the whole scene as it should. But obviously, I want it to do this too when I'm only nudging a scrollbar or something similar.
My question is: How can I make this work as intended?
I think I need to do something with the ChangeListener, but I can't seem to find a solution by myself.
As I noticed most of you guys ask for source code, I've provided this for you:
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); }
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您的问题可能是由于您在同一程序中混合了 AWT 和 Swing(重量级和轻量级)组件,这是不应该做的事情,除非您明确需要这样做并且知道自己在做什么。我怀疑您是否需要 ChangeListener,因为 JScrollPanes 应该能够开箱即用地处理此类事情。您是否尝试过让您的类扩展 JPanel 或 JComponent 而不是 Canvas?
另外,在发布代码时,请考虑创建并发布一个 SSCCE,一个小的可编译的可运行程序,我们可以运行、测试、修改、并希望是正确的。如果您创建并发布此类代码,您可能会很快获得一个体面且完整的解决方案。
编辑:我创建了一个测试程序来查看 Canvas 对 JScrollPanes 的影响,正如我所想的那样——Canvas 覆盖了滚动条以及其他所有内容。要亲自查看,请编译并运行此代码,然后通过单击并拖动来调整 JFrame 的大小。 Canvas 是蓝色的,位于左侧的 JScrollPane 中,而 JPanel 是红色的,位于右侧的 JScrollPane 中。
Your problem may be due to your mixing AWT and Swing (heavy weight and light weight) components in the same program, and this is something that shouldn't be done, unless you have definite need of this and know what you're doing. I doubt that you need a ChangeListener as JScrollPanes should be able to handle this sort of thing out of the box. Have you tried having your class extend JPanel or JComponent instead of Canvas?
Also, when posting code, consider creating and posting an SSCCE, a small compilable runnable program that we can run, test, modify, and hopefully correct. If you create and post this type of code, you'll likely get a decent and complete solution quickly.
edit: I created a test program to see what effect Canvas has on JScrollPanes, and it's as I thought -- the Canvas covers over the scroll bars, and everything else. To see for yourself compile and run this code and then resize the JFrame by clicking and dragging. The Canvas is blue and is in a JScrollPane on the left while the JPanel is red and is in a JScrollPane on the right.