十八章Swing程序设计

Swing用于开发桌面窗体程序,是JDK的第二代GUI框架,其功能比JDK第一代GUI框架AWT更为强大、性能更加优良。但因为Swing技术推出时间太早,其性能、开发效率等不及一些其他流行技术,所以目前市场上大多数桌面窗体程序都不是由Java开发的,Swing技术也逐渐被广大开发人员放弃了。

不过,Swing是JDK自带的功能,并且能非常好地体现Java语言在面向对象、接口事件等方面的设计模式,又能提供直观地呈现运行效果,所以本书还是纳入此内容。本章不会深入地讲解Swing技术,仅会介绍一些常用组件的使用方法,读者了解即可。

Swing中的大多数组件均为轻量级组件,使用Swing开发出的窗体风格会与当前平台(如Windows、Linux等)的窗体风格保持一致。本章主要介绍Swing中的基本要素,包括窗体的布局、容器、常用组件、如何创建表格等内容。

18.1 Swing概述

Swing主要用来开发GUI(Graphical User Interface)程序,GUI是应用程序提供给用户操作的图形界面,包括窗口、菜单、按钮等图形界面元素,我们经常使用的QQ软件、360安全卫士等均为GUI程序。Java语言为Swing程序的开发提供了丰富的类库,这些类分别被存储在java.awt和javax.swing包中。Swing提供了丰富的组件,在开发Swing程序时,这些组件被广泛地应用。

Swing组件是完全由Java语言编写的组件。因为Java语言不依赖于本地平台(即“操作系统”),所以Swing组件可以被应用于任何平台。基于“跨平台”这一特性,Swing组件被称作“轻量级组件”;反之,依赖于本地平台的组件被称作“重量级组件”,

在Swing包的层次结构和继承关系中,比较重要的类是Component类(组件类)、Container类(容器类)和JComponent类(Swing组件父类)。Swing包的层次结构和继承关系如图18.1所示。

Swing包的层次结构和继承关系

图18.1包含了一些Swing组件,常用的Swing组件如表18.1所示。

表18.1 常用的Swing组件

18.2 Swing常用窗体

18.2.1 JFrame窗体

开发Swing程序的流程可以被简单地概括为首先通过继承javax.swing.JFrame类创建一个窗体,然后向这个窗体中添加组件,最后为添加的组件设置监听事件。下面将详细讲解JFrame窗体的使用方法。

JFrame类的常用构造方法包括以下两种形式:

  1. public JFrame():创建一个初始不可见、没有标题的窗体。
  2. public JFrame(String title):创建一个不可见、具有标题的窗体。

例如,创建一个不可见、具有标题的窗体,关键代码如下:

JFrame jf = new JFrame("登录系统");
Container container = jf.getContentPane();

 在创建窗体后,先调用getContentPane()方法将窗体转换为容器,再调用add()方法或者remove()方法向容器中添加组件或者删除容器中的组件。向容器中添加按钮,关键代码如下:

JButton okBtn = new JButton("确定")
container.add(okBtn);

 删除容器中的按钮,关键代码如下:

container.remove(okBtn);

创建窗体后,要对窗体进行设置,如设置窗体的位置、大小、是否可见等。JFrame类提供的相应方法可实现上述设置操作,具体如下:

  1. setBounds(int x, int y, int width, int leight):设置窗体左上角在屏幕中的坐标为(x, y),窗体的宽度为width,窗体的高度为height。
  2. setLocation(int x, int y):设置窗体左上角在屏幕中的坐标为(x, y)。
  3. setSize(int width, int height):设置窗体的宽度为width,高度为height。
  4. setVisibale(boolean b):设置窗体是否可见。b为true时,表示可见;b为false时,表示不可见。
  5. setDefaultCloseOperation(int operation):设置窗体的关闭方式,默认值为DISPOSE_ON_CLOSE。Java语言提供了多种窗体的关闭方式,常用的有4种,如表18.2所示。

 【例18.1】第一个窗体程序

 18.2.2 JDialog对话框

JDialog对话框继承了java.awt.Dialog类,其功能是从一个窗体中弹出另一个窗体,如使用IE浏览器时弹出的确定对话框。JDialog对话框与JFrame窗体类似,被使用时也需要先调用getContentPane()方法把JDialog对话框转换为容器,再对JDialog对话框进行设置。JDialog类常用的构造方法如下:

  1. public JDialog():创建一个没有标题和父窗体的对话框。
  2. public JDialog(Frame f):创建一个没有标题,但指定父窗体的对话框。
  3. public JDialog(Frame f, boolean model):创建一个没有标题,但指定父窗体和模式的对话框。如果model为true,那么弹出对话框后,用户无法操作父窗体。
  4. public JDialog(Frame f, String title):创建一个指定标题和父窗体的对话框。
  5. public JDialog(Frame f, String title, boolean model):创建一个指定标题、父窗体和模式的对话框。

【例18.2】在窗体中弹出对话框

18.2.3 JOptionPane小型对话框

Java API中的javax.swing.JOptionPane类是一个非常简便的小型对话框类,该类用于创建对话框的方法都是静态方法,无须创建对象即可弹出。在日常开发中经常使用该类弹出提示、确认用户需求、调试程序等。JOptionPane提供了4种创建对话框的方法,如表18.3所示。

 

1.自定义对话框

首先介绍一个自定义的对话框,这个对话框可以说是一块白板,开发者可以自行定义对话框中显示的元素。创建自定义对话框的方法如下:

 

参数说明如下:

  1. parentComponent:指明对话框在哪个窗体上显示,如果传入具体的窗体对象,对话框会在该窗体居中位置显示,如果传入null则在屏幕中间弹出对话框。
  2. message:提示的信息。
  3. title:对话框的标题。
  4. optionType:指定可用于对话框的选项的整数:DEFAULT_OPTION、YES_NO_OPTION、YES_NO_CANCEL_OPTION或OK_CANCEL_OPTION。
  5. messageType:指定消息种类的整数,主要用于确定来自可插入外观的图标:ERROR_MESSAGE、INFORMATION_MESSAGE、WARNING_MESSAGE、QUESTION_MESSAGE或PLAIN_MESSAGE。
  6. icon:在对话框中显示的图标。
  7. options:指示用户可能选择的对象组成的数组。如果对象是组件,则可以正确呈现,非String对象使用其toString方法呈现;如果此参数为null,则由外观确定选项。
  8. initialValue:表示对话框的默认选择的对象,只有在使用options时才有意义,可以为null。

