Reputation: 3845
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); }
}
Upvotes: 1
Views: 6137
Reputation: 285415
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.
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();
}
});
}
}
Upvotes: 4