Java 中文官方教程 2022 版(十八)

原文:docs.oracle.com/javase/tutorial/reallybigindex.html

如何使用旋转器

原文:docs.oracle.com/javase/tutorial/uiswing/components/spinner.html

旋转器类似于组合框和列表,允许用户从一系列值中选择。与可编辑的组合框类似,旋转器允许用户输入值。与组合框不同,旋转器没有可以覆盖其他组件的下拉列表。因为旋转器不显示可能值 - 只有当前值可见 - 当可能值集合非常大时,通常使用旋转器代替组合框或列表。但是,只有在可能值及其顺序明显时才应使用旋转器。

旋转器是一个复合组件,包含三个子组件:两个小按钮和一个编辑器。编辑器可以是任何JComponent,但默认情况下实现为包含格式化文本字段的面板。旋转器的可能值和当前值由其模型管理。

这是一个名为SpinnerDemo的应用程序的图片,其中有三个用于指定日期的旋转器:

SpinnerDemo 显示了 3 种旋转器

主类的代码可以在SpinnerDemo.java中找到。月份旋转器显示用户所在地区的第一个月的名称。此旋转器的可能值是使用字符串数组指定的。年份旋转器显示整数范围内的一个值,初始化为当前年份。另一个日期旋转器显示Date对象范围内的一个值(最初是当前日期),以自定义格式显示仅月份和年份。


试试这个:

  1. 单击“启动”按钮以使用Java™ Web Start运行 SpinnerDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。启动 SpinnerDemo 应用程序

  2. 使用月份旋转器时,使用箭头按钮或键向前和向后循环浏览可能值。

    请注意,最小值是一年中的第一个月(例如,一月),最大值是最后一个月(例如,十二月)。确切的值取决于您所在地区。还要注意,这些值不会循环 - 您不能使用上箭头按钮或键直接从十二月跳到一月 - 因为标准旋转器模型不支持循环。

  3. 输入您所在地区的有效月份名称,例如,七月。

    请注意,旋转器会自动完成月份名称。

  4. 接下来是年份旋转器,尝试输入一个 100 年前的年份 — 例如,1800 — 然后单击另一个旋转器或按 Tab 键移出旋转器的焦点。

    因为此程序将旋转器的模型限制在当前年份的 100 年内的数字范围内,所以 1800 是无效的。当焦点移出旋转器时,显示的文本会更改回上一个有效值。

  5. 移动到另一个日期旋转器,使用箭头按钮或键来更改日期。

    请注意,默认情况下日期的第一部分 — 在本例中是月份数字 — 会更改。您可以通过单击鼠标或使用箭头键移动到日期的另一部分来更改日期的哪一部分更改。


要创建一个旋转器,首先创建其模型,然后将模型传递给 JSpinner 构造函数。例如:

String[] monthStrings = getMonthStrings(); //get month names
SpinnerListModel monthModel = new SpinnerListModel(monthStrings);
JSpinner spinner = new JSpinner(monthModel);

本节的其余部分涵盖以下主题:

  • 使用标准旋转器模型和编辑器

  • 指定旋转器格式

  • 创建自定义旋转器模型和编辑器

  • 检测旋转器值的更改

  • 旋转器 API

  • 使用旋转器的示例

使用标准旋转器模型和编辑器

Swing API 提供了三种旋转器模型:

SpinnerListModel

SpinnerListModel 是一个模型,其值由对象数组或 List 对象定义。SpinnerDemo 示例中的月份旋转器使用了这个模型,初始化为从 java.text.DateFormatSymbols 类的 getMonths 方法返回的值派生的数组。有关详细信息,请参见 SpinnerDemo.java

SpinnerNumberModel

SpinnerNumberModel 支持可以表示为 double 对象、int 对象或 Number 对象的数字序列。您可以指定允许的最小和最大值,以及步长 — 每次增加或减少的量。年份旋转器使用了这个模型,使用以下代码创建:

SpinnerModel model =
        new SpinnerNumberModel(currentYear, //initial value
                               currentYear - 100, //min
                               currentYear + 100, //max
                               1);                //step

SpinnerDateModel

SpinnerDateModel 支持 Date 对象的序列。您可以指定最小和最大日期,以及要增加或减少的字段(例如 Calendar.YEAR)。但请注意,某些类型的外观和感觉会忽略指定的字段,而是更改所选的字段。另一个日期旋转器使用了这个模型,使用以下代码创建:

Date initDate = calendar.getTime();
calendar.add(Calendar.YEAR, -100);
Date earliestDate = calendar.getTime();
calendar.add(Calendar.YEAR, 200);
Date latestDate = calendar.getTime();
model = new SpinnerDateModel(initDate,
                             earliestDate,
                             latestDate,
                             Calendar.YEAR);

当您设置微调器模型时,微调器的编辑器会自动设置。Swing API 为上述三个模型类中的每一个提供了相应的编辑器类。这些类 — JSpinner.ListEditorJSpinner.NumberEditorJSpinner.DateEditor — 都是 JSpinner.DefaultEditor 类的子类,具有可编辑的格式化文本字段。如果使用没有与之关联的编辑器的模型,则编辑器默认为具有不可编辑格式化文本字段的JSpinner.DefaultEditor实例。

指定微调器格式

要更改标准微调器编辑器中使用的格式,您可以自行创建并设置编辑器。

JSpinner.NumberEditorJSpinner.DateEditor 类具有构造函数,允许您创建以特定方式格式化其数据的编辑器。例如,以下代码设置了另一个日期微调器,以便不使用默认的长日期格式(包括时间),而是以紧凑的方式显示月份和年份。

spinner.setEditor(new JSpinner.DateEditor(spinner, "MM/yyyy"));


注意:

您可以通过运行ComboBoxDemo2示例来尝试日期格式。单击“启动”按钮以使用Java™ Web Start运行 ComboBoxDemo2(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。

启动 ComboBoxDemo2 应用程序

有关格式字符串的信息,请参阅国际化教程中的格式化部分。


在使用默认编辑器时更改格式,您可以获取编辑器的格式化文本字段并调用其方法。您可以使用JSpinner.DefaultEditor类中定义的getTextField方法调用这些方法。请注意,Swing 提供的编辑器不是格式化文本字段。相反,它们是包含格式化文本字段的JPanel实例。以下是获取并调用编辑器格式化文本字段方法的示例:

//Tweak the spinner's formatted text field.
ftf = getTextField(spinner);
if (ftf != null ) {
    ftf.setColumns(8); //specify more width than we need
    ftf.setHorizontalAlignment(JTextField.RIGHT);
}
...

public JFormattedTextField getTextField(JSpinner spinner) {
    JComponent editor = spinner.getEditor();
    if (editor instanceof JSpinner.DefaultEditor) {
        return ((JSpinner.DefaultEditor)editor).getTextField();
    } else {
        System.err.println("Unexpected editor type: "
                           + spinner.getEditor().getClass()
                           + " isn't a descendant of DefaultEditor");
        return null;
    }
}

创建自定义微调器模型和编辑器

如果现有的微调器模型或编辑器不符合您的需求,您可以创建自己的。

创建自定义微调器模型的最简单方法是创建现有AbstractSpinnerModel子类的子类,该子类已经实现了大部分您需要的内容。另一种方法是通过扩展AbstractSpinnerModel类来实现自己的类,该类实现了所有微调器模型所需的事件通知。

下面的SpinnerListModel子类实现了一个循环遍历对象数组的微调器模型。它还允许您指定第二个微调器模型,每当循环重新开始时将更新该模型。例如,如果对象数组是一个月份列表,则链接的模型可以用于显示年份的微调器。当月份从十二月翻转到一月时,年份会增加。类似地,当月份从一月翻回到十二月时,年份会减少。

public class CyclingSpinnerListModel extends SpinnerListModel {
    Object firstValue, lastValue;
    SpinnerModel linkedModel = null;

    public CyclingSpinnerListModel(Object[] values) {
        super(values);
        firstValue = values[0];
        lastValue = values[values.length - 1];
    }

    public void setLinkedModel(SpinnerModel linkedModel) {
        this.linkedModel = linkedModel;
    }

    public Object getNextValue() {
        Object value = super.getNextValue();
        if (value == null) {
            value = firstValue;
            if (linkedModel != null) {
                linkedModel.setValue(linkedModel.getNextValue());
            }
        }
        return value;
    }

    public Object getPreviousValue() {
        Object value = super.getPreviousValue();
        if (value == null) {
            value = lastValue;
            if (linkedModel != null) {
                linkedModel.setValue(linkedModel.getPreviousValue());
            }
        }
        return value;
    }
}

CyclingSpinnerListModel模型用于SpinnerDemo2示例中的月份微调器,该示例几乎与SpinnerDemo相同。单击启动按钮以使用Java™ Web Start运行 SpinnerDemo2(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。

启动 SpinnerDemo2 应用程序

正如我们之前提到的,如果您实现的微调器模型不是从SpinnerListModelSpinnerNumberModelSpinnerDateModel继承的,那么微调器的默认编辑器是JSpinner.DefaultEditor的不可编辑实例。正如您已经看到的,您可以在设置微调器的模型属性后,通过调用微调器的setEditor方法来设置微调器的编辑器。使用setEditor的替代方法是创建JSpinner类的子类,并覆盖其createEditor方法,以便在微调器模型是某种类型时返回特定类型的编辑器。

理论上,您可以使用任何JComponent实例作为编辑器。可能的选择包括使用标准组件的子类,如JLabel,或者您从头开始实现的组件,或者JSpinner.DefaultEditor的子类。唯一的要求是编辑器必须更新以反映微调器值的更改,并且必须具有合理的首选大小。编辑器通常还应将其工具提示文本设置为微调器指定的工具提示文本。下一节提供了实现编辑器的示例。

检测微调器值更改

您可以通过在微调器或其模型上注册更改侦听器来检测微调器的值已更改。以下是实现此类更改侦听器的示例。此示例来自SpinnerDemo3,它基于SpinnerDemo,并使用更改侦听器将某些文本的颜色更改为与另一个日期微调器的值匹配。单击“启动”按钮以使用Java™ Web Start运行 SpinnerDemo3(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。

启动 SpinnerDemo3 应用程序

public class SpinnerDemo3 extends JPanel
                          implements ChangeListener {
    protected Calendar calendar;
    protected JSpinner dateSpinner;
    ...
    public SpinnerDemo3() {
        ...
        SpinnerDateModel dateModel = ...;
        ...
        setSeasonalColor(dateModel.getDate()); //initialize color

        //Listen for changes on the date spinner.
        dateSpinner.addChangeListener(this);
        ...
    }

    public void stateChanged(ChangeEvent e) {
        SpinnerModel dateModel = dateSpinner.getModel();
        if (dateModel instanceof SpinnerDateModel) {
            setSeasonalColor(((SpinnerDateModel)dateModel).getDate());
        }
    }

    protected void setSeasonalColor(Date date) {
        calendar.setTime(date);
        int month = calendar.get(Calendar.MONTH);
        JFormattedTextField ftf = getTextField(dateSpinner);
        if (ftf == null) return;

        //Set the color to match northern hemisphere seasonal conventions.
        switch (month) {
            case 2:  //March
            case 3:  //April
            case 4:  //May
                     ftf.setForeground(SPRING_COLOR);
                     break;
            ...
            default: //December, January, February
                     ftf.setForeground(WINTER_COLOR);
        }
    }
    ...
}

以下示例实现了一个具有更改侦听器的编辑器,以便它可以反映微调器的当前值。此特定编辑器显示了从白色到黑色的任何位置的灰色实色。单击“启动”按钮以使用Java™ Web Start运行 SpinnerDemo4(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。

启动 SpinnerDemo4 应用程序

*...//Where the components are created:*
JSpinner spinner = new JSpinner(new GrayModel(170));
spinner.setEditor(new GrayEditor(spinner));

class GrayModel extends SpinnerNumberModel {
    ...
}

class GrayEditor extends JLabel
                 implements ChangeListener {
    public GrayEditor(JSpinner spinner) {
        setOpaque(true);
        ...
        //Get info from the model.
        GrayModel myModel = (GrayModel)(spinner.getModel());
        setBackground(myModel.getColor());
        spinner.addChangeListener(this);
        ...
        updateToolTipText(spinner);
    }

    protected void updateToolTipText(JSpinner spinner) {
        String toolTipText = spinner.getToolTipText();
        if (toolTipText != null) {
            //JSpinner has tool tip text.  Use it.
            if (!toolTipText.equals(getToolTipText())) {
                setToolTipText(toolTipText);
            }
        } else {
            //Define our own tool tip text.
            GrayModel myModel = (GrayModel)(spinner.getModel());
            int rgb = myModel.getIntValue();
            setToolTipText("(" + rgb + "," + rgb + "," + rgb + ")");
        }
    }

    public void stateChanged(ChangeEvent e) {
            JSpinner mySpinner = (JSpinner)(e.getSource());
            GrayModel myModel = (GrayModel)(mySpinner.getModel());
            setBackground(myModel.getColor());
            updateToolTipText(mySpinner);
    }
}

微调器 API

以下表格列出了一些常用的用于使用微调器的 API。如果您需要直接处理编辑器的格式化文本字段,还应查看格式化文本字段 API。您可能使用的其他方法列在 JComponent 类的 API 表中。

  • 与微调器相关的类

  • JSpinner 构造函数和方法

  • 有用的编辑器构造函数和方法

  • SpinnerListModel 方法

  • SpinnerDateModel 方法

  • SpinnerNumberModel 方法

与微调器相关的类

类或接口 目的
JSpinner 允许用户从有序序列中选择数字或对象值的单行输入字段。
SpinnerModel 所有微调器模型实现的接口。
AbstractSpinnerModel 微调器模型实现的通常超类。
SpinnerListModel 其值由数组或List定义的AbstractSpinnerModel的子类。
SpinnerDateModel 支持Date实例序列的AbstractSpinnerModel的子类。
SpinnerNumberModel 支持数字序列的AbstractSpinnerModel的子类。
JSpinner.DefaultEditor 实现一个显示微调器值的不可编辑组件。该类的子类通常更专业化(可编辑)。
JSpinner.ListEditor JSpinner.DefaultEditor的子类,其值由数组或List定义。
JSpinner.DateEditor 支持Date实例序列的JSpinner.DefaultEditor的子类。
JSpinner.NumberEditor 支持数字序列的JSpinner.DefaultEditor的子类。

有用的 JSpinner 构造函数和方法

构造函数或方法 目的
JSpinner() JSpinner(SpinnerModel) 创建一个新的JSpinner。无参数构造函数创建一个带有整数SpinnerNumberModel的微调器,初始值为 0,没有最小或最大限制。第二个构造函数上的可选参数允许您指定自己的SpinnerModel
void setValue(java.lang.Object) Object getValue() 设置或获取当前显示的序列元素。
Object getNextValue() Object getPreviousValue() 获取getValue方法返回的对象之前或之后的序列中的对象。
SpinnerModel getModel() void setModel(SpinnerModel) 获取或设置微调器的模型。
JComponent getEditor() void setEditor(JComponent) 获取或设置微调器的编辑器,通常是JSpinner.DefaultEditor类型的对象。
protected JComponent createEditor(SpinnerModel) JSpinner构造函数调用此方法来创建微调器的编辑器。重写此方法以将编辑器与特定类型的模型关联起来。

有用的编辑器构造函数和方法

构造函数或方法 目的
JSpinner.NumberEditor(JSpinner, String) 创建一个JSpinner.NumberEditor实例,显示并允许编辑指定微调器的数字值。字符串参数指定用于显示数字的格式。有关十进制格式字符串的信息,请参阅DecimalFormat的 API 文档。
JSpinner.DateEditor(JSpinner, String) 创建一个JSpinner.DateEditor实例,显示并允许编辑指定微调器的Date值。字符串参数指定用于显示日期的格式。有关日期格式字符串的信息,请参阅SimpleDateFormat的 API 文档。
JFormattedTextField getTextField() (在JSpinner.DefaultEditor中定义) 获取提供此编辑器主要 GUI 的格式化文本字段。

SpinnerListModel 方法

方法 目的
void setList(List) List getList() 设置或获取定义此模型序列的List

SpinnerDateModel 方法

方法 目的

| void setValue(Object) Date getDate()

Object getValue() | 设置或获取此序列的当前Date。 |

void setStart(Comparable) Comparable getStart() 设置或获取此序列中的第一个Date。使用null指定微调器没有下限。
void setEnd(Comparable) Comparable getEnd() 设置或获取此序列中的最后一个Date。使用null指定微调器没有上限。
void setCalendarField(int) int getCalendarField() 设置或获取getNextValuegetPreviousValue方法使用的日期值增量的大小。当用户明确增加或减少值时,不使用此属性;相反,格式化文本字段的选定部分会增加或减少。指定的参数必须是Calendar中定义的以下常量之一:ERAYEARMONTHWEEK_OF_YEARWEEK_OF_MONTHDAY_OF_MONTHDAY_OF_YEARDAY_OF_WEEKDAY_OF_WEEK_IN_MONTHAM_PMHOUR_OF_DAYMINUTESECONDMILLISECOND

微调器数字模型方法

方法 目的
void setValue(Object) Number getNumber() 设置或获取此序列的当前值。
void setMaximum(Comparable) Comparable getMaximum() 设置或获取此序列中数字的上限。如果最大值为null,则没有上限。
void setMinimum(Comparable) Comparable getMinimum() 设置或获取此序列中数字的下限。如果最小值为null,则没有下限。
void setStepSize(Number) Number getStepSize() 设置或获取getNextValuegetPreviousValue方法使用的增量。

使用微调器的示例

这个表格列出了使用 Spinner 的示例,并指向这些示例的描述位置。

示例 描述位置 备注
SpinnerDemo 这一部分 使用了所有三种标准的 Spinner 模型类。 包含使用自定义 Spinner 模型的代码,但默认情况下该代码被关闭。
SpinnerDemo2 这一部分 一个SpinnerDemo的子类,使用自定义的 Spinner 模型来显示月份的 Spinner。
SpinnerDemo3 这一部分 基于 SpinnerDemo,该应用程序展示了如何监听 Spinner 值的变化。
SpinnerDemo4 这一部分 实现了一个自定义模型和一个自定义编辑器,用于显示灰度的 Spinner。

如何使用分割窗格

原文:docs.oracle.com/javase/tutorial/uiswing/components/splitpane.html

一个JSplitPane显示两个组件,可以并排显示或者上下显示。通过拖动出现在组件之间的分隔符,用户可以指定分割窗格总面积中每个组件所占的比例。通过将分割窗格放入分割窗格中,可以在三个或更多组件之间划分屏幕空间,如 Nesting Split Panes 中所述。

与直接将感兴趣的组件添加到分割窗格不同,通常将每个组件放入滚动窗格中。然后将滚动窗格放入分割窗格中。这样用户可以查看感兴趣组件的任何部分,而无需组件占用大量屏幕空间或适应在不同屏幕空间中显示自身。

这是一个使用分割窗格并将列表和图像并排显示的应用程序的图片:

SplitPaneDemo 的快照


试试这个:

  1. 单击“启动”按钮以使用Java™ Web Start运行 SplitPaneDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。启动 SplitPaneDemo 示例

  2. 拖动分隔列表和图像的虚线,使其向左或向右移动。尝试将分隔线拖动到窗口边缘。

  3. 单击分隔符上的小箭头可隐藏/展开左侧或右侧组件。


下面是从SplitPaneDemo中创建和设置分割窗格的代码。

//Create a split pane with the two scroll panes in it.
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                           listScrollPane, pictureScrollPane);
splitPane.setOneTouchExpandable(true);
splitPane.setDividerLocation(150);

//Provide minimum sizes for the two components in the split pane
Dimension minimumSize = new Dimension(100, 50);
listScrollPane.setMinimumSize(minimumSize);
pictureScrollPane.setMinimumSize(minimumSize);

此示例使用的构造函数接受三个参数。第一个参数表示分割方向。其他参数是要放入分割窗格的两个组件。有关动态设置组件的JSplitPane方法,请参考设置分割窗格中的组件。

此示例中的分割窗格水平分割 — 两个组件并排显示 — 如构造函数中的JSplitPane.HORIZONTAL_SPLIT参数所指定。分割窗格提供另一个选项,使用JSplitPane.VERTICAL_SPLIT指定,将一个组件放在另一个组件上方。您可以在创建分割窗格后使用setOrientation方法更改分割方向。

在示例的分割窗格中,分隔条顶部会出现两个小箭头。这些箭头允许用户通过单击折叠(然后展开)任一组件。当前的外观和感觉决定了这些控件是否默认显示。在 Java 外观和感觉中,默认情况下是关闭的。(请注意,并非所有外观和感觉都支持此功能。)示例使用setOneTouchExpandable方法将它们打开。

分割窗格的分隔条范围部分取决于分割窗格内组件的最小尺寸。有关详细信息,请参阅设置分隔条的位置和限制其范围。

本节的其余部分涵盖以下主题:

  • 在分割窗格中设置组件

  • 设置分隔条的位置和限制其范围

  • 嵌套分割窗格

  • 分割窗格 API

  • 使用分割窗格的示例

在分割窗格中设置组件

程序可以使用这四种方法动态设置分割窗格的两个组件:

  • setLeftComponent

  • setRightComponent

  • setTopComponent

  • setBottomComponent

您可以随时使用这些方法,而不管分割窗格当前的分割方向如何。调用setLeftComponentsetTopComponent是等效的,并根据分割窗格当前的分割方向在顶部或左侧位置设置指定的组件。类似地,调用setRightComponentsetBottomComponent是等效的。这些方法将替换该位置已有的任何组件为新组件。

与其他容器一样,JSplitPane支持add方法。分割窗格将添加的第一个组件放在左侧或顶部位置。使用add的危险在于您可能会无意中多次调用它,这种情况下,分割窗格的布局管理器将抛出一个看起来相当晦涩的异常。如果您正在使用add方法并且分割窗格已经填充,您首先需要使用remove删除现有组件。

如果您只在分割窗格中放置一个组件,则分隔条将被固定在分割窗格的右侧或底部,具体取决于其分割方向。

设置分隔条的位置和限制其范围

要使您的分割窗格正常工作,通常需要设置分割窗格中组件的最小尺寸,以及分割窗格或其包含组件的首选尺寸。选择应设置哪些尺寸是一门需要理解分割窗格首选尺寸和分隔条位置如何确定的艺术。在深入讨论之前,让我们再次看看 SplitPaneDemo。或者,如果您赶时间,可以直接跳转到规则列表。


试试这个:

  1. 点击“启动”按钮以使用Java™ Web Start运行 SplitPaneDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。启动 SplitPaneDemo 示例

    因为演示框架的大小是使用pack方法设置的,分隔窗格处于其首选大小,SplitPaneDemo 恰好明确设置了。分隔符会自动放置,使左侧组件处于其首选宽度,所有剩余空间都分配给右侧组件。

  2. 使窗口变宽。

    分隔符保持在原位,额外的空间分配给右侧组件。

  3. 使窗口比首次出现时明显变窄 — 可能是左侧组件宽度的两倍。

    再次,左侧组件的大小和分隔符位置保持不变。只有右侧组件的大小发生变化。

  4. 将窗口尽可能变窄。

    假设窗口使用了 Java 外观和感觉提供的装饰,您无法将窗口调整为小于分隔窗格的最小大小,该大小由分隔窗格包含的组件的最小大小确定。SplitPaneDemo 明确设置了这些包含组件的最小大小。

  5. 使窗口变宽,然后将分隔符拖动到最右侧。

    分隔符只能移动到右侧组件的最小大小允许的位置。如果将分隔符向左拖动,您会发现它也会尊重左侧组件的最小大小。


现在您已经看到了分隔窗格的默认行为,我们可以告诉您幕后发生的事情以及您如何影响它。在本讨论中,当我们提到组件的首选或最小大小时,通常指的是如果分隔窗格是水平的,则组件的首选或最小宽度,如果分隔窗格是垂直的,则指的是其首选或最小高度。

默认情况下,分隔窗格的首选大小和分隔符位置会初始化,以使分隔窗格中的两个组件处于其首选大小。如果分隔窗格未以首选大小显示,并且程序未明确设置分隔符位置,则分隔符的初始位置(以及两个组件的大小)取决于一个称为调整权重的分隔窗格属性。如果分隔窗格最初以其首选大小或更大显示,则包含的组件在调整调整权重之前以其首选大小开始。如果分隔窗格最初太小而无法同时显示两个组件的首选大小,则它们将以其最小大小开始,然后再调整调整权重。

分割窗格的调整权重的值介于 0.0 和 1.0 之间,并确定在设置分割窗格的大小时两个包含的组件之间如何分配空间 — 无论是通过编程方式还是用户调整分割窗格的大小(例如扩大其包含窗口)。分割窗格的调整权重默认为 0.0,表示左侧或顶部组件的大小固定,右侧或底部组件调整其大小以适应剩余空间。将调整权重设置为 0.5 会均匀地分配任何额外或缺失的空间给两个组件。将调整权重设置为 1.0 会使右侧或底部组件的大小保持不变。然而,当用户拖动分隔线时,调整权重没有影响。

用户可以拖动分隔线到任何位置,只要没有一个包含的组件的大小低于其最小大小。如果分隔线有一键按钮,用户可以使用它们使分隔线完全移动到一侧或另一侧 — 无论组件的最小大小是多少。

现在您已经了解了影响分割窗格大小和分隔线位置的因素,以下是一些使它们良好运作的规则:

  • 要确保在分割窗格处于其首选大小时可以拖动分隔线,请确保一个或两个包含的组件的最小大小小于包含组件的首选大小。您可以通过在组件上调用setMinimumSize或重写其getMinimumSize方法来设置组件的最小大小。例如,如果您希望用户能够将分隔线拖动到两侧:

    Dimension minimumSize = new Dimension(0, 0);
    leftComponent.setMinimumSize(minimumSize);
    rightComponent.setMinimumSize(minimumSize);
    
    
  • 要确保两个包含的组件都显示出来,请确保分割窗格初始时处于或高于其首选大小,或者包含的组件的最小大小大于零。

    如果分割窗格被赋予其首选大小,这通常会发生,这取决于包含分割窗格的布局管理器。另一个选项是在分割窗格上明确设置一个大于包含组件大小的首选大小。

  • 如果您希望底部或右侧组件保持相同大小,并且在分割窗格变大时顶部或左侧组件是灵活的,请将调整权重设置为 1.0。您可以通过调用setResizeWeight来实现这一点:

    splitPane.setResizeWeight(1.0);
    
    
  • 如果您希望分割窗格的两半共享分割窗格的额外空间或减少的空间,请将调整权重设置为 0.5:

    splitPane.setResizeWeight(0.5);
    
    
  • 确保由分割窗格包含的每个组件都有一个合理的首选大小。如果组件是使用布局管理器的面板,通常可以直接使用其返回的值。如果组件是滚动窗格,则有几种选择。您可以在滚动窗格上调用setPreferredSize方法,在滚动窗格中的组件上调用适当的方法(例如JListJTreesetVisibleRowCount方法)。

  • 确保分割窗格包含的每个组件在不同大小的空间中都能合理显示自己。例如,包含多个组件的面板应使用布局管理器以合理的方式使用额外空间。

  • 如果您想将包含的组件的大小设置为其首选大小以外的其他值,请使用setDividerLocation方法。例如,要使左侧组件宽度为 150 像素:

    splitPane.setDividerLocation(150 + splitPane.getInsets().left);
    
    

    尽管分割窗格尽力遵守初始分隔线位置(在本例中为 150),但一旦用户拖动分隔线,可能无法再将其拖动到程序指定的大小。

    要使右侧组件宽度为 150 像素:

    splitPane.setDividerLocation(splitPane.getSize().width
                                 - splitPane.getInsets().right
                                 - splitPane.getDividerSize()
                                 - 150);
    
    

    如果分割窗格已经可见,您可以将分隔线位置设置为分割窗格的百分比。例如,使 25%的空间分配给左侧/顶部:

    splitPane.setDividerLocation(0.25);
    
    

    请注意,这是根据当前大小实现的,因此只有在分割窗格可见时才真正有用。

  • 要将分割窗格布局设置为刚刚出现的状态,可能会重新定位分隔线,请在分割窗格上调用resetToPreferredSizes()


    注意: 仅更改包含的组件的首选大小 — 即使之后调用revalidate — 不足以导致分割窗格重新布局。您还必须调用resetToPreferredSizes


以下快照显示了一个名为 SplitPaneDividerDemo 的示例,演示了分割窗格组件大小和分隔线位置。

SplitPaneDividerDemo 的快照

与 SplitPaneDemo 一样,SplitPaneDividerDemo 具有带有一键按钮的水平分割窗格。SplitPaneDividerDemo 具有以下附加功能:

  • 分割窗格的调整权重被明确设置为(0.5)。

  • 分割窗格显示为其默认首选大小。

  • 窗口底部的“重置”按钮调用分割窗格上的resetToPreferredSizes

  • 分割窗格中的组件是名为SizeDisplayer的自定义JComponent子类的实例。SizeDisplayer显示可选文本在淡化的背景图像上(也是可选的)。更重要的是,它有显示其首选大小和最小大小的矩形。

  • SplitPaneDividerDemo 设置其SizeDisplayer具有相等的首选大小(由于它们显示的图像大小相等),但不等的最小大小。


试试这个:

  1. 单击“启动”按钮以使用Java™ Web Start运行 SplitPaneDividerDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。启动 SplitPaneDividerDemo 示例

    因为演示的窗口大小是使用pack方法设置的,分割窗格处于其首选大小,默认情况下刚好足够让SizeDisplayer以其首选大小显示。每个SizeDisplayer的首选大小由一个红色矩形表示。分隔线会自动放置,以使两个组件的宽度达到其首选宽度。

  2. 使窗口变宽。

    因为分割窗格的调整权重为 0.5,额外空间均匀分配给左右组件。分隔线相应移动。

  3. 使窗口尽可能窄。

    假设窗口使用 Java 外观和感觉提供的装饰,它不会让您将窗口调整为小于分割窗格的最小尺寸,该最小尺寸由其包含的SizeDisplayer的最小尺寸确定。每个SizeDisplayer的最小尺寸由一个明亮蓝色矩形表示。

  4. 使窗口稍微变宽,然后将分隔线拖动到最右侧。

    分隔线只能移动到右侧组件的最小尺寸允许的位置。

  5. 确保分割窗格小于其首选大小后,单击重置按钮。

    请注意,尽管应用程序启动时它们的大小相等,但两个SizeDisplayer显示的大小不同。原因是虽然它们的首选大小相等,但它们的最小大小不同。因为分割窗格无法以其首选大小或更大的尺寸显示它们,所以使用它们的最小大小进行布局。剩余空间平均分配给组件,因为分割窗格的调整权重为 0.5。

  6. 扩大分割窗格,使其足够大,以便两个SizeDisplayer以其首选大小显示,然后单击重置按钮。

    分隔线再次放置在中间,以使两个组件大小相同。


这是为 SplitPaneDividerDemo 创建 GUI 的代码:

public class SplitPaneDividerDemo extends JPanel ... {

    private JSplitPane splitPane;

    public SplitPaneDividerDemo() {
        super(new BorderLayout());

        Font font = new Font("Serif", Font.ITALIC, 24);

        ImageIcon icon = createImageIcon("images/Cat.gif");
        SizeDisplayer sd1 = new SizeDisplayer("left", icon);
        sd1.setMinimumSize(new Dimension(30,30));
        sd1.setFont(font);

        icon = createImageIcon("images/Dog.gif");
        SizeDisplayer sd2 = new SizeDisplayer("right", icon);
        sd2.setMinimumSize(new Dimension(60,60));
        sd2.setFont(font);

        splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                                   sd1, sd2);
        splitPane.setResizeWeight(0.5);
        splitPane.setOneTouchExpandable(true);
        splitPane.setContinuousLayout(true);

        add(splitPane, BorderLayout.CENTER);
        add(createControlPanel(), BorderLayout.PAGE_END);
    }
    ...
}

代码相当容易理解,除了对setContinuousLayout的调用可能有点难以理解。将continuousLayout属性设置为 true 会使分割窗格的内容在用户移动分隔线时连续绘制。默认情况下不启用连续布局,因为它可能会对性能产生负面影响。然而,在这个演示中使用它是有意义的,因为使分割窗格的组件尽可能保持最新可以提高用户体验。

嵌套分割窗格

这是一个通过在一个分割窗格内嵌套另一个分割窗格来实现三向分割的程序的图片:

SplitPaneDemo2 的快照

如果顶部部分的分割窗格看起来很熟悉,那是因为程序将SplitPaneDemo创建的分割窗格放在第二个分割窗格中。第二个分割窗格中的另一个组件是一个简单的JLabel。这并不是嵌套分割窗格的最实用用法,但可以传达出要点。

启动 SplitPaneDemo2 示例

这是代码中的有趣部分,你可以在SplitPaneDemo2.java中找到:

//Create an instance of SplitPaneDemo
SplitPaneDemo splitPaneDemo = new SplitPaneDemo();
JSplitPane top = splitPaneDemo.getSplitPane();

...
//Create a regular old label
label = new JLabel("Click on an image name in the list.",
                   JLabel.CENTER);

//Create a split pane and put "top" (a split pane)
//and JLabel instance in it.
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
                                      top, label);

请参考解决常见组件问题以获取有关修复嵌套分隔窗格时可能出现的边框问题的信息。

分隔窗格 API

以下表格列出了常用的JSplitPane构造函数和方法。您最有可能在JSplitPane对象上调用的其他方法是其超类提供的setPreferredSize之类的方法。请参阅 The JComponent API 以获取常用继承方法的表格。

使用列表的 API 可分为以下几类:

  • 设置分隔窗格

  • 管理分隔窗格的内容

  • 调整分隔条位置

设置分隔窗格

方法或构造函数 目的

| JSplitPane() JSplitPane(int)

JSplitPane(int, boolean)

JSplitPane(int, Component, Component)

JSplitPane(int, boolean, Component, Component) | 创建一个分隔窗格。当存在int参数时,指示分隔窗格的方向,可以是HORIZONTAL_SPLIT(默认)或VERTICAL_SPLIT。当存在boolean参数时,设置组件在用户拖动分隔窗格时是否持续重绘。如果未指定,此选项(称为连续布局)将被关闭。Component参数设置初始左侧和右侧,或顶部和底部组件。 |

void setOrientation(int) int getOrientation() 设置或获取分割窗格的方向。使用JSplitPane中定义的HORIZONTAL_SPLITVERTICAL_SPLIT。如果未指定,分割窗格将水平分割。
void setDividerSize(int) int getDividerSize() 设置或获取分隔条的大小(以像素为单位)。
void setContinuousLayout(boolean) boolean isContinuousLayout() 设置或获取分割窗格的组件是否在用户拖动分隔条时持续布局和绘制。默认情况下,连续布局是关闭的。
void setOneTouchExpandable(boolean) boolean isOneTouchExpandable() 设置或获取分割窗格是否显示一个控件在分隔条上以展开/折叠分隔条。默认取决于外观。在 Java 外观中,默认情况下是关闭的。

管理分割窗格的内容

方法 目的

| void setTopComponent(Component) void setBottomComponent(Component)

void setLeftComponent(Component)

void setRightComponent(Component)

Component getTopComponent()

Component getBottomComponent()

Component getLeftComponent()

Component getRightComponent() | 设置或获取指定的组件。每种方法都适用于分割窗格的方向。顶部和左侧是等效的,底部和右侧是等效的。 |

void remove(Component) void removeAll() 从分割窗格中移除指定的组件。
void add(Component) 将组件添加到分割窗格。您只能向分割窗格添加两个组件。添加的第一个组件是顶部/左侧组件。添加的第二个组件是底部/右侧组件。任何尝试添加更多组件都会导致异常。

分隔条定位

方法 目的

| void setDividerLocation(double) void setDividerLocation(int)

int getDividerLocation() | 设置或获取当前分隔条位置。在设置分隔条位置时,可以指定新位置为百分比(double)或像素位置(int)。 |

void resetToPreferredSizes() 移动分隔条,使两个组件都处于其首选大小。这是分割窗格在启动时如何划分自身的方式,除非另有规定。
void setLastDividerLocation(int) int getLastDividerLocation() 设置或获取分隔条的上一个位置。
int getMaximumDividerLocation() int getMinimumDividerLocation() 获取分隔条的最小和最大位置。这些位置是通过设置分割窗格的两个组件的最小大小来隐式设置的。
void setResizeWeight(float) float getResizeWeight() 设置或获取分割窗格的调整权重,一个介于 0.0(默认值)和 1.0 之间的值。请参见分隔条定位和限制其范围以了解和使用调整权重的解释和示例。

使用分割窗格的示例

本表显示了一些使用JSplitPane的示例以及这些示例的描述位置。

示例 描述位置 注释
SplitPaneDemo 这个页面和 如何使用列表 展示了一个水平分割的分割窗格。
SplitPaneDividerDemo 这个页面 演示了如何使用组件大小信息和调整权重来定位分隔条。
SplitPaneDemo2 这个页面 将一个分割窗格放在另一个分割窗格中,创建一个三向分割。
TreeDemo 如何使用树 使用了一个垂直分割的分割窗格来分隔一个树(在一个滚动窗格中)和一个编辑器窗格(在一个滚动窗格中)。不使用一键展开功能。
TextComponentDemo 文本组件特性 使用了一个垂直分割的分割窗格来分隔一个文本窗格和一个文本区域,两者都在滚动窗格中。
TextSamplerDemo 文本组件特性 使用了一个垂直分割和调整权重为 0.5 的分割窗格来分隔一个文本窗格和一个编辑器窗格,两者都在滚动窗格中。分割窗格位于一个具有相当复杂布局的容器的右半部分。使用诸如GridLayoutBorderLayout之类的布局管理器,以及分割窗格的调整权重,确保滚动窗格中的组件共享所有额外空间。
ListSelectionDemo 如何编写列表选择监听器 使用了一个垂直分割的分割窗格来分隔一个上部窗格,其中包含一个列表和一个表格(都在滚动窗格中),以及一个下部窗格,其中包含一个组合框和一个滚动窗格。下部窗格使用边界布局来保持组合框较小,而滚动窗格则贪婪地占用空间。

如何使用选项卡窗格

原文:docs.oracle.com/javase/tutorial/uiswing/components/tabbedpane.html

使用JTabbedPane类,您可以让多个组件,比如面板,共享相同的空间。用户通过选择对应所需组件的选项卡来选择要查看的组件。如果您想要类似的功能但不想使用选项卡界面,可以使用卡片布局而不是选项卡窗格。

创建选项卡窗格

要创建选项卡窗格,实例化JTabbedPane,创建您希望显示的组件,然后使用addTab方法将组件添加到选项卡窗格中。

以下图片介绍了一个名为TabbedPaneDemo的应用程序,其中有一个带有四个选项卡的选项卡窗格。

TabbedPaneDemo 的屏幕截图


试试这个:

  1. 单击“启动”按钮以使用Java™ Web Start运行 TabbedPaneDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。启动 TabbedPaneDemo 应用程序

  2. 将光标放在选项卡上。

    与标签相关联的工具提示显示出来。为了方便起见,您可以在将组件添加到选项卡窗格时指定工具提示文本。

  3. 通过单击选项卡来选择一个选项卡。

    选项卡窗格显示与选项卡对应的组件。

  4. 通过输入其助记符来选择一个选项卡。

    例如,在 Java 外观中,您可以通过键入 Alt-3 来选择标记为“Tab 3”的选项卡。

  5. 在可滚动选项卡之间导航。

    此示例提供可滚动的选项卡。通过移动对话框的左侧或右侧边界来调整对话框的大小,以便选项卡不适合对话框内。在选项卡旁边会出现滚动箭头。

    单击箭头以查看其中一个隐藏的选项卡。

    请注意,单击箭头只会显示隐藏的标签,而不会选择新的标签。


正如TabbedPaneDemo示例所示,选项卡可以具有工具提示和助记符,并且可以同时显示文本和图像。

标签放置

默认的标签放置位置设置为TOP位置,如上所示。您可以使用setTabPlacement方法将标签放置位置更改为LEFTRIGHTTOPBOTTOM

选项卡窗格的代码

来自TabbedPaneDemo.java的以下代码创建了前面示例中的选项卡窗格。请注意,不需要事件处理代码。JTabbedPane对象会为您处理鼠标和键盘事件。

JTabbedPane tabbedPane = new JTabbedPane();
ImageIcon icon = createImageIcon("images/middle.gif");

JComponent panel1 = makeTextPanel("Panel #1");
tabbedPane.addTab("Tab 1", icon, panel1,
                  "Does nothing");
tabbedPane.setMnemonicAt(0, KeyEvent.VK_1);

JComponent panel2 = makeTextPanel("Panel #2");
tabbedPane.addTab("Tab 2", icon, panel2,
                  "Does twice as much nothing");
tabbedPane.setMnemonicAt(1, KeyEvent.VK_2);

JComponent panel3 = makeTextPanel("Panel #3");
tabbedPane.addTab("Tab 3", icon, panel3,
                  "Still does nothing");
tabbedPane.setMnemonicAt(2, KeyEvent.VK_3);

JComponent panel4 = makeTextPanel(
        "Panel #4 (has a preferred size of 410 x 50).");
panel4.setPreferredSize(new Dimension(410, 50));
tabbedPane.addTab("Tab 4", icon, panel4,
                      "Does nothing at all");
tabbedPane.setMnemonicAt(3, KeyEvent.VK_4);

如前面的代码所示,addTab方法处理了在选项卡窗格中设置选项卡的大部分工作。addTab方法有几种形式,但它们都使用字符串标题和要由选项卡显示的组件。可选地,您可以指定图标和工具提示字符串。文本或图标(或两者)可以为 null。创建选项卡的另一种方法是使用insertTab方法,该方法允许您指定要添加的选项卡的索引。请注意,在此步骤中,addTab方法不允许指定索引。

切换到特定选项卡

有三种使用 GUI 切换到特定选项卡的方法。

  1. 使用鼠标。 要切换到特定选项卡,用户可以用鼠标点击它。

  2. 使用键盘箭头。JTabbedPane对象获得焦点时,可以使用键盘箭头从一个选项卡切换到另一个选项卡。

  3. 使用键盘助记键。 setMnemonicAt方法允许用户使用键盘切换到特定选项卡。例如,setMnemonicAt(3, KeyEvent.VK_4)将'4'设置为第四个选项卡的助记键(索引从 0 开始,因此第四个选项卡的索引为 3);按下 Alt-4 将显示第四个选项卡的组件。通常,助记键使用选项卡标题中的字符,然后自动加下划线。

要通过编程方式切换到特定选项卡,可以使用setSelectedIndexsetSelectedComponent方法。

选项卡的首选大小

在构建要添加到选项卡窗格的组件时,请记住,无论哪个选项卡的子组件可见,每个子组件都会获得相同的空间来显示自己。选项卡窗格的首选大小刚好足够显示其最高子组件的首选高度和最宽子组件的首选宽度。类似地,选项卡窗格的最小大小取决于所有子组件的最大最小宽度和高度。

TabbedPaneDemo示例中,第四个面板的首选宽度和高度大于其他面板的宽度和高度。因此,选项卡窗格的首选大小刚好足够显示第四个面板的首选大小。每个面板都获得完全相同的空间 - 假设选项卡窗格处于首选大小状态,宽度为 410 像素,高度为 50 像素。如果您不了解首选大小的使用方式,请参考布局管理工作原理。

带有自定义组件的选项卡

TabComponentsDemo示例介绍了一个选项卡窗格,其选项卡包含真实组件。使用自定义组件带来了新功能,如按钮、组合框、标签和其他组件到选项卡,并允许更复杂的用户交互。

这是一个带有选项卡关闭按钮的选项卡窗格。

TabComponentsDemo 的屏幕截图


试试这个:

  1. 单击“启动”按钮以使用Java™ Web Start运行 TabComponentsDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。启动 TabComponentsDemo 应用程序

  2. 将光标放在选项卡上。

  3. 通过单击选项卡来选择它(确保不要点击小十字)。

  4. 将光标放在带有小十字的小部件上。

    小十字变成品红色并被包裹在一个正方形中。与关闭按钮关联的工具提示会出现。

    用鼠标左键单击十字关闭选项卡。

  5. 通过选择“重置 JTabbedPane”项目来恢复已删除的选项卡。

  6. 请注意,带有自定义组件的选项卡显示在原始选项卡窗格选项卡的顶部。

    要查看下面的选项卡,请打开选项菜单并取消选中“使用 TabComponents”复选框。

  7. 通过再次选择“使用 TabComponents”复选框来显示带有组件的选项卡。

  8. 关闭所有选项卡。现在选项卡窗格是空的。


删除选项卡

下面的代码来自ButtonTabComponent.java,从选项卡窗格中移除一个选项卡。请注意,需要事件处理代码。由于每个选项卡包含一个真实的JButton对象,您必须将ActionListener附加到关闭按钮上。当用户点击按钮时,actionPerformed方法确定它所属的选项卡的索引,并移除相应的选项卡。

public void actionPerformed(ActionEvent e) {
    int i = pane.indexOfTabComponent(ButtonTabComponent.this);
    if (i != -1) {
    pane.remove(i);
    }
}

为自定义选项卡添加标题

下面的代码取自ButtonTabComponent.java,展示了如何从原始选项卡窗格选项卡中获取自定义选项卡组件的标题。

JLabel label = new JLabel(title) {
    public String getText() {
        int i = pane.indexOfTabComponent(ButtonTabComponent.this);
        if (i != -1) {
            return pane.getTitleAt(i);
        }
        return null;
    }
};

选项卡窗格 API

以下表格列出了常用的JTabbedPane构造函数和方法。使用选项卡窗格的 API 分为以下几个类别:

  • 创建和设置选项卡窗格

  • 插入、删除、查找和选择选项卡

  • 更改选项卡外观

  • 在选项卡上设置自定义组件

创建和设置选项卡窗格

方法或构造函数 目的

| JTabbedPane() JTabbedPane(int)

JTabbedPane(int, int) | 创建一个选项卡窗格。第一个可选参数指定选项卡应出现的位置。默认情况下,选项卡出现在选项卡窗格的顶部。您可以指定这些位置(在SwingConstants接口中定义,由JTabbedPane实现):TOPBOTTOMLEFTRIGHT。第二个可选参数指定选项卡布局策略。您可以指定这些策略之一(在JTabbedPane中定义):WRAP_TAB_LAYOUTSCROLL_TAB_LAYOUT。 |

| addTab(String, Icon, Component, String) addTab(String, Icon, Component)

addTab(String, Component) | 向选项卡窗格添加一个新选项卡。第一个参数指定选项卡上的文本。可选的图标参数指定选项卡的图标。组件参数指定选项卡被选中时选项卡窗格应显示的组件。如果存在第四个参数,则指定选项卡的工具提示文本。 |

void setTabLayoutPolicy(int) int getTabLayoutPolicy() 设置或获取选项卡窗格在所有选项卡不适合单个运行时使用的布局策略。可能的值为WRAP_TAB_LAYOUTSCROLL_TAB_LAYOUT。默认策略是WRAP_TAB_LAYOUT
void setTabPlacement(int) int getTabPlacement() 设置或获取选项卡相对于内容出现的位置。可能的值(在SwingConstants中定义,由JTabbedPane实现)为TOPBOTTOMLEFTRIGHT

插入、删除、查找和选择选项卡

方法 目的
insertTab(String, Icon, Component, String, int) 在指定的索引处插入一个选项卡,其中第一个选项卡的索引为 0。参数与addTab相同。
删除(组件) 删除指定索引处的选项卡 删除与指定组件或索引对应的选项卡。
删除所有 删除所有选项卡。

| 组件的索引 选项卡的索引

选项卡的索引(图标) | 返回具有指定组件、标题或图标的选项卡的索引。 |

void setSelectedIndex(int) void setSelectedComponent(Component) 选择具有指定组件或索引的选项卡。选择选项卡会显示其关联的组件。
获取选定索引的整数 获取选定组件的组件 返回选定选项卡的索引或组件。

更改选项卡外观

方法 目的
void setComponentAt(int, Component) 获取指定索引处选项卡关联的组件 设置或获取与指定索引处选项卡关联的组件。第一个选项卡的索引为 0。
void setTitleAt(int, String) 获取指定索引处选项卡的标题 设置或获取指定索引处选项卡的标题。

| void setIconAt(int, Icon) 获取指定索引处的图标

void setDisabledIconAt(int, Icon)

获取指定索引处选项卡显示的禁用图标 | 设置或获取指定索引处选项卡显示的图标。 |

| void setBackgroundAt(int, Color) Color getBackgroundAt(int)

void setForegroundAt(int, Color)

Color getForegroundAt(int) | 设置或获取指定索引处选项卡使用的背景或前景颜色。默认情况下,选项卡使用选项卡窗格的背景和前景颜色。例如,如果选项卡窗格的前景色是黑色,则每个选项卡的标题都是黑色,除非您使用setForegroundAt指定另一种颜色。

void setEnabledAt(int, boolean) boolean isEnabledAt(int) 设置或获取指定索引处选项卡的启用状态。
void setMnemonicAt(int, int) int getMnemonicAt(int) 设置或获取访问指定选项卡的键盘助记符。
void setDisplayedMnemonicIndexAt(int, int) int getDisplayedMnemonicIndexAt(int) 设置或获取应该被装饰以表示助记符的字符。当助记符字符在选项卡标题中出现多次且您不希望第一次出现时被下划线标记时,这将非常有用。
void setToolTipTextAt(int, String) String getToolTipTextAt(int) 设置或获取指定选项卡上显示的工具提示文本。

设置自定义组件在选项卡上

方法 目的
void setTabComponentAt(int, Component) 设置负责为由第一个参数指定的选项卡渲染标题或图标(或两者)的组件。当指定空值时,JTabbedPane将渲染标题或图标。同一组件不能用于多个选项卡。
获取指定索引处的选项卡组件 获取指定索引处选项卡的选项卡组件。如果指定选项卡没有选项卡组件,则返回空值。
检查组件是否属于选项卡 检查指定组件是否属于其中一个选项卡。返回相应选项卡的索引,如果没有这样的选项卡,则返回-1。

使用选项卡窗格的示例

此表列出了使用JTabbedPane的示例,并指向这些示例的描述位置。

示例 描述位置 备注
TabbedPaneDemo 本页 演示一些选项卡窗格的功能,如工具提示、图标、可滚动布局和助记键。
TabComponentsDemo 本页 演示选项卡上的自定义组件。使用带有关闭按钮的选项卡窗格。
BoxAlignmentDemo 如何使用 BoxLayout JTabbedPane用作框架内容窗格的唯一子元素。
BorderDemo 如何使用边框 以类似于BoxAlignmentDemo的方式使用其选项卡窗格。
DialogDemo 如何使用对话框 在框架内容窗格的中心有一个带有标签的选项卡窗格。

如何使用表格

原文:docs.oracle.com/javase/tutorial/uiswing/components/table.html

使用JTable类可以显示数据表,可选择允许用户编辑数据。 JTable不包含或缓存数据;它只是您数据的一个视图。这里是一个典型表格在滚动窗格中显示的图片:

TableDemo 的快照,显示了一个典型的表格。

本节的其余部分将向您展示如何完成一些常见的与表格相关的任务。本节涵盖以下主题:

  • 创建简单表格

  • 将表格添加到容器中

  • 设置和更改列宽

  • 用户选择

  • 创建表格模型

  • 监听数据更改

  • 触发数据更改事件

  • 概念:编辑器和渲染器

  • 使用自定义渲染器

  • 为单元格指定工具提示

  • 为列标题指定工具提示

  • 排序和过滤

  • 使用下拉框作为编辑器

  • 使用其他编辑器

  • 使用编辑器验证用户输入的文本

  • 打印

  • 使用表格的示例

创建简单表格


试试这个:

  1. 点击“启动”按钮以使用Java™ Web Start运行SimpleTableDemo下载 JDK 7 或更高版本)。或者,要自己编译和运行示例,请参考示例索引。

    启动 SimpleTableDemo 示例

  2. 点击包含“滑雪”的单元格。

    整个第一行被选中,表示您已选择了 Kathy Smith 的数据。特殊的高亮显示表示“滑雪”单元格可编辑。通常,双击文本单元格即可开始编辑。

  3. 将光标放在“名字”上。现在按下鼠标按钮并向右拖动。

    如您所见,用户可以重新排列表格中的列。

  4. 将光标放在列标题的右侧。现在按下鼠标按钮并向右或向左拖动。

    列的大小会改变,其他列会调整以填充剩余空间。

  5. 调整包含表格的窗口大小,使其比显示整个表格所需的空间更大。

    所有表格单元格变宽,扩展以填充额外的水平空间。