【例18.3】弹出会话框,问用户准备好了吗

 2.确认框

确认框已经封装好了一套外观样式,弹出后要求用户做选择操作,用户选择具体选项后,确认框可以返回用户的选择结果,结果以int方式返回。创建确认对话框的方法有以下几种重载形式:

  • 调出带有选项Yes、No和Cancel的对话框;标题为Select an Option。
static int showConfirmDialog(Component parentComponent, Object message)
  •  调出一个由optionType参数确定其中选项数的对话框。
static int showConfirmDialog(Component parentComponent, Object message, String title, int optionType)
  •  调用一个由optionType参数确定其中选项数的对话框,messageType参数确定要显示的图标。

  •  调出一个带有指定图标的对话框,其中的选项数由optionType参数确定。

【例18.4】弹出会话框,询问用户是否离开

 

此确认框弹出效果,如图18.5所示。如果用户单击“是”按钮,变量answer获得的值为0;如果用户单击“否”按钮,变量answer获得的值为1;如果用户单击“取消”按钮,变量answer获得的值为2。

如果只想弹出“是”和“否”两个按钮,可以使用JOptionPane.YES_NO_OPTION类型,该类型对话框效果如图18.6所示。

3.输入框

输入框已经封装好了一套外观样式,弹出后要求用户在文本框中输入文本,用户完成输入操作后,输入框可以返回用户输入的结果。创建输入框的方法有以下几种重载形式:

显示请求用户输入内容的问题消息对话框,它以parentComponent为父级。

static String showInputDialog(Component parentComponent, Object message)

 显示请求用户输入内容的问题消息对话框,它以parentComponent为父级。

static String showInputDialog(Component parentComponent, Object message, Object initialSelectionValue)

 显示请求用户输入内容的对话框,它以parentComponent为父级,该对话框的标题为title,消息类型为messageType。

static String showInputDialog(Component parentComponent, Object message, String title, int messageType)

 提示用户在可以指定初始选择、可能选择及其他所有选项的模块化的对话框中输入内容。

 显示请求用户输入的问题消息对话框。

static String showInputDialog(Object message)

 显示请求用户输入的问题消息对话框,它带有已初始化为initialSelectionValue的输入值。

static String showInputDialog(Object message, Object initialSelectionValue)

 【例18.5】弹出会话框,让用户输入自己的姓名

此输入框弹出效果如图18.7所示,用户输入姓名后,单击“确定”按钮,变量name获得的值就是输入框中的文本值。如果用户单击“取消”按钮,变量name获得的值为null。

4.通知框

通知框是最简单的一个对话框,仅弹出提示,不会返回任何值。创建通知框方法有以下几种重载形式:

 调出标题为Message的信息消息对话框。

static void showMessageDialog(Component parentComponent, Object message)

 调出对话框,它显示使用由messageType参数确定的默认图标的message。

调出一个显示信息的对话框,为其指定了所有参数。

【例18.6】弹出警告对话框

此通知框弹出效果如图18.8所示,用户单击“确定”按钮后通知框消失。

18.3 常用布局管理器

开发Swing程序时,在容器中使用布局管理器能够设置窗体的布局,进而控制Swing组件的位置和大小。Swing常用的布局管理器为绝对布局管理器、流布局管理器、边界布局管理器和网格布局管理器。

18.3.1 null绝对布局

绝对布局也叫null布局,其特点是硬性指定组件在容器中的位置和大小,组件的位置通过绝对坐标的方式来指定。使用绝对布局首先要使用Container.setLayout(null)方法取消容器的布局管理器,然后再使用Component.setBounds(int x, int y, int width, int height)方法设置每个组件在容器中的位置和大小。

【例18.7】使用绝对布局定位按钮位置和大小

18.3.2 FlowLayout流布局管理器

图18.9 使用绝对布局设置两个按钮在窗体中的位置

流布局(FlowLayout)管理器是Swing中最基本的布局管理器。使用流布局管理器摆放组件时,组件被从左到右摆放。当组件占据了当前行的所有空间时,溢出的组件会被移动到当前行的下一行。默认情况下,行组件的排列方式被指定为居中对齐,但是通过设置可以更改每一行组件的排列方式。

FlowLayout类具有以下常用的构造方法:

  1. public FlowLayout()。
  2. public FlowLayout(int alignment)。
  3. public FlowLayout(int alignment,int horizGap,int vertGap)。

构造方法中的alignment参数表示使用流布局管理器时每一行组件的排列方式,该参数可以被赋予FlowLayout.LEFT、FlowLayout.CENTER或FlowLayout.RIGHT,这3个值的详细说明如表18.4所示。

在public FlowLayout(int alignment, int horizGap, int vertGap)构造方法中,还存在horizGap与vertGap两个参数,这两个参数分别以像素为单位指定组件与组件之间的水平间隔与垂直间隔。

【例18.8】使用流布局排列按钮

 

18.3.3 BorderLayout边界布局管理器

使用Swing创建窗体后,容器默认的布局管理器是边界布局(BorderLayout)管理器,边界布局管理器把容器划分为东、南、西、北、中5个区域,如图18.11所示。

当组件被添加到被设置为边界布局管理器的容器时,需要使用BorderLayout类中的成员变量指定被添加的组件在边界布局管理器中的区域,BorderLayout类中的成员变量及其说明如表18.5所示。

 

说明

如果使用了边界布局管理器,在向容器中添加组件时,如果不指定要把组件添加到哪个区域,那么当前组件会被默认添加到CENTER区域;如果向同一个区域中添加多个组件,那么后放入的组件会覆盖先放入的组件。

 add()方法被用于实现向容器中添加组件的功能,它可以设置组件的摆放位置。add()方法常用的语法格式如下:

