JComponents 在 Repaint() 之后移动到 Origin

JComponents Moving to Origin After Repaint()

类似于,但对该问题的回答只说明了他没有使用布局管理器这一事实,而我使用布局管理器(很差),所以它并没有真正帮助,不幸的是。

详情

计划的意图:

要显示 1920x1080 网格的预览(缩小到 640x480 以节省 space),在您更改每个正方形的高度、每个正方形的宽度以及它的列数时进行拉伸和收缩会有的(您首先指定要在网格中的总方块数,因此程序会推断出行数。)

结构:

Current UI, with the two main JPanels outlined in red.

问题

每当我更改任何组件并因此调用 Grid.repaint():

  1. 网格不清晰,导致出现多行;
  2. 所有侧边栏组件都绘制在左上角,同时仍然 showing/functioning 在侧边栏上;
  3. 出于某种原因,网格将自身调整为比正常情况更宽。

Current UI, but borked.

我尝试过的

  1. 将 repaint() 区域更改为仅覆盖网格的矩形,
  2. 检查文档以了解有关此的任何信息,
  3. Google,
  4. 问你们

代码

Reprex:(简化为“按下按钮重现”,同时仍然保留潜在问题区域的本质。)

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

public class BuildGridGUI2
{
    static final int WIDTH_MIN = 100;
    static final int WIDTH_MAX = 2000;
    static final int WIDTH_INIT = 520;
    
    static final int HEIGHT_MIN = 100;
    static final int HEIGHT_MAX = 2000;
    static final int HEIGHT_INIT = 300;
    
    int widthOfFrames = 255;
    int heightOfFrames = 255;
    int numCols = 3;
    int numFrames = 1;
    
    
    private final JComponent makeUI()
    {
        JPanel p = new JPanel();
        
        
        p.setLayout(new BorderLayout());
        
        Grid g = new Grid();
        JComponent j = makeSideMenu(g);
        
        g.setBorder(BorderFactory.createCompoundBorder(
              BorderFactory.createLineBorder(Color.red),
              g.getBorder()));
        
        
        j.setBorder(BorderFactory.createCompoundBorder(
                  BorderFactory.createLineBorder(Color.red),
                  j.getBorder()));
        
        
        p.add(j, BorderLayout.EAST);
        p.add(g, BorderLayout.WEST);
        
        
        return p;
    }
    