SimpleTableDemo.java中的表格使用字符串数组声明列名:

String[] columnNames = {"First Name",
                        "Last Name",
                        "Sport",
                        "# of Years",
                        "Vegetarian"};

其数据被初始化并存储在二维对象数组中:

Object[][] data = {
    {"Kathy", "Smith",
     "Snowboarding", new Integer(5), new Boolean(false)},
    {"John", "Doe",
     "Rowing", new Integer(3), new Boolean(true)},
    {"Sue", "Black",
     "Knitting", new Integer(2), new Boolean(false)},
    {"Jane", "White",
     "Speed reading", new Integer(20), new Boolean(true)},
    {"Joe", "Brown",
     "Pool", new Integer(10), new Boolean(false)}
};

然后使用这些数据和列名构建表格:

JTable table = new JTable(data, columnNames);

有两个直接接受数据的JTable构造函数(SimpleTableDemo使用第一个):

  • JTable(Object[][] rowData, Object[] columnNames)

  • JTable(Vector rowData, Vector columnNames)

这些构造函数的优点是易于使用。但是,这些构造函数也有缺点:

  • 它们会自动使每个单元格可编辑。

  • 它们将所有数据类型视为相同(字符串)。例如,如果表格列具有Boolean数据,表格可以将数据显示为复选框。但是,如果您使用前面列出的两个JTable构造函数之一,您的Boolean数据将显示为字符串。您可以在前一个图中的Vegetarian列中看到这种差异。

  • 它们要求您将所有表格数据放入数组或向量中,这对于某些数据可能不合适。例如,如果您正在从数据库实例化一组对象,您可能希望直接查询对象以获取其值,而不是将所有值复制到数组或向量中。