public void add(Component comp, Object constraints)
  1. comp:被添加的组件。
  2. constraints:被添加组件的布局约束对象。

【例18.9】使用边界布局排列按钮

 18.3.4 GridLayout网格布局管理器

网格布局(GridLayout)管理器能够把容器划分为网格,组件可以按行、列进行排列。在网格布局管理器中,网格的个数由行数和列数决定,且每个网格的大小都相同。例如,一个两行两列的网格布局管理器能够产生4个大小相等的网格。组件从网格的左上角开始,按照从左到右、从上到下的顺序被添加到网格中,且每个组件都会填满整个网格。改变窗体大小时,组件的大小也会随之改变。

网格布局管理器主要有以下两个常用的构造方法:

  1. public GridLayout(int rows, int columns)。
  2. public GridLayout(int rows, int columns, int horizGap, int vertGap)。

其中,参数rows和columns分别代表网格的行数和列数,这两个参数只允许有一个参数可以为0,被用于表示一行或一列可以排列任意多个组件;参数horizGap和vertGap分别代表网格之间的水平间距和垂直间距。

【例18.10】使用网格布局排列按钮

 运行结果如图18.13所示。当改变窗体的大小时,组件的大小也会随之改变。

使用网格布局的窗体即使变型也不会改变组件排列

18.4 常用面板

在Swing程序设计中,面板是一个容器,被用于容纳其他组件,但面板必须被添加到其他容器中。Swing中常用的面板包括JPanel面板和JScrollPane面板。下面将分别予以讲解。

18.4.1 JPanel面板

JPanel面板继承java.awt.Container类。JPanel面板必须在窗体容器中使用,无法脱离窗体显示。

【例18.11】在一个窗体中显示4种布局风格的面板

18.4.2 JScrollPane滚动面板

JScrollPane面板是带滚动条的面板,被用于在较小的窗体中显示较大篇幅的内容。需要注意的是,JScrollPane滚动面板不能使用布局管理器,且只能容纳一个组件。如果需要向JScrollPane面板中添加多个组件,那么需要先将多个组件添加到JPanel面板,再将JPanel面板添加到JScrollPane滚动面板。

【例18.12】为窗体添加上下滚动条

运行结果如图18.15所示,图中文字内容是作者在编辑器窗口输入的。

18.5 文字标签组件与图标

在Swing程序设计中,标签(JLabel)被用于显示文本、图标等内容。在Swing应用程序的用户界面中,用户能够通过标签上的文本、图标等内容获得相应的提示信息。本节将对Swing标签的用法、如何创建标签和如何在标签上显示文本及图标等内容予以讲解。

18.5.1 JLabel标签

标签(JLabel)的父类是JComponent类。虽然标签不能被添加监听器,但是标签显示的文本、图标等内容可以被指定对齐方式。

通过JLabel类的构造方法,可以创建多种标签,如显示只有文本的标签、只有图标的标签以及同时包含文本和图标的标签等。JLabel类常用的构造方法如下:

  1. public JLabel():创建一个不带图标或文本的标签。
  2. public JLabel(Icon icon):创建一个带图标的标签。
  3. public JLabel(Icon icon, int aligment):创建一个带图标的标签,并设置图标的水平对齐方式。
  4. public JLabel(String text, int aligment):创建一个带文本的标签,并设置文本的水平对齐方式。

public JLabel(String text, Icon icon, int aligment):创建一个带文本和图标的JLabel对象,并设置文本和图标的水平对齐方式。

【例18.13】在窗体中显示文字标签

18.5.2 图标的使用

图18.17 在窗体中显示文字标签

在Swing程序设计中,图标经常被添加到标签、按钮等组件,使用javax.swing.ImageIcon类可以依据现有的图片创建图标。ImageIcon类实现了Icon接口,它有多个构造方法,常用的如下:

  1. public ImageIcon():创建一个ImageIcon对象,创建ImageIcon对象后,使用其调用setImage(Image image)方法设置图片。
  2. public ImageIcon(Image image):依据现有的图片创建图标。
  3. public ImageIcon(URL url):依据现有图片的路径创建图标。

【例18.14】在窗体中演示图标

运行结果: 

注意

java.lang.Class类中的getResource()方法可以获取本类(编译后的class文件)所在的完整路径。

18.6 按钮组件

在Swing程序设计中,按钮是较为常见的组件,被用于触发特定的动作。Swing提供了多种按钮组件:按钮、单选按钮、复选框等,本节将分别对这些按钮组件进行讲解。

18.6.1 JButton按钮

Swing按钮由JButton对象表示,JButton常用的构造方法如下:

  1. public JButton():创建一个不带文本或图标的按钮。
  2. public JButton(String text):创建一个带文本的按钮。
  3. public JButton(Icon icon):创建一个带图标的按钮。
  4. public JButton(String text, Icon icon):创建一个带文本和图标的按钮。

创建JButton对象后,如果要对JButton对象进行设置,那么可以使用JButton类提供的方法。JButton类的常用方法及其说明如表18.6所示:

【例18.15】演示不同效果的按钮

运行结果: 

 

Swing单选按钮由JRadioButton对象表示。在Swing程序设计中,需要把多个单选按钮添加到按钮组,当用户选中某个单选按钮时,按钮组中的其他单选按钮将不能被同时选中。

18.6.2 JRadioButton单选按钮

1.单选按钮

创建JRadioButton对象需要使用JRadioButton类的构造方法。JRadioButton类常用的构造方法如下:

  1. public JRadioButton():创建一个未被选中、文本未被设定的单选按钮。
  2. public JRadioButton(Icon icon):创建一个未被选中、文本未被设定,但具有指定图标的单选按钮。
  3. public JRadioButton(Icon icon, boolean selected):创建一个具有指定图标、选择状态,但文本未被设定的单选按钮。
  4. public JRadioButton(String text):创建一个具有指定文本,但未被选中的单选按钮。
  5. public JRadioButton(String text, Icon icon):创建一个具有指定文本、指定图标,但未被选中的单选按钮。
  6. public JRadioButton(String text, Icon icon, boolean selected):创建一个具有指定的文本、指定图标和选择状态的单选按钮。