    private final JComponent makeSideMenu(Grid grid)
    {
        JPanel p = new JPanel();
        
        JLabel numColsSelectorLabel = new JLabel("Frames Per Column");
        JSpinner numColsSelectorField = new JSpinner();
        
        JLabel widthLabel = new JLabel("Width per Frame (Pixels)");
        JSpinner widthSpinner = new JSpinner();
        JSlider widthSlider = new JSlider(WIDTH_MIN, WIDTH_MAX, WIDTH_INIT);
        
        JLabel heightLabel = new JLabel("Height per Frame (Pixels)");
        JSpinner heightSpinner = new JSpinner();
        JSlider heightSlider = new JSlider(BuildGridGUI2.HEIGHT_MIN, BuildGridGUI2.HEIGHT_MAX, BuildGridGUI2.HEIGHT_INIT);
        JButton confirmButton = new JButton("Confirm");
        
        
        
        numColsSelectorField.setEditor(new JSpinner.NumberEditor(numColsSelectorField));
        numColsSelectorField.setMaximumSize(numColsSelectorField.getPreferredSize());
        
        
        
        widthSlider.setMajorTickSpacing(300);
        widthSlider.setMinorTickSpacing(20);
        widthSlider.setPaintTicks(true);
        widthSlider.setPaintLabels(true);
        
        
        
        widthSpinner.setEditor(new JSpinner.NumberEditor(widthSpinner));
        widthSpinner.setPreferredSize(new Dimension(70, 30));
        widthSpinner.setMaximumSize(widthSpinner.getPreferredSize());
        
        
        
        
        heightSlider.setMajorTickSpacing(300);
        heightSlider.setMinorTickSpacing(20);
        heightSlider.setPaintTicks(true);
        heightSlider.setPaintLabels(true);
        
        
        
        heightSpinner.setEditor(new JSpinner.NumberEditor(heightSpinner));
        heightSpinner.setPreferredSize(new Dimension(70, 30));
        heightSpinner.setMaximumSize(heightSpinner.getPreferredSize());
        
        
        confirmButton.addActionListener(new ActionListener() {
            @Override public void actionPerformed(ActionEvent e)
            {
                widthOfFrames = 200;
                grid.refresh();
            }
        });
        
        
        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
        p.setPreferredSize(new Dimension(300, 480));
        p.setSize(new Dimension(300, 480));
        
        numColsSelectorLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
        numColsSelectorField.setAlignmentX(Component.CENTER_ALIGNMENT);
        widthSlider.setAlignmentX(Component.CENTER_ALIGNMENT);
        heightSlider.setAlignmentX(Component.CENTER_ALIGNMENT);
        confirmButton.setAlignmentX(Component.CENTER_ALIGNMENT);
        widthLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
        heightLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
        widthSpinner.setAlignmentX(Component.CENTER_ALIGNMENT);
        heightSpinner.setAlignmentX(Component.CENTER_ALIGNMENT);
        
        
        p.add(Box.createRigidArea(new Dimension(300, 30)));
        p.add(numColsSelectorLabel);
        p.add(Box.createRigidArea(new Dimension(300, 5)));
        p.add(numColsSelectorField);
        p.add(Box.createRigidArea(new Dimension(300, 25)));
        p.add(widthLabel);
        p.add(Box.createRigidArea(new Dimension(300, 3)));
        p.add(widthSpinner);
        p.add(widthSlider);
        p.add(Box.createRigidArea(new Dimension(300, 25)));
        p.add(heightLabel);
        p.add(Box.createRigidArea(new Dimension(300, 3)));
        p.add(heightSpinner);
        p.add(heightSlider);
        p.add(Box.createRigidArea(new Dimension(300, 45)));
        p.add(confirmButton);
        
        
        return p;
    }
    
    
    private static void createAndShowGUI() {
        BuildGridGUI2 b = new BuildGridGUI2();
        JFrame mainFrame = new JFrame("Grid Builder");
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mainFrame.setSize(940, 480);
        mainFrame.getContentPane().add(b.makeUI());
        mainFrame.setResizable(false);
        mainFrame.setVisible(true);
    }
    
    public static void main(String... args)
    {
        SwingUtilities.invokeLater(new Runnable() {
            @Override public void run() {
                createAndShowGUI();
            }
        });
    }
    
    
    class Grid extends JComponent
    {
        static final int CANVAS_WIDTH = 640;
        static final int CANVAS_HEIGHT = 480;
        
        private final double conversionRatio = 4.0/9.0; // 1080p scaled down to 480p preview
        
        private int squareHeight, squareWidth, numColumns, numRows;
        
        
        public Grid()
        {
            setOpaque(true);
            setSize(CANVAS_WIDTH, CANVAS_HEIGHT); // trying to get the size to work properly.
            setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));
        }
        
        @Override
        public Dimension getPreferredSize()
        {
            return new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT);
        }
        
        @Override
        public void paintComponent(Graphics g)
        {
            
            super.paintComponent(g);
            
            updateVars();
            
            int k;
            
            for (k = 0; k < numColumns; k++)
            {
                // @param: (x1, y1), (x2, y2)
                g.drawLine(k * squareWidth, 0, k * squareWidth, CANVAS_HEIGHT);
            }
            
            for (k = 0; k < numRows; k++)
            {
                g.drawLine(0, k * squareHeight, CANVAS_WIDTH, k * squareHeight);
            }
        }
        
        public void refresh()
        {
            // Attempting to set the repaint area to only the grid region
            // because CTRL+F is free and parameters are not
            repaint(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
        }
        
        public void updateVars()
        {
            this.squareWidth = (int)(
                    (double)BuildGridGUI2.this.widthOfFrames
                    *
                    conversionRatio);
            
            this.squareHeight = (int)(
                    (double)BuildGridGUI2.this.heightOfFrames
                    *
                    conversionRatio);
            
            this.numColumns = BuildGridGUI2.this.numCols;
            
            this.numRows = (int)(
                    (
                            (double)BuildGridGUI2.this.numFrames
                            /
                            numColumns
                    )
                    + 0.5);
        }
        
    }
    
}