如果您想绕过这些限制,您需要实现自己的表格模型,如创建表格模型中所述。

将表格添加到容器中

这是创建滚动窗格作为表格容器的典型代码:

JScrollPane scrollPane = new JScrollPane(table);
table.setFillsViewportHeight(true);

此片段中的两行代码执行以下操作:

  • 使用引用表格对象的参数调用JScrollPane构造函数。这将创建一个作为表格容器的滚动窗格;表格会自动添加到容器中。

  • 调用JTable.setFillsViewportHeight来设置fillsViewportHeight属性。当此属性为true时,表格将使用容器的整个高度,即使表格没有足够的行来使用整个垂直空间。这使得将表格用作拖放目标更容易。

滚动窗格会自动将表头放置在视口顶部。当表格数据滚动时,列名仍然可见在查看区域的顶部。

如果您使用没有滚动窗格的表格,则必须自行获取表头组件并放置它。例如:

container.setLayout(new BorderLayout());
container.add(table.getTableHeader(), BorderLayout.PAGE_START);
container.add(table, BorderLayout.CENTER);

设置和更改列宽度

默认情况下,表格中的所有列都具有相等的宽度,并且列会自动填充表格的整个宽度。当表格变宽或变窄时(当用户调整包含表格的窗口大小时可能会发生),所有列宽度会相应地更改。

