为何自定义PieChart(继承JComponent)仅一个实例在主窗口显示?
嘿,这问题我刚学Swing的时候也踩过一模一样的坑!核心原因是Swing布局管理器的默认行为和你手动设置组件位置的方式冲突了,咱们一步步来解决:
问题根源分析
JFrame的内容面板(contentPane)默认使用BorderLayout布局管理器。当你直接调用contentPane.add(xxx)而不指定布局约束时,所有组件都会被塞进BorderLayout.CENTER区域——但这个区域只能显示最后添加的那个组件,前面的都会被完全覆盖!而且布局管理器会彻底接管组件的大小和位置,你手动调用setBounds()的代码会被直接忽略,这就是为什么第一个饼图的宽高变成0的原因。
另外还有个小细节:Swing组件的绘制逻辑应该重写paintComponent()方法,而不是直接重写paint(),前者才是Swing官方推荐的绘制入口,能保证双缓冲、背景清除等机制正常工作。
具体修复步骤
1. 修正PieChart的绘制方法
把paint()改成paintComponent(),必须调用父类方法,同时用组件自身的实际边界(getBounds())代替你自己存的bounds变量:
public class PieChart extends JComponent { private static final long serialVersionUID = -5712412361082680298L; public ArrayList<PieSlice> slices = new ArrayList<PieSlice>(); // 改成无参构造,让布局管理器控制大小位置 PieChart() {} @Override protected void paintComponent(Graphics g) { super.paintComponent(g); // 必须调用,确保背景正常清除 drawPie((Graphics2D) g, getBounds(), slices); } void drawPie(Graphics2D g, Rectangle area, ArrayList<PieSlice> slices) { double total = 0.0D; for (PieSlice slice : slices) { total += slice.value; } double curValue = 0.0D; for (PieSlice slice : slices) { int startAngle = (int) (curValue * 360 / total); int arcAngle = (int) (slice.value * 360 / total); g.setColor(slice.color); g.fillArc(area.x, area.y, area.width, area.height, startAngle, arcAngle); curValue += slice.value; } } public void addPieSlice(double value, Color color) { slices.add(new PieSlice(value, color)); } }
2. 用布局管理器重新组织MainView的组件
移除所有手动设置setBounds()的代码,改用布局管理器来安排组件位置。比如我们用BorderLayout把界面分成三个区域:
- 顶部放按钮面板
- 左侧放停车场视图
- 右侧用一个JPanel装两个饼图(用GridLayout上下排列)
修改MainView的代码:
public class MainView extends JFrame { private static final long serialVersionUID = 1L; private CarParkView carParkView; public PieChart occupiedPieChart; public PieChart subscribersPieChart; public JPanel buttonPane; public JButton resume; public JButton pause; public JButton plusHundredTicks; public MainView(Model model, int numberOfFloors, int numberOfRows, int numberOfPlaces) { this.setPreferredSize(new Dimension(1500,1000)); carParkView = new CarParkView(model, numberOfFloors, numberOfRows, numberOfPlaces); occupiedPieChart = new PieChart(); subscribersPieChart = new PieChart(); // 按钮面板用默认FlowLayout排列 buttonPane = new JPanel(); resume = new JButton("Resume"); pause = new JButton("Pause"); plusHundredTicks = new JButton("+100 ticks"); buttonPane.add(resume); buttonPane.add(pause); buttonPane.add(plusHundredTicks); // 给饼图添加数据 occupiedPieChart.addPieSlice(10, Color.WHITE); occupiedPieChart.addPieSlice(90, Color.GREEN); subscribersPieChart.addPieSlice(50, Color.WHITE); subscribersPieChart.addPieSlice(50, Color.BLUE); // 创建面板装两个饼图,两行一列,组件间留10像素间距 JPanel pieChartPanel = new JPanel(new GridLayout(2, 1, 10, 10)); pieChartPanel.add(occupiedPieChart); pieChartPanel.add(subscribersPieChart); // 用BorderLayout组织整个界面 Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout(10, 10)); // 组件间留10像素间距 contentPane.add(buttonPane, BorderLayout.NORTH); // 按钮放顶部 contentPane.add(carParkView, BorderLayout.CENTER); // 停车场视图放中间 contentPane.add(pieChartPanel, BorderLayout.EAST); // 饼图放右侧 pack(); setVisible(true); updateView(); } public void updateView() { carParkView.updateView(); subscribersPieChart.repaint(); occupiedPieChart.repaint(); } }
为什么这样改就好了?
- 用布局管理器代替手动
setBounds():Swing的布局管理器会自动根据窗口大小调整组件的位置和尺寸,不会出现组件被覆盖的情况,而且能适配窗口缩放。 - 重写
paintComponent():这是Swing组件绘制的标准方式,能避免绘制残留、保证双缓冲生效,让你的饼图绘制更稳定。
这样修改后,两个饼图应该就能正常显示啦!刚开始学Swing的时候,布局管理器确实容易搞混,多试几次就顺手了😉
内容的提问来源于stack exchange,提问作者Stijn Westerhof
相关产品推荐
相关产品推荐