根据上述构造方法的相关介绍,不难发现,单选按钮的图标、文本和选择状态等属性能够被同时设定。例如,使用JRadioButton类的构造方法创建一个文本为“选项A”的单选按钮,关键代码如下:

JRadioButton rbtn = new JRadioButton("选项A");
2.按钮组

Swing按钮组由ButtonGroup对象表示,多个单选按钮被添加到按钮组后,能够实现“选项有多个,但只能选中一个”的效果。ButtonGroup对象被创建后,可以使用add()方法把多个单选按钮添加到ButtonGroup对象中。例如,在应用程序窗体中定义一个单选按钮组,代码如下:

【例18.16】性别选择

运行结果如图18.21所示,当选中某一个单选按钮时,另一个单选按钮会取消选中状态。

18.6.3 JCheckBox复选框

复选框组件由JCheckBox对象表示。与单选按钮不同的是,窗体中的复选框可以被选中多个,这是因为每一个复选框都提供“被选中”和“不被选中”两种状态。JCheckBox的常用构造方法如下:

  1. public JCheckBox():创建一个文本、图标未被设定且默认未被选中的复选框。
  2. public JCheckBox(Icon icon, Boolean checked):创建一个具有指定图标、指定初始时是否被选中,但文本未被设定的复选框。

  3. public JCheckBox(String text, Boolean checked):创建一个具有指定文本、指定初始时是否被选中,但图标未被设定的复选框。

【例18.17】打印用户的所选内容

运行结果如图18.22所示,选中第一、二个复选框后。

18.7 列表组件

Swing中提供两种列表组件,分别为下拉列表框(JComboBox)与列表框(JList)。下拉列表框与列表框都是带有一系列列表项的组件,用户可以从中选择需要的列表项。列表框较下拉列表框更直观,它将所有的列表项罗列在列表框中。但是,下拉列表框较列表框更为便捷、美观,它将所有的列表项隐藏起来,当用户选用其中的列表项时才会显现出来。本节将详细讲解列表框与下拉列表框的使用。

18.7.1 JComboBox下拉列表框

初次使用Swing中的下拉列表框时,会感觉到Swing中的下拉列表框与Windows操作系统中的下拉列表框有一些相似,实质上两者并不完全相同,因为Swing中的下拉列表框不仅可以供用户从中选择列表项,也提供编辑列表项的功能。

下拉列表框是一个条状的显示区,它具有下拉功能,在下拉列表框的右侧存在一个倒三角形的按钮,当用户单击该按钮时,下拉列表框中的项目将会以列表形式显示出来。

下拉列表框组件由JComboBox对象表示,JComboBox类是javax.swing.JComponent类的子类。JComboBox类的常用构造方法如下:

  1. public JComboBox(ComboBoxModel dataModel):创建一个JComboBox对象,下拉列表中的列表项使用ComboBoxModel中的列表项,ComboBoxModel是一个用于组合框的数据模型。
  2. public JComboBox(Object[] arrayData):创建一个包含指定数组中的元素的JComboBox对象。
  3. public JComboBox(Vector vector):创建一个包含指定Vector对象中的元素的JComboBox对象。Vector对象中的元素可以通过整数索引进行访问,而且Vector对象中的元素可以根据需求被添加或者移除。

JComboBox类的常用方法及其说明如表18.7所示。

【例18.18】在下拉列表框中显示用户的所选内容

运行结果:

18.7.2 JList列表框

列表框组件被添加到窗体中后,就会被指定长和宽。如果列表框的大小不足以容纳列表项的个数,那么需要设置列表框具有滚动效果,即把列表框添加到滚动面板。用户在选择列表框中的列表项时,既可以通过单击列表项的方式选择列表项,也可以通过“单击列表项+按住Shift键”的方式连续选择列表项,还可以通过“单击列表项+按住Ctrl键”的方式跳跃式选择列表项,并能够在非选择状态和选择状态之间反复切换。列表框组件由JList对象表示,JList类的常用构造方法如下:

  1. public void JList():创建一个空的JList对象。
  2. public void JList(Object[] listData):创建一个显示指定数组中的元素的JList对象。
  3. public void JList(Vector listData):创建一个显示指定Vector中的元素的JList对象。
  4. public void JList(ListModel dataModel):创建一个显示指定的非null模型的元素的JList对象。

例如,使用数组类型的数据作为创建JList对象的参数,关键代码如下:

String[] contents = {"列表1","列表2","列表3","列表4"};
JList jl = new JList(contents);

 使用Vector类型的数据作为创建JList对象的参数,关键代码如下:

Vector contents = new Vector();
JList jl = new JList(contents);
contents.add("列表1");
contents.add("列表2");
contents.add("列表3");
contents.add("列表4");

 【例18.19】在列表框显示用户的所选内容

运行结果:

18.8 文本组件

文本组件在开发Swing程序过程中经常被用到,尤其是文本框组件和密码框组件。使用文本组件可以很轻松地操作单行文字、多行文字、口令字段等文本内容。

18.8.1 JTextField文本框

文本框组件由JTextField对象表示。JTextField类的常用构造方法如下:

  1. public JTextField():创建一个文本未被指定的文本框。
  2. public JTextField(String text):创建一个指定文本的文本框。
  3. public JTextField(int fieldwidth):创建一个指定列宽的文本框。
  4. public JTextField(String text, int fieldwidth):创建一个指定文本和列宽的文本框。
  5. public JTextField(Document docModel, String text, int fieldWidth):创建一个指定文本模型、文本内容和列宽的文本框。

如果要为一个文本未被指定的文本框设置文本内容,那么需要使用setText()方法。setText()方法的语法如下:

public void setText(String t)

其中,t表示文本框要显示的文本内容。