当用户通过拖动列的右边框调整列的大小时,要么其他列的大小会改变,要么表格的大小会改变。默认情况下,表格的大小保持不变,拖动点左侧的列增加或减少的空间会导致拖动点右侧的所有列调整大小。

要自定义初始列宽度,您可以在表格的每一列上调用setPreferredWidth。这将设置列的首选宽度和它们的大致相对宽度。例如,将以下代码添加到SimpleTableDemo中会使其第三列比其他列更宽:

TableColumn column = null;
for (int i = 0; i < 5; i++) {
    column = table.getColumnModel().getColumn(i);
    if (i == 2) {
        column.setPreferredWidth(100); //third column is bigger
    } else {
        column.setPreferredWidth(50);
    }
}

正如前面的代码所示,表格中的每一列都由一个TableColumn对象表示。TableColumn提供了用于获取和设置列的最小、首选和最大宽度的方法,以及用于获取当前宽度的方法。有关根据绘制单元格内容所需空间的近似值设置单元格宽度的示例,请参见TableRenderDemo.java中的initColumnSizes方法。

当用户明确调整列宽时,列的首选宽度会被设置为用户指定的大小,成为列的新当前宽度。然而,当表格本身被调整大小时 — 通常是因为窗口大小调整 —;列的首选宽度不会改变。相反,现有的首选宽度会被用来计算新的列宽以填充可用空间。

您可以通过调用setAutoResizeMode来更改表格的调整大小行为。

用户选择

在其默认配置中,表格支持由一个或多个行组成的选择。用户可以选择一系列连续的行或任意一组行。用户指示的最后一个单元格会得到特殊的指示;在 Metal 外观中,该单元格会被勾勒出来。这个单元格被称为主要选择;有时被称为“焦点单元格”或“当前单元格”。

用户使用鼠标和/或键盘进行选择,如下表所述:

操作 鼠标操作 键盘操作
选择单行。 点击。 上箭头或下箭头。
扩展连续选择。 Shift-Click 或拖动行。 Shift-Up Arrow 或 Shift-Down Arrow。
将行添加到选择/切换行选择。 Control-Click 使用 Control-Up Arrow 或 Control-Down Arrow 移动主要选择,然后使用空格键添加到选择或使用 Control-Space 键切换行选择。

要查看选择是如何工作的,请单击“启动”按钮以使用Java™ Web Start运行TableSelectionDemo下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。

启动 TableSelectionDemo 示例

此示例程序呈现了熟悉的表格,并允许用户操作某些 JTable 选项。还有一个文本窗格记录选择事件。

在下面的截图中,用户运行了程序,在第一行单击,然后在第三行按住控制键单击。注意最后单击的单元格周围的轮廓;这是 Metal 外观如何突出显示主要选择。

TableSelectionDemo 显示非连续行选择。

在“选择模式”下有一组单选按钮。单击标记为“单一选择”的按钮。现在您只能一次选择一行。如果单击“单一间隔选择”单选按钮,则可以选择必须是连续的一组行。

“选择模式”下的所有单选按钮都调用JTable.setSelectionMode。此方法接受一个参数,必须是javax.swing.ListSelectionModel中定义的以下常量之一:MULTIPLE_INTERVAL_SELECTIONSINGLE_INTERVAL_SELECTIONSINGLE_SELECTION

