0

I have a GUI set up like this:

  • JFrame
    • JScrollPane, in the CENTER of the JFrame's default BorderLayout
      • Vertical Box, as in Box.getVerticalBox()
        • A bunch of JPanels

In response to KeyEvents, the program should zoom in or out. I am attempting to do this by calling setPreferredSize() on the JPanels, but it doesn't "take." What happens is that the contents of each JPanel is zoomed in or out as they should be, but the overall layout doesn't change. That is, if a JPanel was 100x100 before the zoom, and it was to have zoomed out to be 50x50, then the contents will be drawn half as large (correctly), but the area of the JPanel taken is still 100x100. The other JPanels aren't "pushed up."

Oddly, if I manually resize the window, then things settle to what they should be. I thought the problem might be that I need to call some combination of invalidate(), validate(), revalidate(), pack(), doLayout(), etc., but none of these seem to have any effect.

I've looked and there are plenty of questions like this, and some answers, but nothing that seems to help. It's probably something obvious, but I am at a loss.

Here is a minimal example.

import javax.swing.SwingUtilities;
public class Main {
      
     public static void main(String args[]) {
                    
            SwingUtilities.invokeLater(new Runnable() {
              public void run() {
              MainWindow mWindow = new MainWindow();
              }
            });
          }
}
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JComponent;
import javax.swing.Box;

public class MainWindow extends JFrame implements KeyListener {
  
  public static int numPages = 4;
  
  public static float[] zoomOptions = {
      18, 27, 36, 54, 72, 96, 120, 144, 
      180, 216,252, 288, 360, 432, 504, 576 
      };
  public static int zoomIndex = 4;
  
  private PagePanel[] pagePanels = null;
  private JScrollPane scroller = null;
  
  public MainWindow() {
    
    this.setBackground(Color.WHITE);
    this.setDefaultCloseOperation(EXIT_ON_CLOSE);
    
    this.addKeyListener(this);
    
    this.pagePanels = new PagePanel[numPages];
    for (int i = 0; i < this.numPages; i++)
      {
        pagePanels[i] = new PagePanel(i);        
        pagePanels[i].setReferenceSize(300,200);
        pagePanels[i].resizeTo();
      }
    
    JComponent vbox = Box.createVerticalBox();
    
    for (int i = 0; i < pagePanels.length; i++)
      vbox.add(pagePanels[i]);
    
    this.scroller = new JScrollPane(vbox);
    this.add(scroller);
    
    this.setPreferredSize(new Dimension(500,500));
    this.pack();
    this.setVisible(true);
    this.setFocusable(true);
  }
  
  
  private void resizeAllSubPanels() {
    
    for (int i = 0; i < this.pagePanels.length; i++)
      pagePanels[i].resizeTo();
    
    // Tried various things here to force adjustment of layout.
  }

  public void keyReleased(KeyEvent e) {  }
  public void keyTyped(KeyEvent e) {  }
  
  public void keyPressed(KeyEvent e) {
    
    char c = e.getKeyChar();
    if ((c == '-') || (c == '_'))
      {
        if (this.zoomIndex > 0)
          --zoomIndex;
        
        resizeAllSubPanels();
        this.repaint();
      }
    else if ((c == '+') || (c == '='))
      {
        if (this.zoomIndex + 1 < this.zoomOptions.length)
          ++zoomIndex;

        resizeAllSubPanels();
        this.repaint();
      }
  }
}
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Dimension;
import javax.swing.JPanel;

public class PagePanel extends JPanel {
  
  private int referenceHeight = -1;
  private int referenceWidth = -1;
  
  public PagePanel(int pageNum) {
    
    // Will be using absolute positioning. Could this be the problem?
    this.setLayout(null);
  }
  
  public void setReferenceSize(int h,int w) {
    this.referenceHeight = h;
    this.referenceWidth = w;
  }
  
  public void resizeTo() {
    
    float zoomRatio = MainWindow.zoomOptions[MainWindow.zoomIndex] / 72.0f;
    int w = (int) (referenceWidth * zoomRatio);
    int h = (int) (referenceHeight * zoomRatio);
            
    this.setPreferredSize(new Dimension(w,h));
    this.repaint();
  }
  
  public void paintComponent(Graphics g) {
    
    super.paintComponent(g);
    g.clearRect(0,0,this.getSize().width,this.getSize().height);

    float zoomRatio = MainWindow.zoomOptions[MainWindow.zoomIndex] / 72.0f;
    g.fillOval(
        (int) (10 * zoomRatio),
        (int) (10 * zoomRatio),
        (int) (100 * zoomRatio),  
        (int) (100 * zoomRatio));

    Rectangle r = this.getVisibleRect();
    g.drawRect(r.x,r.y,r.width,r.height);
  }
}
Randall Fairman
  • 296
  • 3
  • 9
  • Not sure I'm 100% certain of your intention, but I might recommend use of `JLayer`'s "zoom" functionality, for [example](https://stackoverflow.com/questions/21439341/zooming-jlayeredpane-via-jlayer-and-the-layerui) – MadProgrammer Jan 28 '22 at 22:38
  • You also need to trigger a layout pass, but you probably need to trigger it on the parent container. Call `revalidate` on the parent container to trigger a new layout pass before you do a pain pass – MadProgrammer Jan 28 '22 at 22:38
  • I added an invalidate() on each of the JPanels as they are resized in PagePanel.resizeTo(), then an invalidate()/revalidate() in MainWindow.resizeAllSubPanels(). It works. – Randall Fairman Jan 29 '22 at 14:11
  • I would have thought that calling these on the super-container led to a cascade of revalidations to all the sub-items, but I guess not. Thank you. – Randall Fairman Jan 29 '22 at 14:13
  • Think about it, whose responsible for laying out the component? The parent container! So, if you change the component's `preferredSize`, you need the parent container to perform a new layout pass. The parent container is unlikely to invalidate itself and, unless you make it do so, the window won't change size just because a child component changed size – MadProgrammer Jan 29 '22 at 21:18

0 Answers0