【例18.20】在文本框中显示默认文字并清除

运行结果:

18.8.2 JPasswordField密码框

密码框组件由JPasswordField对象表示,其作用是把用户输入的字符串以某种符号进行加密。JPasswordField类的常用构造方法如下:

  1. public JPasswordField():创建一个文本未被指定的密码框。
  2. public JPasswordFiled(String text):创建一个指定文本的密码框。
  3. public JPasswordField(int fieldwidth):创建一个指定列宽的密码框。
  4. public JPasswordField(String text, int fieldwidth):创建一个指定文本和列宽的密码框。
  5. public JPasswordField(Document docModel, String text, int fieldWidth):创建一个指定文本模型和列宽的密码框。

JPasswordField类提供了setEchoChar()方法,这个方法被用于改变密码框的回显字符。setEchoChar()方法的语法如下:

public void setEchoChar(char c)

其中,c表示密码框要显示的回显字符。

例如,创建JPasswordField对象,并设置密码框的回显字符为“#”。关键代码如下:

JPasswordField jp = new JPasswordField();
jp.setEchoChar('#');                       //设置回显字符

那么,如何获取JPasswordField对象中的字符呢?关键代码如下:

JPasswordField passwordField = new JPasswordField()  //密码框对象
char ch[] = passwordField.getPassword();             //获取密码字符数组
String pwd = new String(ch);                         //将字符数组转换为字符串

18.8.3 JTextArea文本域

文本域组件由JTextArea对象表示,其作用是接受用户的多行文本输入。JTextArea类的常用构造方法如下:

  1. public JTextArea():创建一个文本未被指定的文本域。
  2. public JTextArea(String text):创建一个指定文本的文本域。
  3. public JTextArea(int rows,int columns):创建一个指定行高和列宽,但文本未被指定的文本域。
  4. public JTextArea(Document doc):创建一个指定文档模型的文本域。
  5. public JTextArea(Document doc,String Text,int rows,int columns):创建一个指定文档模型、文本内容以及行高和列宽的文本域。

JTextArea类提供了一个setLineWrap(boolean wrap)方法,这个方法被用于设置文本域中的文本内容是否可以自动换行。如果参数wrap的值为true,那么文本域中的文本内容会自动换行;否则不会自动换行。

此外,JTextArea类还提供了一个append(String str)方法,这个方法被用于向文本域中添加文本内容。

【例18.21】在文本域中显示默认文字

运行结果:

18.9 表格组件

Swing表格由JTable对象表示,其作用是把数据以表格的形式显示给用户。本节将对Swing表格组件进行讲解。

18.9.1 创建表格

JTable类除提供了默认的构造方法外,还提供了被用于显示二维数组中的元素的构造方法,这个构造方法的语法如下:

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

  1.  rowData:存储表格数据的二维数组。
  2. columnNames:存储表格列名的一维数组。

在使用表格时,要先把表格添加到滚动面板,再把滚动面板添加到窗体的相应位置。

【例18.22】创建带滚动条的表格

运行结果: 

 

当窗体的高度变小时,将出现滚动条

18.9.2 DefaultTableModel表格数据模型

Swing使用TableModel接口定义了一个表格模型,AbstractTableModel抽象类实现了TableModel接口的大部分方法,只有以下3个抽象方法没有实现:

  1. public int getRowCount();
  2. public int getColumnCount();
  3. public Object getValueAt(int rowIndex, int columnIndex);

为了实现使用表格模型创建表格的功能,Swing提供了表格模型类,即DefaultTableModel类。DefaultTableModel类继承了AbstractTableModel抽象类且实现了上述3个抽象方法。DefaultTableModel类提供的常用构造方法

表格模型被创建后,使用JTable类的构造方法JTable(TableModel dm)即可创建表格。表格被创建后,还可以使用setRowSorter()方法为表格设置排序器:当单击表格的某一列的列头时,在这一列的列名后将出现▲标记,说明将按升序排列表格中的所有行;当再次单击这一列的列头时,标记将变为,说明按降序排列表格中的所有行。

【例18.23】表格自动排序

运行结果如图

单击名称为B的列头后,将得到

此时B列的数据按升序排列;再次单击名称为B的列头后,将得到效果,此时B列的数据按降序排列

18.9.3 维护表格模型

表格中的数据内容需要予以维护,如使用getValueAt()方法获得表格中某一个单元格的值,使用addRow()方法向表格中添加新的行,使用setValueAt()方法修改表格中某一个单元格的值,使用removeRow()方法从表格中删除指定行等。

注意

当删除表格模型中的指定行时,每删除一行,其后所有行的索引值将相应地减1,所以当连续删除多行时,需要注意对删除行索引的处理。

 【例18.24】对表格内容进行增删改查

 

 运行结果如图

其中,A、B文本框分别用来编辑A、B列中单元格的数据内容。当单击“添加”按钮时,可以将编辑好的数据内容添加到表格;当选中表格的某一行时,在A、B文本框中将分别显示对应列的信息。重新编辑表格中某一个单元格的值后,单击“修改”按钮即可修改被选中的单元格的值;当单击“删除”按钮时,可以删除表格中被选中的行。

18.10事件监听器

前文中一直在讲解组件,这些组件本身并不带有任何功能。例如,在窗体中定义一个按钮,当用户单击该按钮时,虽然按钮可以凹凸显示,但在窗体中并没有实现任何功能。这时需要为按钮添加特定的事件监听器,该监听器负责处理用户单击按钮后实现的功能。本节将着重讲解Swing中常用的3种事件监听器,即动作事件监听器、键盘事件监听器、鼠标事件监听器。

18.10.1 ActionEvent动作事件

动作事件(ActionEvent)监听器是Swing中比较常用的事件监听器,很多组件的动作都会使用它监听,如按钮被单击等。表18.9描述了动作事件监听器的接口与事件源等。

 下面以单击按钮事件为例来说明动作事件监听器,当用户单击按钮时,将触发动作事件。

【例18.25】单击按钮后,修改按钮文本

