ContentPane和JPanel之间的关系是什么?

36

我发现一个例子,其中按钮被添加到面板(JPanel实例),然后将面板添加到容器(由getContentPane()生成的实例),然后通过构造方法将容器包含在JFrame(窗口)中。

我尝试了两种方式:

  1. 我摆脱了容器。更具体地说,我先将按钮添加到面板(JPanel实例),然后再将面板添加到窗口(JFrame实例)。它运行得很好。

  2. 我摆脱了面板。更具体地说,我直接将按钮添加到容器,然后将容器添加到窗口(JFrame实例)。

因此,我不理解以下两点:

  1. 为什么我们有两种竞争机制来完成相同的事情?

  2. 使用容器与面板(JPanel)组合的原因是什么? (例如,我们将按钮包含在JPanel中,然后将JPanel包含在容器中)。我们可以在JPanel中包含JPanel吗?我们可以在容器中包含容器吗?

添加:

也许我的问题的本质可以放在一行代码中:

frame.getContentPane().add(panel);

我们为什么要在中间放置getContentPane()?我尝试只使用frame.add(panel);也可以正常工作。

添加2:

我想添加一些代码,以更清楚地说明我的意思。在这个例子中,我只使用了JPane:

import java.awt.*;
import javax.swing.*;
public class HelloWorldSwing {
    public static void main(String[] args) {
        JFrame frame = new JFrame("HelloWorldSwing");
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());        
        panel.add(new JButton("W"), BorderLayout.NORTH);
        panel.add(new JButton("E"), BorderLayout.SOUTH);
        frame.add(panel);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }
}

在这个例子中,我仅使用内容窗格(Content Pane):

import java.awt.*;
import javax.swing.*;
public class HelloWorldSwing {
    public static void main(String[] args) {
    JFrame frame = new JFrame("HelloWorldSwing");
    Container pane = frame.getContentPane();
    pane.setLayout(new BorderLayout()); 
    pane.add(new JButton("W"), BorderLayout.NORTH);
    pane.add(new JButton("E"), BorderLayout.SOUTH);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.pack();
    frame.setVisible(true);
    }
}

两种方法都可以正常工作! 我只想知道在这两种方法中,哪一种更好(更安全)。

6个回答

37

JPanel 是一个 Container,它们不是两个竞争机制(只需查看 JPanel javadocs 顶部的类层次结构)。JFrame.getContentPane() 只返回一个 Container,用于放置要在 JFrame 中显示的 Components。在内部,默认情况下使用的是一个 JPanel(你可以通过调用 setContentPane() 更改这一点。)至于为什么返回 Container 而不是 JPanel——这是因为你应该针对接口编程而不是实现——在这个级别上,你只需要关心能否将 Components 添加到某些东西中即可,即使 Container 是一个类而不是一个接口——它提供了所需的接口。

至于为什么 JFrame.add()JFrame.getContentPane().add() 都执行相同操作——JFrame.add() 被覆盖以调用 JFrame.getContentPane().add()。这并不总是这样——在 JDK 1.5 之前,你必须明确指定 JFrame.getContentPane().add(),如果调用 JFrame.add(),则会抛出 RuntimeException,但由于很多投诉,JDK 1.5 中对此进行了更改,使其达到预期效果。


@Nate,你说“Internally, it's using a JPanel”,是指Container正在使用JPanel吗?这是什么意思?我认为JPanel是Container的子类。而“setContentPane”可以改变什么呢?好的,你说Container是一个类(我一直认为Container是一个类)。但是JPane也是一个类。为什么我们不能只使用JPane或只使用Container?我能够通过仅使用JPane来完成相同的事情。我也能够仅使用Containers来完成相同的事情。 - Roman
JFrame 内部使用 JPanel 对象来支持 getContentPane() 方法。这就是为什么 Zachary 的答案有效的原因。实际的 JPanel 引用被嵌入在 JFrame.rootPane 字段中。你可以在 JFrame 上调用 setContentPane() - 例如,你可以将第一个示例中的第10行从 frame.add(panel) 更改为 frame.setContentPane(panel)Container 是一个类。JPanel 是一个类(它是 Container 的子类)。你混淆了引用类型和引用所指向的对象类型。想一想 Container container = new JPanel() - Nate
@Nate的源代码(http://pragmaticjava.blogspot.com.ar/2008/08/program-to-interface-not-implementation.html)已被删除。 - Lucio
@Lucio 谢谢!我已经将链接更改为关于同样问题的 StackOverflow 上的一个问题。 - Nate

3

这是一个好问题。我发现理解 "Swing提供了三个通常有用的顶层容器类:JFrameJDialogJApplet。... 作为一种便利,add方法及其变体、remove和setLayout已经被重写,必要时转发到contentPane。" ——使用顶层容器


1

我认为原因是因为Swing是基于AWT构建的,而Container是顶级AWT对象。虽然这不是最好的设计选择,因为通常不希望混合使用AWT(重量级)对象和Swing(轻量级)。

我认为处理它的最佳方法是始终将contentPane强制转换为JPanel。

JPanel contentPanel = (JPanel)aFrame.getContentPane();

这不是混合使用AWT和Swing组件的问题...请看我的回答。 - Nate

1

在最新版本的API文档中API doc已经明确写明,JFrame.add()(现在)已经足够。

你可以与以前的Java版本这里进行比较。


1

这个问题的历史和机制也在this leepoint article中详细讨论。特别注意:

getContentPane()返回一个Container对象。这实际上不是一个普通的Container对象,而是一个JPanel!这是由于层次结构而产生的Container。因此,如果我们获取预定义的内容面板,结果它实际上是一个JPanel,但我们真的不能利用JComponent添加的功能。

以及

他们在JFrame中定义了add()方法,只需调用相应的内容窗格add()方法即可。现在添加这个功能似乎有些奇怪,特别是因为许多布局使用多个嵌套面板,所以您仍然必须熟悉直接添加到JPanel。并且,并非所有想要通过调用JFrame来完成的内容窗格操作都可以完成。


1
有趣的是:jframe.setBackground(color) 对我不起作用,但是jframe.getContentPane().setBackground(color) 可以。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接