回到TableSelectionDemo,注意“选择选项”下的三个选项复选框。每个复选框控制由JTable定义的boolean绑定变量的状态:

  • “行选择”控制rowSelectionAllowed,其具有 setter 方法setRowSelectionAllowed和 getter 方法getRowSelectionAllowed。当此绑定属性为true(且columnSelectionAllowed属性为false)时,用户可以按行选择。

  • “列选择”控制columnSelectionAllowed,其具有 setter 方法setColumnSelectionAllowed和 getter 方法getColumnSelectionAllowed。当此绑定属性为true(且rowSelectionAllowed绑定属性为false)时,用户可以按列选择。

  • “单元格选择”控制cellSelectionEnabled,其具有 setter 方法setCellSelectionEnabled和 getter 方法getCellSelectionEnabled。当此绑定属性为true时,用户可以选择单个单元格或矩形块的单元格。


注意: JTable使用非常简单的选择概念,作为行和列的交集进行管理。它不是设计为处理完全独立的单元格选择。


如果清除所有三个复选框(将所有三个绑定属性设置为false),则没有选择;只显示主要选择。

您可能注意到在多个间隔选择模式下,“单元格选择”复选框被禁用。这是因为在演示中不支持单元格选择。您可以在多个间隔选择模式下指定按单元格选择,但结果是产生不实用的选择的表格。

您可能还注意到,更改三个选择选项中的任何一个都可能影响其他选项。这是因为允许行选择和列选择与启用单元格选择完全相同。JTable会根据需要自动更新三个绑定变量,以保持它们一致。


注意:cellSelectionEnabled设置为一个值会同时将rowSelectionEnabledcolumnSelectionEnabled设置为该值。将rowSelectionEnabledcolumnSelectionEnabled都设置为一个值会同时将cellSelectionEnabled设置为该值。将rowSelectionEnabledcolumnSelectionEnabled设置为不同的值会同时将cellSelectionEnabled设置为false


要检索当前选择,请使用JTable.getSelectedRows返回一个行索引数组,以及JTable.getSelectedColumns返回一个列索引数组。要检索主选择的坐标,请参考表本身和表的列模型的选择模型。以下代码格式化一个包含主选择行和列的字符串:

String.format("Lead Selection: %d, %d. ",
    table.getSelectionModel().getLeadSelectionIndex(),
    table.getColumnModel().getSelectionModel().getLeadSelectionIndex());

用户选择会生成多个事件。有关这些事件的信息,请参考如何编写列表选择监听器中的编写事件监听器课程。


注意: 选择数据实际上描述了在“视图”中选择的单元格(表数据在任何排序或过滤后的外观),而不是在表模型中的单元格。除非您查看的数据已通过排序、过滤或用户操作列重新排列,否则此区别并不重要。在这种情况下,您必须使用排序和过滤中描述的转换方法转换选择坐标。


创建表模型

每个表对象都使用一个表模型对象来管理实际的表数据。表模型对象必须实现TableModel接口。如果程序员没有提供表模型对象,JTable会自动创建一个DefaultTableModel实例。下面展示了这种关系。

表、表对象、模型对象之间的关系

SimpleTableDemo使用的JTable构造函数会使用以下代码创建其表模型:

new AbstractTableModel() {
    public String getColumnName(int col) {
        return columnNames[col].toString();
    }
    public int getRowCount() { return rowData.length; }
    public int getColumnCount() { return columnNames.length; }
    public Object getValueAt(int row, int col) {
        return rowData[row][col];
    }
    public boolean isCellEditable(int row, int col)
        { return true; }
    public void setValueAt(Object value, int row, int col) {
        rowData[row][col] = value;
        fireTableCellUpdated(row, col);
    }
}

如前面的代码所示,实现表模型可以很简单。通常,您会在AbstractTableModel类的子类中实现您的表模型。

您的模型可能将数据保存在数组、向量或哈希映射中,也可能从外部来源(如数据库)获取数据。甚至可能在执行时生成数据。

这个表格与SimpleTableDemo表格有以下不同之处:

  • TableDemo的自定义表格模型虽然简单,但可以轻松确定数据的类型,帮助JTable以最佳格式显示数据。另一方面,SimpleTableDemo自动创建的表格模型并不知道# of Years列包含数字(通常应右对齐并具有特定格式)。它也不知道Vegetarian列包含布尔值,可以用复选框表示。

  • TableDemo中实现的自定义表格模型不允许您编辑名称列;但是,它允许您编辑其他列。在SimpleTableDemo中,所有单元格都是可编辑的。

请看下面从TableDemo.java中提取的代码,它与SimpleTableDemo.java不同。粗体字表示的代码表明了使该表格模型与SimpleTableDemo自动定义的表格模型不同的代码。

public TableDemo() {
    ...
    JTable table = new JTable(new MyTableModel());
    ...
}

class MyTableModel extends AbstractTableModel {
    private String[] columnNames = *...//same as before...*
    private Object[][] data = *...//same as before...*

    public int getColumnCount() {
        return columnNames.length;
    }

    public int getRowCount() {
        return data.length;
    }

    public String getColumnName(int col) {
        return columnNames[col];
    }

    public Object getValueAt(int row, int col) {
        return data[row][col];
    }

    public Class getColumnClass(int c) {
        return getValueAt(0, c).getClass();
    }

    /*
     * Don't need to implement this method unless your table's
     * editable.
     */
    public boolean isCellEditable(int row, int col) {
        //Note that the data/cell address is constant,
        //no matter where the cell appears onscreen.
        if (col < 2) {
            return false;
        } else {
            return true;
        }
    }

    /*
     * Don't need to implement this method unless your table's
     * data can change.
     */
    public void setValueAt(Object value, int row, int col) {
        data[row][col] = value;
        fireTableCellUpdated(row, col);
    }
    ...
}

监听数据更改

表格模型可以有一组监听器,每当表格数据发生变化时就会通知它们。监听器是TableModelListener的实例。在下面的示例代码中,SimpleTableDemo被扩展以包含这样一个监听器。新代码用粗体表示。

import javax.swing.event.*;
import javax.swing.table.TableModel;

public class SimpleTableDemo ... implements TableModelListener {
    ...
    public SimpleTableDemo() {
        ...
        table.getModel().addTableModelListener(this);
        ...
    }

    public void tableChanged(TableModelEvent e) {
        int row = e.getFirstRow();
        int column = e.getColumn();
        TableModel model = (TableModel)e.getSource();
        String columnName = model.getColumnName(column);
        Object data = model.getValueAt(row, column);

        *...// Do something with the data...*
    }
    ...
}

触发数据更改事件

为了触发数据更改事件,表格模型必须知道如何构造一个TableModelEvent对象。这可能是一个复杂的过程,但已经在DefaultTableModel中实现。您可以允许JTable使用其默认的DefaultTableModel实例,或者创建自己的DefaultTableModel的自定义子类。

如果DefaultTableModel不适合作为您的自定义表格模型类的基类,请考虑子类化AbstractTableModel。这个类实现了一个简单的框架来构造TableModelEvent对象。您的自定义类只需在外部源更改表格数据时调用以下AbstractTableModel方法之一。

方法 变更
fireTableCellUpdated 更新指定单元格。
fireTableRowsUpdated 更新指定行
fireTableDataChanged 更新整个表格(仅数据)。
fireTableRowsInserted 插入新行。
fireTableRowsDeleted 删除现有行
fireTableStructureChanged 使整个表格无效,包括数据和结构。

概念:编辑器和渲染器

在继续下面的几项任务之前,您需要了解表格如何绘制其单元格。您可能期望表格中的每个单元格都是一个组件。然而,出于性能原因,Swing 表格的实现方式不同。

相反,通常使用单个单元格渲染器来绘制包含相同类型数据的所有单元格。您可以将渲染器视为可配置的墨水印,表格用它来将适当格式的数据印在每个单元格上。当用户开始编辑单元格数据时,单元格编辑器接管单元格,控制单元格的编辑行为。

例如,在TableDemo# of Years列中的每个单元格都包含Number数据 — 具体来说,是一个Integer对象。默认情况下,包含Number的列的单元格渲染器使用单个JLabel实例在列的单元格上绘制适当的数字,右对齐。如果用户开始编辑其中一个单元格,默认单元格编辑器使用右对齐的JTextField来控制单元格编辑。

要选择显示列中单元格的渲染器,表格首先确定您是否为该特定列指定了渲染器。如果没有,则表格调用表格模型的getColumnClass方法,该方法获取列单元格的数据类型。接下来,表格将列的数据类型与已注册单元格渲染器的数据类型列表进行比较。此列表由表格初始化,但您可以添加或更改。目前,表格将以下类型的数据放入列表中:

  • Boolean — 使用复选框渲染。

  • Number — 由右对齐的标签渲染。

  • DoubleFloat — 与Number相同,但对象到文本的转换由NumberFormat实例执行(使用当前区域设置的默认数字格式)。

  • Date — 由一个标签渲染,对象到文本的转换由DateFormat实例执行(使用日期和时间的短格式)。

  • ImageIconIcon — 由居中的标签渲染。

  • Object — 由显示对象字符串值的标签渲染。

单元格编辑器是使用类似的算法选择的。

请记住,如果让表格创建自己的模型,它会将Object用作每列的类型。要指定更精确的列类型,表格模型必须适当定义getColumnClass方法,正如TableDemo.java所示。

请记住,尽管渲染器确定每个单元格或列标题的外观并可以指定其工具提示文本,但渲染器不处理事件。如果您需要捕获表格内发生的事件,您所使用的技术取决于您感兴趣的事件类型:

情况 如何获取事件
要检测正在编辑的单元格的事件... 使用单元格编辑器(或在单元格编辑器上注册监听器)。
要检测行/列/单元格的选择和取消选择... 使用如检测用户选择中描述的选择监听器。
要在列标题上检测鼠标事件... 在表的JTableHeader对象上注册适当类型的鼠标监听器。(参见TableSorter.java的示例。)
要检测其他事件... JTable对象上注册适当的监听器。

接下来的几节告诉您如何通过指定渲染器和编辑器来自定义显示和编辑。您可以按列或数据类型指定单元格渲染器和编辑器。

使用自定义渲染器

本节告诉您如何创建和指定单元格渲染器。您可以使用JTable方法setDefaultRenderer设置特定类型的单元格渲染器。要指定特定列中的单元格应使用渲染器,可以使用TableColumn方法setCellRenderer。甚至可以通过创建JTable子类来指定特定单元格的渲染器。

定制默认渲染器DefaultTableCellRenderer渲染的文本或图像很容易。只需创建一个子类并实现setValue方法,使其调用适当的字符串或图像的setTextsetIcon。例如,这是默认日期渲染器的实现方式:

static class DateRenderer extends DefaultTableCellRenderer {
    DateFormat formatter;
    public DateRenderer() { super(); }

    public void setValue(Object value) {
        if (formatter==null) {
            formatter = DateFormat.getDateInstance();
        }
        setText((value == null) ? "" : formatter.format(value));
    }
}

如果扩展DefaultTableCellRenderer不够,可以使用另一个超类构建渲染器。最简单的方法是创建一个现有组件的子类,使您的子类实现TableCellRenderer接口。TableCellRenderer只需要一个方法:getTableCellRendererComponent。您实现此方法应设置渲染组件以反映传入状态,然后返回组件。

TableDialogEditDemo的快照中,用于Favorite Color单元格的渲染器是一个名为ColorRendererJLabel子类。以下是ColorRenderer.java的摘录,展示了它的实现方式。

public class ColorRenderer extends JLabel
                           implements TableCellRenderer {
    ...
    public ColorRenderer(boolean isBordered) {
        this.isBordered = isBordered;
        setOpaque(true); //MUST do this for background to show up.
    }

    public Component getTableCellRendererComponent(
                            JTable table, Object color,
                            boolean isSelected, boolean hasFocus,
                            int row, int column) {
        Color newColor = (Color)color;
        setBackground(newColor);
        if (isBordered) {
            if (isSelected) {
                ...
                //selectedBorder is a solid border in the color
                //table.getSelectionBackground().
                setBorder(selectedBorder);
            } else {
                ...
                //unselectedBorder is a solid border in the color
                //table.getBackground().
                setBorder(unselectedBorder);
            }
        }

        setToolTipText(...); *//Discussed in the following section*
        return this;
    }
}