运行结果: 

 

在本实例中,为按钮设置了动作监听器。由于获取事件监听时需要获取实现ActionListener接口的对象,所以定义了一个内部类jbAction实现ActionListener接口,同时在该内部类中实现了actionPerformed()方法,也就是在actionPerformed()方法中定义当用户单击该按钮后实现怎样的功能。

18.10.2 KeyEvent键盘事件

当向文本框中输入内容时,将发生键盘事件。KeyEvent类负责捕获键盘事件,可以通过为组件添加实现了KeyListener接口的监听器类来处理相应的键盘事件。

KeyListener接口共有3个抽象方法,分别在发生击键事件(按下并释放键)、按键被按下(手指按下键但不松开)和按键被释放(手指从按下的键上松开)时被触发,具体如下:

 

在上述每个抽象方法中,均传入了KeyEvent类的对象。KeyEvent类中比较常用的方法

说明

在KeyEvent类中,以“VK_”开头的静态常量代表各个按键的keyCode,可以通过这些静态常量判断事件中的按键,获得按键的标签。

【例18.26】虚拟键盘

package Package18;

import java.awt.BorderLayout;
import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import java.awt.Color;
import java.awt.Component;

import javax.swing.JButton;
import java.awt.Font;
import javax.swing.SwingConstants;
import javax.swing.border.TitledBorder;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;

import javax.swing.JTextField;

/**
 * 虚拟键盘(键盘的按下与释放)
 */
public class KeyBoard extends JFrame { // 创建“键盘”类继承JFrame
	// 声明窗体中的成员组件
	private JPanel contentPane;
	private JTextField textField;
	private JButton btnQ;
	private JButton btnW;
	private JButton btnE;
	private JButton btnR;
	private JButton btnT;
	private JButton btnY;
	private JButton btnU;
	private JButton btnI;
	private JButton btnO;
	private JButton btnP;
	private JButton btnA;
	private JButton btnS;
	private JButton btnD;
	private JButton btnF;
	private JButton btnG;
	private JButton btnH;
	private JButton btnJ;
	private JButton btnK;
	private JButton btnL;
	private JButton btnZ;
	private JButton btnX;
	private JButton btnC;
	private JButton btnV;
	private JButton btnB;
	private JButton btnN;
	private JButton btnM;
	Color green = Color.GREEN;// 定义Color对象,用来表示按下键的颜色
	Color white = Color.WHITE;// 定义Color对象,用来表示释放键的颜色

	ArrayList<JButton> btns = new ArrayList<JButton>();// 定义一个集合,用来存储所有的按键ID
	// 自定义一个方法,用来将容器中的所有JButton组件添加到集合中

	private void addButtons() {
		for (Component cmp : contentPane.getComponents()) {// 遍历面板中的所有组件
			if (cmp instanceof JButton) {// 判断组件的类型是否为JButton类型
				btns.add((JButton) cmp);// 将JButton组件添加到集合中
			}
		}
	}