Actual Source Code (Not strictly relevant, but if you're in the mood for code review 那么我很想学习更好的编码约定。)

谢谢!

我对您发布的代码做了一些修改。

  • 我更改了 class Grid,使其扩展 JPanel 而不是 JComponent,因为自定义绘画通常是在 JPanel 上完成的。
  • 我将类型为 Grid 的实例成员变量 grid 添加到 class BuildGridGUI2 而不是创建一个并将其作为参数发送给方法 makeSideMenu.

这是经过我修改后的代码(以及我喜欢的编码风格)。它只是解决了您报告的问题,仅此而已。

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

public class BuildGridGUI2 {
    static final int WIDTH_MIN = 100;
    static final int WIDTH_MAX = 2000;
    static final int WIDTH_INIT = 520;

    static final int HEIGHT_MIN = 100;
    static final int HEIGHT_MAX = 2000;
    static final int HEIGHT_INIT = 300;

    int widthOfFrames = 255;
    int heightOfFrames = 255;
    int numCols = 3;
    int numFrames = 1;
    Grid  grid;

    private final JComponent makeUI() {
        JPanel p = new JPanel();
        p.setLayout(new BorderLayout());
        grid = new Grid();
        JComponent j = makeSideMenu();
        grid.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.red),
                                                          grid.getBorder()));

        j.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.red),
                                                       j.getBorder()));
        p.add(j, BorderLayout.EAST);
        p.add(grid, BorderLayout.WEST);
        return p;
    }

    private final JComponent makeSideMenu() {
        JPanel p = new JPanel();
        JLabel numColsSelectorLabel = new JLabel("Frames Per Column");
        JSpinner numColsSelectorField = new JSpinner();
        JLabel widthLabel = new JLabel("Width per Frame (Pixels)");
        JSpinner widthSpinner = new JSpinner();
        JSlider widthSlider = new JSlider(WIDTH_MIN, WIDTH_MAX, WIDTH_INIT);
        JLabel heightLabel = new JLabel("Height per Frame (Pixels)");
        JSpinner heightSpinner = new JSpinner();
        JSlider heightSlider = new JSlider(BuildGridGUI2.HEIGHT_MIN,
                                           BuildGridGUI2.HEIGHT_MAX,
                                           BuildGridGUI2.HEIGHT_INIT);
        JButton confirmButton = new JButton("Confirm");
        numColsSelectorField.setEditor(new JSpinner.NumberEditor(numColsSelectorField));
        numColsSelectorField.setMaximumSize(numColsSelectorField.getPreferredSize());
        widthSlider.setMajorTickSpacing(300);
        widthSlider.setMinorTickSpacing(20);
        widthSlider.setPaintTicks(true);
        widthSlider.setPaintLabels(true);
        widthSpinner.setEditor(new JSpinner.NumberEditor(widthSpinner));
        widthSpinner.setPreferredSize(new Dimension(70, 30));
        widthSpinner.setMaximumSize(widthSpinner.getPreferredSize());

        heightSlider.setMajorTickSpacing(300);
        heightSlider.setMinorTickSpacing(20);
        heightSlider.setPaintTicks(true);
        heightSlider.setPaintLabels(true);

        heightSpinner.setEditor(new JSpinner.NumberEditor(heightSpinner));
        heightSpinner.setPreferredSize(new Dimension(70, 30));
        heightSpinner.setMaximumSize(heightSpinner.getPreferredSize());

        confirmButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                widthOfFrames = 200;
                grid.refresh();
            }
        });

        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
        p.setPreferredSize(new Dimension(300, 480));
        p.setSize(new Dimension(300, 480));

        numColsSelectorLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
        numColsSelectorField.setAlignmentX(Component.CENTER_ALIGNMENT);
        widthSlider.setAlignmentX(Component.CENTER_ALIGNMENT);
        heightSlider.setAlignmentX(Component.CENTER_ALIGNMENT);
        confirmButton.setAlignmentX(Component.CENTER_ALIGNMENT);
        widthLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
        heightLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
        widthSpinner.setAlignmentX(Component.CENTER_ALIGNMENT);
        heightSpinner.setAlignmentX(Component.CENTER_ALIGNMENT);

        p.add(Box.createRigidArea(new Dimension(300, 30)));
        p.add(numColsSelectorLabel);
        p.add(Box.createRigidArea(new Dimension(300, 5)));
        p.add(numColsSelectorField);
        p.add(Box.createRigidArea(new Dimension(300, 25)));
        p.add(widthLabel);
        p.add(Box.createRigidArea(new Dimension(300, 3)));
        p.add(widthSpinner);
        p.add(widthSlider);
        p.add(Box.createRigidArea(new Dimension(300, 25)));
        p.add(heightLabel);
        p.add(Box.createRigidArea(new Dimension(300, 3)));
        p.add(heightSpinner);
        p.add(heightSlider);
        p.add(Box.createRigidArea(new Dimension(300, 45)));
        p.add(confirmButton);
        return p;
    }

    private static void createAndShowGUI() {
        BuildGridGUI2 b = new BuildGridGUI2();
        JFrame mainFrame = new JFrame("Grid Builder");
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mainFrame.setSize(940, 480);
        mainFrame.getContentPane().add(b.makeUI());
        mainFrame.setResizable(false);
        mainFrame.setVisible(true);
    }

    public static void main(String... args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }

    class Grid extends JPanel {
        static final int CANVAS_WIDTH = 640;
        static final int CANVAS_HEIGHT = 480;

        private final double conversionRatio = 4.0 / 9.0; // 1080p scaled down to 480p preview

        private int squareHeight, squareWidth, numColumns, numRows;

        public Grid() {
            setOpaque(true);
            setSize(CANVAS_WIDTH, CANVAS_HEIGHT); // trying to get the size to work properly.
            setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT);
        }

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            updateVars();
            int k;
            for (k = 0; k < numColumns; k++) {
                // @param: (x1, y1), (x2, y2)
                g.drawLine(k * squareWidth, 0, k * squareWidth, CANVAS_HEIGHT);
            }
            for (k = 0; k < numRows; k++) {
                g.drawLine(0, k * squareHeight, CANVAS_WIDTH, k * squareHeight);
            }
        }

        public void refresh() {
            // Attempting to set the repaint area to only the grid region
            // because CTRL+F is free and parameters are not
            repaint(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
        }

        public void updateVars() {
            this.squareWidth = (int) ((double) BuildGridGUI2.this.widthOfFrames * conversionRatio);
            this.squareHeight = (int) ((double) BuildGridGUI2.this.heightOfFrames * conversionRatio);
            this.numColumns = BuildGridGUI2.this.numCols;
            this.numRows = (int) (((double) BuildGridGUI2.this.numFrames / numColumns) + 0.5);
        }
    }
}

一个提示:尝试使用 JComponent 的特定子 class 而不是 JComponent。例如,我建议将 return 类型的方法 makeSideMenu() 更改为 JPanel 而不是 JComponent 因为该方法实际上 return 是 JPanel.

All of the sidebar components get painted at the top-left corner, while still showing/functioning on the sidebar;

JComponent 是一个抽象 class,所有 Swing 组件都从中扩展。它没有默认的绘画逻辑。

因此调用 super.paintComponent(...) 并没有真正做任何事情。

特别是在进行自定义绘制之前不会清除组件的背景。这将导致您看到的绘画伪影。

任何时候你扩展 JComponent 你的逻辑应该是这样的:

protected void paintComponent(Graphics g)
{
    super.paintComponent(g);

    //  clear background

    g.setColor( getBackground() );
    g.fillRect(0, 0, getWidth(), getHeight());

    //  do custom painting

    g.setColor( getForeground() );
    ...
}

注意:这就是 Abra 提到的许多人为简单的自定义绘画覆盖 JPanel 的原因。 JPanelpaintComponent(...)方法默认会清空背景