这是从TableDialogEditDemo.java中注册ColorRenderer实例作为所有Color数据的默认渲染器的代码:

table.setDefaultRenderer(Color.class, new ColorRenderer(true));

要指定特定单元格的渲染器,需要定义一个覆盖getCellRenderer方法的JTable子类。例如,以下代码使表格中第一列的第一个单元格使用自定义渲染器:

TableCellRenderer weirdRenderer = new WeirdRenderer();
table = new JTable(...) {
    public TableCellRenderer getCellRenderer(int row, int column) {
        if ((row == 0) && (column == 0)) {
            return weirdRenderer;
        }
        // else...
        return super.getCellRenderer(row, column);
    }
};

为单元格指定工具提示

默认情况下,表格单元格显示的工具提示文本由单元格的渲染器确定。然而,有时通过覆盖JTablegetToolTipText(MouseEvent)方法来指定工具提示文本可能更简单。本节将向您展示如何同时使用这两种技术。

要通过其渲染器为单元格添加工具提示,首先需要获取或创建单元格渲染器。然后,在确保渲染组件是JComponent后,在其上调用setToolTipText方法。

TableRenderDemo中设置单元格工具提示的示例。点击启动按钮以使用Java™ Web Start运行它(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。

启动 TableRenderDemo 示例

源代码在TableRenderDemo.java中。它使用以下代码为Sport列的单元格添加工具提示:

//Set up tool tips for the sport cells.
DefaultTableCellRenderer renderer =
        new DefaultTableCellRenderer();
renderer.setToolTipText("Click for combo box");
sportColumn.setCellRenderer(renderer);

尽管前面示例中的工具提示文本是静态的,但您也可以实现根据单元格或程序状态变化的工具提示。以下是几种实现方式:

  • 在渲染器的getTableCellRendererComponent方法的实现中添加一点代码。

  • 覆盖JTable方法getToolTipText(MouseEvent)

TableDialogEditDemo中添加代码到单元格渲染器的示例。点击启动按钮以使用Java™ Web Start运行它(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。

启动 TableDialogEditDemo 示例

TableDialogEditDemo使用了一个颜色渲染器,实现在ColorRenderer.java中,使用粗体代码设置工具提示文本,如下所示:

public class ColorRenderer extends JLabel 
                           implements TableCellRenderer {
    ...
    public Component getTableCellRendererComponent(
                            JTable table, Object color,
                            boolean isSelected, boolean hasFocus,
                            int row, int column) {
        Color newColor = (Color)color;
        ...
        setToolTipText("RGB value: " + newColor.getRed() + ", "
                                     + newColor.getGreen() + ", "
                                     + newColor.getBlue());
        return this;
    }
}

这是工具提示的示例:

TableDialogEditDemo 中描述鼠标悬停在单元格上的 RGB 值的工具提示

您可以通过重写JTablegetToolTipText(MouseEvent)方法来指定工具提示文本。程序TableToolTipsDemo展示了如何操作。点击启动按钮以使用Java™ Web Start运行它(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。

启动 TableToolTipsDemo 示例

工具提示的单元格位于SportVegetarian列中。这是其工具提示的图片:

TableToolTipsDemo 中 Sport 列单元格的工具提示

这是TableToolTipsDemo.java中实现SportVegetarian列单元格工具提示的代码:

JTable table = new JTable(new MyTableModel()) {    
    //Implement table cell tool tips.
    public String getToolTipText(MouseEvent e) {
        String tip = null;
        java.awt.Point p = e.getPoint();
        int rowIndex = rowAtPoint(p);
        int colIndex = columnAtPoint(p);
        int realColumnIndex = convertColumnIndexToModel(colIndex);

        if (realColumnIndex == 2) { //Sport column
            tip = "This person's favorite sport to "
                   + "participate in is: "
                   + getValueAt(rowIndex, colIndex);

        } else if (realColumnIndex == 4) { //Veggie column
            TableModel model = getModel();
            String firstName = (String)model.getValueAt(rowIndex,0);
            String lastName = (String)model.getValueAt(rowIndex,1);
            Boolean veggie = (Boolean)model.getValueAt(rowIndex,4);
            if (Boolean.TRUE.equals(veggie)) {
                tip = firstName + " " + lastName
                      + " is a vegetarian";
            } else {
                tip = firstName + " " + lastName
                      + " is not a vegetarian";
            }

        } else { //another column
            //You can omit this part if you know you don't 
            //have any renderers that supply their own tool 
            //tips.
            tip = super.getToolTipText(e);
        }
        return tip;
    }
    ...
}

代码相当简单,除了convertColumnIndexToModel的调用可能有点复杂。这个调用是必要的,因为如果用户移动列,视图对于列的索引将不匹配模型对于列的索引。例如,用户可能会拖动Vegetarian列(模型认为在索引 4 处),使其显示为第一列 - 在视图索引 0 处。由于prepareRenderer提供了视图索引,您需要将视图索引转换为模型索引,以确保选择了预期的列。

为列标题指定工具提示

您可以通过为表的JTableHeader设置工具提示文本来向列标题添加工具提示。通常,不同的列标题需要不同的工具提示文本。您可以通过重写表头的getToolTipText方法来更改文本。或者,您可以调用TableColumn.setHeaderRenderer来为表头提供自定义渲染器。

TableSorterDemo.java中有一个示例,它为所有列标题使用相同的工具提示文本。以下是它设置工具提示文本的方式:

table.getTableHeader().setToolTipText(
        "Click to sort; Shift-Click to sort in reverse order");

TableToolTipsDemo.java有一个示例,实现了根据列变化的列标题工具提示。如果运行TableToolTipsDemo(点击启动按钮)使用Java™ Web Start下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。

启动 TableToolTipsDemo 示例

当您将鼠标悬停在除前两列之外的任何列标题上时,您将看到工具提示。对于名称列没有提供工具提示,因为它们似乎是不言自明的。这是其中一个列标题工具提示的图片:

TableToolTipsDemo 带有列标题工具提示

以下代码实现了工具提示。基本上,它创建了JTableHeader的一个子类,覆盖了getToolTipText(MouseEvent)方法,以便返回当前列的文本。为了将修改后的表头与表格关联起来,重写了JTable方法createDefaultTableHeader,使其返回JTableHeader子类的一个实例。

protected String[] columnToolTips = {
    null, // "First Name" assumed obvious
    null, // "Last Name" assumed obvious
    "The person's favorite sport to participate in",
    "The number of years the person has played the sport",
    "If checked, the person eats no meat"};
...

JTable table = new JTable(new MyTableModel()) {
    ...

    //Implement table header tool tips.
    protected JTableHeader createDefaultTableHeader() {
        return new JTableHeader(columnModel) {
            public String getToolTipText(MouseEvent e) {
                String tip = null;
                java.awt.Point p = e.getPoint();
                int index = columnModel.getColumnIndexAtX(p.x);
                int realIndex = 
                        columnModel.getColumn(index).getModelIndex();
                return columnToolTips[realIndex];
            }
        };
    }
};

排序和过滤

表格排序和过滤由一个sorter对象管理。提供一个 sorter 对象的最简单方法是将autoCreateRowSorter绑定属性设置为true

JTable table = new JTable();
table.setAutoCreateRowSorter(true);

此操作定义了一个是javax.swing.table.TableRowSorter实例的行排序器。当用户单击列标题时,这提供了一个执行简单的区域特定排序的表格。这在TableSortDemo.java中演示,如下截图所示:

点击姓氏后的 TableSortDemo

要更好地控制排序,您可以构造一个TableRowSorter的实例,并指定它是您表格的 sorter 对象。

TableRowSorter<TableModel> sorter 
    = new TableRowSorter<TableModel>(table.getModel());
table.setRowSorter(sorter);

TableRowSorter使用java.util.Comparator对象来对其行进行排序。实现此接口的类必须提供一个名为compare的方法,该方法定义了为排序目的比较任意两个对象的方式。例如,以下代码创建了一个Comparator,按每个字符串中的最后一个单词对一组字符串进行排序:

Comparator<String> comparator = new Comparator<String>() {
    public int compare(String s1, String s2) {
        String[] strings1 = s1.split("\\s");
        String[] strings2 = s2.split("\\s");
        return strings1[strings1.length - 1]
            .compareTo(strings2[strings2.length - 1]);
    }
};

这个例子相当简单;更典型的情况是,Comparator实现是java.text.Collator的子类。您可以定义自己的子类,使用Collator中的工厂方法获取特定区域设置的Comparator,或者使用java.text.RuleBasedCollator

要确定用于列的ComparatorTableRowSorter尝试依次应用以下规则。按照下面列出的顺序遵循规则;提供 sorter 的第一个规则使用Comparator,其余规则被忽略。

  1. 如果通过调用setComparator指定了比较器,则使用该比较器。

  2. 如果表模型报告列数据由字符串组成(TableModel.getColumnClass为该列返回String.class),则使用一个根据当前区域设置对字符串进行排序的比较器。

  3. 如果TableModel.getColumnClass返回的列类实现了Comparable,则使用一个根据Comparable.compareTo返回的值对字符串进行排序的比较器。

  4. 如果通过调用setStringConverter为表指定了字符串转换器,则使用一个根据当前区域设置对生成的字符串表示进行排序的比较器。

  5. 如果前述规则都不适用,则使用一个调用列数据的toString并根据当前区域设置对生成的字符串进行排序的比较器。

对于更复杂的排序类型,可以对TableRowSorter或其父类javax.swing.DefaultRowSorter进行子类化。

要为列指定排序顺序和排序优先级,请调用setSortKeys。以下是一个示例,按照示例中使用的表的前两列进行排序。在排序键列表中排序键的顺序表示排序中列的优先级。在这种情况下,第二列具有第一个排序键,因此按照名字和姓氏的顺序对行进行排序。

List <RowSorter.SortKey> sortKeys 
    = new ArrayList<RowSorter.SortKey>();
sortKeys.add(new RowSorter.SortKey(1, SortOrder.ASCENDING));
sortKeys.add(new RowSorter.SortKey(0, SortOrder.ASCENDING));
sorter.setSortKeys(sortKeys); 

除了重新排序结果,表排序器还可以指定将显示哪些行。这称为过滤TableRowSorter使用javax.swing.RowFilter对象实现过滤。RowFilter实现了几个工厂方法,用于创建常见类型的过滤器。例如,regexFilter返回一个基于正则表达式进行过滤的RowFilter

在以下示例代码中,您明确创建一个排序器对象,以便稍后用于指定过滤器:

MyTableModel model = new MyTableModel();
sorter = new TableRowSorter<MyTableModel>(model);
table = new JTable(model);
table.setRowSorter(sorter);

然后,根据文本字段的当前值进行过滤:

private void newFilter() {
    RowFilter<MyTableModel, Object> rf = null;
    //If current expression doesn't parse, don't update.
    try {
        rf = RowFilter.regexFilter(filterText.getText(), 0);
    } catch (java.util.regex.PatternSyntaxException e) {
        return;
    }
    sorter.setRowFilter(rf);
}

在下一个示例中,每次文本字段更改时都会调用newFilter()。当用户输入复杂的正则表达式时,try...catch会防止语法异常干扰输入。

当表格使用排序器时,用户看到的数据可能与数据模型指定的顺序不同,并且可能不包括数据模型指定的所有行。用户实际看到的数据称为视图,并具有自己的坐标系。JTable提供了一些方法,用于将模型坐标转换为视图坐标 — convertColumnIndexToViewconvertRowIndexToView — 以及将视图坐标转换为模型坐标 — convertColumnIndexToModelconvertRowIndexToModel


注意: 使用排序器时,始终记得转换单元格坐标。


以下示例将本节讨论的思想汇集在一起。TableFilterDemo.javaTableDemo进行了少量更改。这些包括本节前面的代码片段,为主表提供了排序器,并使用文本字段提供过滤正则表达式。以下屏幕截图显示在进行任何排序或过滤之前的TableFilterDemo。请注意,模型中的第 3 行仍然与视图中的第 3 行相同:

没有排序的 TableFilterDemo

如果用户在第二列上点击两次,则第四行变为第一行 — 但仅在视图中:

在第二列中进行反向排序的 TableFilterDemo

正如前面所述,用户在“过滤文本”文本字段中输入的文本定义了一个过滤器,确定显示哪些行。与排序一样,过滤可能导致视图坐标与模型坐标不一致:

带有过滤的 TableFilterDemo

以下是更新状态字段以反映当前选择的代码:

table.getSelectionModel().addListSelectionListener(
        new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent event) {
                int viewRow = table.getSelectedRow();
                if (viewRow < 0) {
                    //Selection got filtered away.
                    statusText.setText("");
                } else {
                    int modelRow = 
                        table.convertRowIndexToModel(viewRow);
                    statusText.setText(
                        String.format("Selected Row in view: %d. " +
                            "Selected Row in model: %d.", 
                            viewRow, modelRow));
                }
            }
        }
);

使用组合框作为编辑器

设置组合框作为编辑器很简单,如下例所示。粗体代码行设置了组合框作为特定列的编辑器。

TableColumn sportColumn = table.getColumnModel().getColumn(2);
...
JComboBox comboBox = new JComboBox();
comboBox.addItem("Snowboarding");
comboBox.addItem("Rowing");
comboBox.addItem("Chasing toddlers");
comboBox.addItem("Speed reading");
comboBox.addItem("Teaching high school");
comboBox.addItem("None");
sportColumn.setCellEditor(new DefaultCellEditor(comboBox));

这是组合框编辑器的使用示例:

使用中的组合框单元格编辑器

上述代码来自TableRenderDemo.java。您可以运行TableRenderDemo(点击启动按钮)使用Java™ Web Start下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。

启动 TableRenderDemo 示例

使用其他编辑器

无论您是为单个单元格列设置编辑器(使用TableColumnsetCellEditor方法)还是为特定类型的数据设置编辑器(使用JTablesetDefaultEditor方法),您都需要使用符合TableCellEditor接口的参数来指定编辑器。幸运的是,DefaultCellEditor类实现了这个接口,并提供构造函数让您指定一个编辑组件,它可以是JTextFieldJCheckBoxJComboBox。通常情况下,您不必显式指定复选框作为编辑器,因为具有Boolean数据的列会自动使用复选框渲染器和编辑器。

如果您想指定除文本字段、复选框或组合框之外的编辑器,该怎么办?由于DefaultCellEditor不支持其他类型的组件,您需要做更多的工作。您需要创建一个实现TableCellEditor接口的类。AbstractCellEditor类是一个很好的超类。它实现了TableCellEditor的超级接口CellEditor,省去了您实现单元格编辑器所需的事件触发代码的麻烦。

您的单元格编辑器类至少需要定义两个方法 — getCellEditorValuegetTableCellEditorComponentgetCellEditorValue方法是CellEditor所需的,返回单元格的当前值。getTableCellEditorComponent方法是TableCellEditor所需的,应配置并返回您要用作编辑器的组件。

这是一个带有对话框的表格图片,间接地作为单元格编辑器。当用户开始编辑Favorite Color列中的单元格时,会出现一个按钮(真正的单元格编辑器),并弹出对话框,用户可以选择不同的颜色。

单元格编辑器弹出对话框

您可以运行TableDialogEditDemo(点击启动按钮)使用Java™ Web Start下载 JDK 7 或更高版本)。或者,要自己编译和运行示例,请参考示例索引。