	/**
	 * 主方法
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() { // 使得Runnable中的的run()方法在the system EventQueue的指派线程中被调用
			public void run() {
				try {
					KeyBoard frame = new KeyBoard(); // 创建KeyBoard对象
					frame.setVisible(true); // 使frame可视
					frame.addButtons();// 初始化存储所有按键的集合
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * 创建JFrame窗体
	 */
	public KeyBoard() { // KeyBoard的构造方法
		setTitle("\u865A\u62DF\u952E\u76D8\uFF08\u6A21\u62DF\u952E\u76D8\u7684\u6309\u4E0B\u4E0E\u91CA\u653E\uFF09"); // 设置窗体题目
		setResizable(false); // 不可改变窗体宽高
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置窗体关闭的方式
		setBounds(100, 100, 560, 280); // 设置窗体的位置和宽高
		/**
		 * 创建JPanel面板contentPane置于JFrame窗体中,并设置面板的背景色、边距和布局
		 */
		contentPane = new JPanel();
		contentPane.setBackground(Color.WHITE);
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		setContentPane(contentPane);
		contentPane.setLayout(null);
		/**
		 * 创建按钮button置于面板contentPane中,设置按钮的背景色、位置、宽高以及按钮中的字体位置、内容、样式
		 */
		btnQ = new JButton("Q");
		btnQ.setBackground(white);
		btnQ.setVerticalAlignment(SwingConstants.TOP);
		btnQ.setHorizontalAlignment(SwingConstants.LEADING);
		btnQ.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnQ.setBounds(0, 60, 47, 45);
		contentPane.add(btnQ);
		/**
		 * 创建按钮button_2置于面板contentPane中,设置按钮的背景色、位置、宽高以及按钮中的字体位置、内容、样式
		 */
		btnW = new JButton("W");
		btnW.setBackground(white);
		btnW.setVerticalAlignment(SwingConstants.TOP);
		btnW.setHorizontalAlignment(SwingConstants.LEADING);
		btnW.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnW.setBounds(55, 60, 49, 45);
		contentPane.add(btnW);
		/**
		 * 创建按钮button_3置于面板contentPane中,设置按钮的背景色、位置、宽高以及按钮中的字体位置、内容、样式
		 */
		btnE = new JButton("E");
		btnE.setBackground(white);
		btnE.setVerticalAlignment(SwingConstants.TOP);
		btnE.setHorizontalAlignment(SwingConstants.LEADING);
		btnE.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnE.setBounds(110, 60, 45, 45);
		contentPane.add(btnE);
		/**
		 * 创建按钮button_4置于面板contentPane中,设置按钮的背景色、位置、宽高以及按钮中的字体位置、内容、样式
		 */
		btnR = new JButton("R");
		btnR.setBackground(white);
		btnR.setVerticalAlignment(SwingConstants.TOP);
		btnR.setHorizontalAlignment(SwingConstants.LEADING);
		btnR.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnR.setBounds(165, 60, 45, 45);
		contentPane.add(btnR);
		/**
		 * 创建按钮button_5置于面板contentPane中,设置按钮的背景色、位置、宽高以及按钮中的字体位置、内容、样式
		 */
		btnF = new JButton("F");
		btnF.setBackground(white);
		btnF.setVerticalAlignment(SwingConstants.TOP);
		btnF.setHorizontalAlignment(SwingConstants.LEADING);
		btnF.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnF.setBounds(195, 125, 45, 45);
		contentPane.add(btnF);
		/**
		 * 创建按钮button_6置于面板contentPane中,设置按钮的背景色、位置、宽高以及按钮中的字体位置、内容、样式
		 */
		btnD = new JButton("D");
		btnD.setBackground(white);
		btnD.setVerticalAlignment(SwingConstants.TOP);
		btnD.setHorizontalAlignment(SwingConstants.LEADING);
		btnD.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnD.setBounds(137, 125, 45, 45);
		contentPane.add(btnD);

		btnT = new JButton("T");
		btnT.setVerticalAlignment(SwingConstants.TOP);
		btnT.setHorizontalAlignment(SwingConstants.LEADING);
		btnT.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnT.setBackground(white);
		btnT.setBounds(220, 60, 45, 45);
		contentPane.add(btnT);

		btnY = new JButton("Y");
		btnY.setVerticalAlignment(SwingConstants.TOP);
		btnY.setHorizontalAlignment(SwingConstants.LEADING);
		btnY.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnY.setBackground(white);
		btnY.setBounds(275, 60, 45, 45);
		contentPane.add(btnY);

		btnU = new JButton("U");
		btnU.setVerticalAlignment(SwingConstants.TOP);
		btnU.setHorizontalAlignment(SwingConstants.LEADING);
		btnU.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnU.setBackground(white);
		btnU.setBounds(330, 60, 45, 45);
		contentPane.add(btnU);

		btnI = new JButton("I");
		btnI.setVerticalAlignment(SwingConstants.TOP);
		btnI.setHorizontalAlignment(SwingConstants.LEADING);
		btnI.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnI.setBackground(white);
		btnI.setBounds(385, 60, 45, 45);
		contentPane.add(btnI);

		btnO = new JButton("O");
		btnO.setVerticalAlignment(SwingConstants.TOP);
		btnO.setHorizontalAlignment(SwingConstants.LEADING);
		btnO.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnO.setBackground(white);
		btnO.setBounds(440, 60, 46, 45);
		contentPane.add(btnO);

		btnP = new JButton("P");
		btnP.setVerticalAlignment(SwingConstants.TOP);
		btnP.setHorizontalAlignment(SwingConstants.LEADING);
		btnP.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnP.setBackground(white);
		btnP.setBounds(495, 60, 45, 45);
		contentPane.add(btnP);

		btnA = new JButton("A");
		btnA.setVerticalAlignment(SwingConstants.TOP);
		btnA.setHorizontalAlignment(SwingConstants.LEADING);
		btnA.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnA.setBackground(white);
		btnA.setBounds(23, 125, 45, 45);
		contentPane.add(btnA);

		btnS = new JButton("S");
		btnS.setVerticalAlignment(SwingConstants.TOP);
		btnS.setHorizontalAlignment(SwingConstants.LEADING);
		btnS.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnS.setBackground(white);
		btnS.setBounds(82, 125, 45, 45);
		contentPane.add(btnS);

		btnG = new JButton("G");
		btnG.setVerticalAlignment(SwingConstants.TOP);
		btnG.setHorizontalAlignment(SwingConstants.LEADING);
		btnG.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnG.setBackground(white);
		btnG.setBounds(251, 125, 45, 45);
		contentPane.add(btnG);

		btnH = new JButton("H");
		btnH.setVerticalAlignment(SwingConstants.TOP);
		btnH.setHorizontalAlignment(SwingConstants.LEADING);
		btnH.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnH.setBackground(white);
		btnH.setBounds(306, 125, 45, 45);
		contentPane.add(btnH);

		btnJ = new JButton("J");
		btnJ.setVerticalAlignment(SwingConstants.TOP);
		btnJ.setHorizontalAlignment(SwingConstants.LEADING);
		btnJ.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnJ.setBackground(white);
		btnJ.setBounds(361, 125, 45, 45);
		contentPane.add(btnJ);

		btnK = new JButton("K");
		btnK.setVerticalAlignment(SwingConstants.TOP);
		btnK.setHorizontalAlignment(SwingConstants.LEADING);
		btnK.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnK.setBackground(white);
		btnK.setBounds(416, 125, 47, 45);
		contentPane.add(btnK);

		btnL = new JButton("L");
		btnL.setVerticalAlignment(SwingConstants.TOP);
		btnL.setHorizontalAlignment(SwingConstants.LEADING);
		btnL.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnL.setBackground(white);
		btnL.setBounds(471, 125, 45, 45);
		contentPane.add(btnL);

		btnZ = new JButton("Z");
		btnZ.setVerticalAlignment(SwingConstants.TOP);
		btnZ.setHorizontalAlignment(SwingConstants.LEADING);
		btnZ.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnZ.setBackground(white);
		btnZ.setBounds(39, 190, 45, 45);
		contentPane.add(btnZ);

		btnX = new JButton("X");
		btnX.setVerticalAlignment(SwingConstants.TOP);
		btnX.setHorizontalAlignment(SwingConstants.LEADING);
		btnX.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnX.setBackground(white);
		btnX.setBounds(107, 190, 45, 45);
		contentPane.add(btnX);

		btnC = new JButton("C");
		btnC.setVerticalAlignment(SwingConstants.TOP);
		btnC.setHorizontalAlignment(SwingConstants.LEADING);
		btnC.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnC.setBackground(white);
		btnC.setBounds(178, 190, 45, 45);
		contentPane.add(btnC);

		btnV = new JButton("V");
		btnV.setVerticalAlignment(SwingConstants.TOP);
		btnV.setHorizontalAlignment(SwingConstants.LEADING);
		btnV.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnV.setBackground(white);
		btnV.setBounds(250, 190, 45, 45);
		contentPane.add(btnV);

		btnB = new JButton("B");
		btnB.setVerticalAlignment(SwingConstants.TOP);
		btnB.setHorizontalAlignment(SwingConstants.LEADING);
		btnB.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnB.setBackground(white);
		btnB.setBounds(315, 190, 45, 45);
		contentPane.add(btnB);

		btnN = new JButton("N");
		btnN.setVerticalAlignment(SwingConstants.TOP);
		btnN.setHorizontalAlignment(SwingConstants.LEADING);
		btnN.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnN.setBackground(white);
		btnN.setBounds(382, 190, 47, 45);
		contentPane.add(btnN);

		btnM = new JButton("M");
		btnM.setVerticalAlignment(SwingConstants.TOP);
		btnM.setHorizontalAlignment(SwingConstants.LEADING);
		btnM.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnM.setBackground(white);
		btnM.setBounds(449, 190, 48, 45);
		contentPane.add(btnM);
		/**
		 * 创建面板panel置于面板contentPane中,设置面板panel的位置、宽高、TitledBorder、背景色以及布局方式(边界布局)
		 */
		JPanel panel = new JPanel();
		panel.setBorder(new TitledBorder(null, "文本显示区", TitledBorder.LEADING, TitledBorder.TOP, null, null));
		panel.setBackground(Color.WHITE);
		panel.setBounds(0, 0, 540, 45);
		contentPane.add(panel);
		panel.setLayout(new BorderLayout(0, 0));

		/**
		 * 创建文本框textField置于面板panel的中间
		 */
		textField = new JTextField();
		textField.addKeyListener(new KeyAdapter() { // 文本框添加键盘事件的监听
			char word;

			@Override
			public void keyPressed(KeyEvent e) { // 按键被按下时被触发
				word = e.getKeyChar();// 获取按下键表示的字符
				for (int i = 0; i < btns.size(); i++) {// 遍历存储按键ID的ArrayList集合
					// 判断按键是否与遍历到的按键的文本相同
					if (String.valueOf(word).equalsIgnoreCase(btns.get(i).getText())) {
						btns.get(i).setBackground(green);// 将指定按键颜色设置为绿色
					}
				}
			}

			@Override
			public void keyReleased(KeyEvent e) { // 按键被释放时被触发
				word = e.getKeyChar();// 获取释放键表示的字符
				for (int i = 0; i < btns.size(); i++) {// 遍历存储按键ID的ArrayList集合
					// 判断按键是否与遍历到的按键的文本相同
					if (String.valueOf(word).equalsIgnoreCase(btns.get(i).getText())) {
						btns.get(i).setBackground(white);// 将指定按键颜色设置为白色
					}
				}
			}
		});
		panel.add(textField, BorderLayout.CENTER);
		textField.setColumns(10);
	}
}

