首页 文章

JScrollPane中的画布仅在滚动条处于最极 endpoints 时才重新绘制

提问于
浏览
1

我已经遇到了一个问题,我似乎无法借助互联网解决这个问题 . 大部分问题已经解决了,但最后一点仍然是挑衅!

我已将此Canvas添加到JPanel,然后将其添加到JScrollPane的视口(添加到游戏的JFrame中) . 将一个ChangeListener添加到JScrollPane的视口中,该视口将对JFrame和视口上的重绘方法执行validate方法 .
JFrame本身具有JMenuBar,其中在菜单结构中有一个'New Game'选项(以及其他尚未实现的JMenuItems),它将创建一个新的Canvas对象并替换旧的对象并验证(使用validate方法)重新绘制JScrollPane .

结果是当我按下New Game JMenuItem时,我的scollpane中会绘制一张新 Map . 它正确显示 Map (Canvas对象),并显示scollbars . 菜单位于Canvas和scrollpane的顶部,这是正确的 .
但是当我稍微改变scollbar的位置(水平或垂直,使用条形图或条形图中的按钮)时,会导致Canvas对象在JFrame上进行转换 . 这意味着它可能会重叠一个scollbar,菜单栏(也会立即被绘制在打开的菜单上),而Canvas对象的一块未被显示出来 . 只有当滚动条实际击中它时'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只会轻推一个滚动条或类似的东西 .

我的问题是:我怎样才能使这项工作按预期进行?
我想我需要对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);

}

}

1 回答

  • 4

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

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

    编辑:我创建了一个测试程序,看看Canvas对JScrollPanes有什么影响,就像我想的那样 - 画布覆盖了滚动条,以及其他一切 . 要自己查看编译并运行此代码,然后单击并拖动来调整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();
                }
            });
        }
    }
    

相关问题