启动 TableDialogEditDemo 示例

这里是代码,取自ColorEditor.java,用于实现单元格编辑器。

public class ColorEditor extends AbstractCellEditor
                         implements TableCellEditor,
                                    ActionListener {
    Color currentColor;
    JButton button;
    JColorChooser colorChooser;
    JDialog dialog;
    protected static final String EDIT = "edit";

    public ColorEditor() {
        button = new JButton();
        button.setActionCommand(EDIT);
        button.addActionListener(this);
        button.setBorderPainted(false);

        //Set up the dialog that the button brings up.
        colorChooser = new JColorChooser();
        dialog = JColorChooser.createDialog(button,
                                        "Pick a Color",
                                        true,  //modal
                                        colorChooser,
                                        this,  //OK button handler
                                        null); //no CANCEL button handler
    }

    public void actionPerformed(ActionEvent e) {
        if (EDIT.equals(e.getActionCommand())) {
            //The user has clicked the cell, so
            //bring up the dialog.
            button.setBackground(currentColor);
            colorChooser.setColor(currentColor);
            dialog.setVisible(true);

            fireEditingStopped(); //Make the renderer reappear.

        } else { //User pressed dialog's "OK" button.
            currentColor = colorChooser.getColor();
        }
    }

    //Implement the one CellEditor method that AbstractCellEditor doesn't.
    public Object getCellEditorValue() {
        return currentColor;
    }

    //Implement the one method defined by TableCellEditor.
    public Component getTableCellEditorComponent(JTable table,
                                                 Object value,
                                                 boolean isSelected,
                                                 int row,
                                                 int column) {
        currentColor = (Color)value;
        return button;
    }
}

正如你所看到的,代码非常简单。唯一有点棘手的部分是在编辑器按钮的动作处理程序末尾调用fireEditingStopped。如果没有这个调用,即使模态对话框不再可见,编辑器仍然会保持活动状态。调用fireEditingStopped让表格知道它可以停用编辑器,让单元格再次由渲染器处理。

使用编辑器验证用户输入的文本

如果单元格的默认编辑器允许文本输入,如果单元格类型指定为StringObject之外的内容,则会获得一些免费的错误检查。错误检查是将输入的文本转换为正确类型对象的副作用。

当默认编辑器尝试创建与单元格列关联的类的新实例时,会自动检查用户输入的字符串。默认编辑器使用以String为参数的构造函数创建此实例。例如,在单元格类型为Integer的列中,当用户输入"123"时,默认编辑器使用等效于new Integer("123")的代码创建相应的Integer。如果构造函数抛出异常,则单元格的轮廓变为红色,并且拒绝让焦点移出单元格。如果您实现了用作列数据类型的类,如果您的类提供一个接受String类型单一参数的构造函数,您可以使用默认编辑器。

如果您喜欢将文本字段作为单元格的编辑器,但希望自定义它 — 也许更严格地检查用户输入的文本或在文本无效时做出不同反应 — 您可以更改单元格编辑器以使用格式化文本字段。格式化文本字段可以在用户输入时连续检查值,或在用户指示输入结束后(例如按下 Enter 键)检查值。

以下代码取自名为TableFTFEditDemo.java的演示,设置了一个格式化文本字段作为编辑器,限制所有整数值在 0 和 100 之间。您可以运行TableFTFEditDemo(点击启动按钮)使用Java™ Web Start下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。

启动 TableFTFEditDemo 示例

以下代码使格式化文本字段成为包含Integer类型数据的所有列的编辑器。

table.setDefaultEditor(Integer.class,
                       new IntegerEditor(0, 100));

IntegerEditor类是DefaultCellEditor的子类,它使用JFormattedTextField而不是DefaultCellEditor支持的JTextField。它首先设置一个格式化文本字段,使用整数格式并具有指定的最小和最大值,使用如何使用格式化文本字段中描述的 API。然后,它重写了DefaultCellEditorgetTableCellEditorComponentgetCellEditorValuestopCellEditing方法的实现,添加了格式化文本字段所需的操作。

getTableCellEditorComponent的重写在编辑器显示之前设置格式化文本字段的value属性(而不仅仅是它从JTextField继承的text属性)。getCellEditorValue的重写保持单元格值为Integer,而不是格式化文本字段的解析器倾向于返回的Long值。最后,重写stopCellEditing让您检查文本是否有效,可能阻止编辑器被关闭。如果文本无效,您的stopCellEditing实现会弹出一个对话框,给用户选择继续编辑或恢复到上一个良好值的选项。源代码有点太长无法在此处包含,但您可以在IntegerEditor.java中找到它。

打印

JTable为打印表格提供了一个简单的 API。打印表格的最简单方法是调用JTable.print而不带参数:

try {
    if (! table.print()) {
        System.err.println("User cancelled printing");
    }
} catch (java.awt.print.PrinterException e) {
    System.err.format("Cannot print %s%n", e.getMessage());
}

在普通的 Swing 应用程序上调用print会弹出一个标准的打印对话框。(在无头应用程序上,表格会直接打印。)返回值指示用户是否继续进行打印作业还是取消了。JTable.print可能会抛出java.awt.print.PrinterException,这是一个受检异常;这就是为什么上面的示例使用了try ... catch

JTable提供了几个带有各种选项的print重载。以下代码来自TablePrintDemo.java展示了如何定义页眉:

MessageFormat header = new MessageFormat("Page {0,number,integer}");
try {
    table.print(JTable.PrintMode.FIT_WIDTH, header, null);
} catch (java.awt.print.PrinterException e) {
    System.err.format("Cannot print %s%n", e.getMessage());
}

对于更复杂的打印应用程序,请使用JTable.getPrintable来获取表格的Printable对象。有关Printable的更多信息,请参考 2D 图形路径中的打印课程。

使用表格的示例

这个表格列出了使用JTable的示例以及这些示例的描述位置。

示例 描述位置 注释
SimpleTableDemo 创建简单表格 一个基本的表格,没有自定义模型。不包括用于指定列宽度或检测用户编辑的代码。
SimpleTable- SelectionDemo 检测用户选择 SimpleTableDemo添加单选和选择检测。通过修改程序的ALLOW_COLUMN_SELECTIONALLOW_ROW_SELECTION常量,您可以尝试替代只允许选择行的表格默认设置。
TableDemo 创建表格模型 一个带有自定义模型的基本表格。
TableFTFEditDemo 使用编辑器验证用户输入文本 修改TableDemo以使用自定义编辑器(格式化文本字段变体)来处理所有Integer数据。
TableRenderDemo 使用下拉框作为编辑器 修改TableDemo以使用自定义编辑器(下拉框)来处理Sport列中的所有数据。还智能选择列大小。使用渲染器为运动单元格显示工具提示。
TableDialogEditDemo 使用其他编辑器 修改TableDemo以具有显示颜色并让您选择新颜色的单元格渲染器和编辑器,使用颜色选择对话框。
TableToolTipsDemo 为单元格指定工具提示, 为列标题指定工具提示, 展示了如何使用多种技巧为单元格和列标题设置工具提示文本。
TableSortDemo 排序和过滤 展示了默认的排序器,允许用户通过点击表头对列进行排序。
TableFilterDemo 排序和过滤 展示了排序和过滤的方法,以及这可能导致视图坐标与模型坐标不一致。
TablePrintDemo 打印 展示了表格打印的方法。
ListSelectionDemo 如何编写列表选择监听器 展示了如何使用列表选择监听器来使用所有列表选择模式,该监听器在表格和列表之间共享。
SharedModelDemo 现在不适用 ListSelectionDemo的基础上构建,使数据模型在表格和列表之间共享。如果你编辑表格的第一列中的项目,新值将反映在列表中。
posted @   绝不原创的飞龙  阅读(33)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2023-04-12 NumPy 初学者指南中文第三版:11~14
2023-04-12 NumPy 初学者指南中文第三版:6~10
2023-04-12 NumPy 初学者指南中文第三版:1~5
2023-04-12 NumPy 秘籍中文第二版:11~12
2023-04-12 NumPy 秘籍中文第二版:6~10
2023-04-12 NumPy 秘籍中文第二版:1~5
2023-04-12 NumPy 秘籍中文第二版:十二、使用 NumPy 进行探索性和预测性数据分析
点击右上角即可分享
微信分享提示