运行本实例,将鼠标定位到文本框组件中,然后按下键盘上的按键,窗体中的相应按钮会变为绿色,释放按键时,相应按钮变为白色

18.10.3 MouseEvent鼠标事件

所有组件都能发生鼠标事件,MouseEvent类负责捕获鼠标事件,可以通过为组件添加实现了MouseListener接口的监听器类来处理相应的鼠标事件。

MouseListener接口共有5个抽象方法,分别在光标移入或移出组件、鼠标按键被按下或释放和发生单击事件时被触发。所谓单击事件,就是按键被按下并释放。需要注意的是,如果按键是在移出组件之后才被释放,则不会触发单击事件。MouseListener接口的具体定义如下:

 在上述每个抽象方法中,均传入了MouseEvent类的对象。MouseEvent类中比较常用的方法如表18.11所示。

 当需要判断触发此次事件的按键时,可以通过表18.12中的静态常量判断由getButton()方法返回的int型值代表的键。

 【例18.27】捕捉鼠标在窗体中的行为

package Package18;


import java.awt.BorderLayout;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.JFrame;
import javax.swing.JLabel;

public class MouseEventDemo extends JFrame { // 继承窗体类JFrame

	public static void main(String args[]) {
		MouseEventDemo frame = new MouseEventDemo();
		frame.setVisible(true); // 设置窗体可见,默认为不可见
	}

	/**
	 * 判断按下的鼠标键,并输出相应提示
	 * 
	 * @param e 鼠标事件
	 */
	private void mouseOper(MouseEvent e) {
		int i = e.getButton();
		if(i == MouseEvent.BUTTON1)
			System.out.println("按下的是鼠标左键");
		else if(i == MouseEvent.BUTTON2)
			System.out.println("按下的是鼠标滚轮");
		else if(i == MouseEvent.BUTTON2)
			System.out.println("按下的是鼠标右键");
	}

	public MouseEventDemo() {
		super(); // 继承父类的构造方法
		setTitle("鼠标事件示例"); // 设置窗体的标题
		setBounds(100, 100, 500, 375); // 设置窗体的显示位置及大小
		// 设置窗体关闭按钮的动作为退出
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		final JLabel label = new JLabel();
		label.addMouseListener(new MouseListener() {
			public void mouseEntered(MouseEvent e) {
				System.out.println("光标移入组件");
			}
			public void mousePressed(MouseEvent e) {
				System.out.println("鼠标按键被按下");
			}
			public void mouseReleased(MouseEvent e) {
				System.out.println("鼠标按键被释放");
			}
			public void mouseCliked(MouseEvent e) {
				System.out.println("单机了鼠标按键");
				mouseOper(e);
				int clickCount = e.getClickCount();
				System.out.println("单机次数为:"+clickCount+"下");
				
			}
			public void mouseExited(MouseEvent e) {
				System.out.println("光标移出组件");
			}
			@Override
			public void mouseClicked(MouseEvent e) {
				// TODO Auto-generated method stub
				
			}
		});
		getContentPane().add(label, BorderLayout.CENTER);
	}

}

运行结果如图

首先将光标移入窗体,然后单击鼠标左键,接着双击鼠标左键,最后将光标移出窗体,在控制台将得到如图的信息。当双击鼠标时,第一次单击鼠标将触发一次单击事件。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值