Wrong JPanel displayed in CardLayout. Issues with

2019-08-03 15:55发布

I have a top-level GUI class called SpacePotaters (subclass of JFrame) and I add a JPanel called panelViews (which uses the CardLayout layout manager) to its content pane. Among other things, I add a MainMenu object (subclass of JPanel) and a GameView object (subclass of JPanel) as cards to panelView.

public SpacePotaters(){
    super("Space Potaters");
    Container content = getContentPane();

    // initialize components
    gameView = new GameView(this);
    mainMenu = new MainMenu(this);
    leaderboard = new Leaderboard(this);
    instructions = new JPanel(); // to do
    cLayout = new CardLayout();

    // add "cards" to CardLayout manager
    panelViews = new JPanel(cLayout);
    panelViews.setPreferredSize(new Dimension(WINDOW_WIDTH, WINDOW_HEIGHT));
    panelViews.add("gameView", gameView);
    panelViews.add("mainMenu", mainMenu);
    panelViews.add("leaderboard", leaderboard);
    panelViews.add("instructions", instructions);

    // initially display menu menu
    content.add(panelViews);
    cLayout.show(panelViews,"mainMenu");

    addWindowListener(this);
    pack();
    setResizable(false);

    // relocate window to center of screen
    setLocationRelativeTo(getRootPane());
}

In case its useful, the main menu paint method is here:

@Override
public void paintComponent(Graphics g){
    super.paintComponent(g);
    g.drawImage(mainMenuBackground, 0, 0, spTop.getWindowWidth(), spTop.getWindowHeight(), null);

}

Right now I just paint the background image - later I'll add JButtons that when clicked will switch to different cards.

The idea is that I start off with the main menu card and switch to the other cards when necessary. I ran the program and noticed that the main menu card briefly flashes before being replaced by the image created for gameView. I know that I should wait to render the gameView graphics until I actually switch to that card, so that the game doesn't start prematurely. That's not where I get confused. Even if I don't wait, shouldn't gameView just start running without being able to view it? In other words, shouldn't mainMenu remain the only visible card regardless of what is going on inside gameView?

I figured the issue was something within gameView, which uses active rendering and double-buffering. GameView has this method which is called once every loop of the game:

public void paintScreen(){
    // get fresh graphics context for GameView each time
    Graphics context = getGraphics();
    if (context != null && dbImage != null){
        context.drawImage(dbImage, 0, 0, null);
    }
    context.dispose();
}

Commenting out the body of this method produces the desired results - only the mainMenu is shown. I'm confused as to why this is the case. I thought each component (JFrame, JPanel, etc) has its own graphics context. So when I call getGraphics() inside GameView, shouldn't it return the graphics context for the gameView panel, not the graphics context for spTop or panelViews? And since it only changes that specific graphics context, if the gameView panel is not visible (which it shouldn't be if I use CardLayout's show() method on the mainMenu object), then it shouldn't affect my view of the main menu. Any ideas?

Note: If I test both gameView and mainMenu with isVisible(), gameView returns false and mainMenu returns true. Also, I used getGraphics() to test if the mainMenu, spTop, and gameView graphics contexts were all different - they were. So gameView is drawing an image on its own graphics context and is not visible, but the effects of drawImage( ) within paintScreen() are still showing! Really confused.

1条回答
做自己的国王
2楼-- · 2019-08-03 16:11

After spending way too much time trying to figure out what's going on, I can only conclude that active rendering within an embedded graphics component (at least in the form I'm using it) ignores the visibility of that component. Therefore, drawImage() within paintScreen() will always show visible results, regardless of whether the JPanel it is called from is visible or not.

I'm not entirely sure, but I think this confusion might have something to do with trying to mix active rendering with passive rendering. The mainMenu overrides paintComponent() and so uses passive rendering to paint itself. It might be that by setting the visibility of my gameView to false, the top-level JFrame simply does not call gameView's paintComponent(). However, since gameView draws with paintScreen(), setting its visibility to false does not affect its behavior. This is all speculation based on experimentation, since I could not find any concrete information about this.

Also, I'm not sure if I would have gotten the same results if I would have tried to perform active rendering using a BufferStrategy (i.e. if BufferStrategy's show() would also ignore the visibility of its related graphics component and draw to the visible screen as well). That's something that still needs to be researched.

As far as how I'm going to reconcile this understanding with my current program - since I don't want to rewrite everything, I've decided to use a hybrid approach. I'm going to use passive rendering for switching between my panelViews/"cards" (using CardLayout). Only when I switch to gameView do I turn on its active-rendering behavior. When I exit and return to another card, I turn the active-rendering of gameView back off. I know that mixing passive and active rendering is discouraged, but hopefully no issues arise since I never use both strategies concurrently. This approach currently works for switching between mainMenu and gameView, so I expect it to work when I add other passively-rendered components.

查看更多
登录 后发表回答