JavaGUI技术——Swing概览
前言
本文原载于我的博客,地址:blog.guoziyang.top/archives/16/
写swing也有一段时间了,Java是一个不适合写GUI的程序,GUI语句繁琐又不优雅,界面也没法定制。
Oracle推出的新一代JavaFX(其实也不新了,jdk1.8以上)由于文档缺失,学习曲线陡峭,也不被人看好。
而且我也一直没有成功进入JavaFX,因为XML文件读不懂。
所以还是写一写swing吧。
这里大致的讲解一下swing的一些关键类和方法。
以我最近写的一个Java的项目——数独游戏为例
以上是我写的数独游戏的界面。
万恶之源——JFrame
GUI界面的类继承自JFrame类,而项目的主类建议实例化这个GUI类即可。
JFrame大致控制的整个界面的“壳子”,其它的组件像菜单栏啊,按钮啊什么的都是直接或者间接地添加在JFrame上的。
“壳子”的范围,可见的基本也就是窗口的标题和”关闭“”最大化“”最小化“这三个按钮。
它的初始化任务,比如设置大小啊,设置标题啊,添加其它组件啊,在构造方法里面进行即可。
主要方法讲解
setSize(int width, int length)
设置窗口的长和宽
setBounds(int x, int y, int width, int length)
设置窗口的默认显示位置(x,y)、长和宽
setLocationRelativeTo(null)
将窗口的位置默认设置为居中,和setSize搭配使用即可
setDefaultCloseOperation(int operation)
设置窗口的默认关闭动作,即点击”关闭“红叉叉的动作。operation有一下四种动作
- JFrame.DO_NOTHING_ON_CLOSE //啥也不干
- JFrame.HIDE_ON_CLOSE //隐藏当前窗口
- JFrame.DISPOSE_ON_CLOSE //关闭当前窗口
- JFrame.EXIT_ON_CLOSE //退出JVM,完全退出
一般主窗口的关闭动作设置为EXIT_ON_CLOSE,而次要的弹出界面(提示或者设置界面)设置为HIDE_ON_CLOSE或者DISPOSE_ON_CLOSE。
setResizable(false)
设置窗口不可通过拖动重置大小
setTitle(String title)
设置窗口标题
setContentPane(JPanel contentPane)
设置窗口的主要面板
setVisible(boolean b)
设置窗口是否可见,true为可见
初始化窗口后请在添加完所有组件之后再设置可见,设置不可见时可用于次要窗口的关闭按钮的触发事件
add(Component c)
将组件添加到主窗口上
setJMenuBar(final JMenuBar menubar)
设置默认的菜单栏
必须要做的事情
-
设置大小,setSize()或者setBounds()
-
设置标题,setTitle()
-
设置默认关闭动作,setDefaultCloseOperation()
-
设置可见,setVisible()
-
(推荐)设置主面板,不推荐直接将组件添加到JFrame上
组件的容器——JPanel
容器可以自己写一个类继承JPanel,也可以直接实例化JPanel
自己写一个类多用于重写paint()方法,在面板上画画什么的……
如果没有那种“特殊的需求”,直接实例化JPanel即可
JPanel的好处在于可以选择各种各样的布局,使组件的排列更加有序。
当JPanel被设置为JFrame的主面板时,JPanel就会自适应JFrame的大小,充满JFrame
主要方法讲解
setLayout(LayoutManager mgr)
设置面板的布局方式,常用的布局方式有以下四种
-
BorderLayout,把容器的的布局分为五个位置:CENTER、EAST、WEST、NORTH、SOUTH,对应着上北下南之类的。添加进去的组件会自适应位置的大小。
-
GridLayout,把容器的布局按照表格分割成等长等宽的块。构造方法GridLayout(int row, int col)指定表格的行和列。
-
FlowLayout,流水式,后添加的组件覆盖先添加的。
-
null,啥都没有,最好用,只要指定组件的x,y,width,height,就可以随便移动组件位置。
例子:
myJPanel.setLayout(new GridLayout(9, 9));
非主面板的话建议设置下x,y,长和宽,setSize()或者setLocation()或者setBounds()皆可
必须要做的事情
- 设置布局方式,setLayout()
菜单栏——JMenuBar
菜单栏就是窗口顶端的窄窄的一条……不是显示标题的那个!
JMenuBar是那窄窄的一条,默认什么都没有,例子中的”游戏“啊,”帮助“啊啥的叫JMenu,而JMenu下又有一些可以选择的具体的东西,叫做JMenuItem……
很乱……
总之,设计菜单栏的时候自顶向下设计,实例化以及逐层添加的时候由下而上添加,即先把JMenuItem添加到JMenu,再把JMenu添加到JMenuBar
很乱……
最后在窗口中调用setJMenuBar即可
主要方法讲解
JMenuBar的方法没啥,也就一个add,可以把JMenu添加进去
JMenu(String name)
JMenuItem(String name)
两个都是构造方法,传入的String是默认显示在菜单项上的东西
别的没了……
必须要做的事情
记得要添加!添加!添加!
实例化后不要忘了添加!!!
没了……
按钮——JButton
按钮,就是个按钮,可以按来按去那种。
简介并没啥。
实例化、设置设置之后,就可以添加到容器了
主要方法讲解
JButton(String name)
传入的String是默认显示在按钮上的东西
addActionListener(ActionListener l)
添加鼠标监听事件,后来再讲。
1.8之后可以使用lambda表达式改写
ActionListener多使用匿名内部类
setText(String text)
设置按钮上显示的东西
必须要做的事情
给它一个名字,构造方法中或者setText()设置一个
其实不设置也行,就是啥都没有
单行文本框——JTextField
没啥好说的,就是一个可以写一行文本的文本框。
可以写多行的是JTextArea——多行文本框。
嗯,就是个单行文本框。
对了,对应的还有一个JPasswordField,方法都差不多,就是输入的东西都会变成*而已,获取实际字符串需要用getPassword(),返回字符数组。
主要方法讲解
setText(String text)
设置文本框内的文字
getText()
返回文本框内的字符串
setEnabled(boolean l)
设置是否可编辑,true为可编辑
必须要做的事情
没啥,添加就行。
JTextArea
多行文本框,主要方法和单行一样
有一个问题,就是添加到面板上之后没有滚动框
解决办法:
把文本框添加到JScrollPane中,再给滚动条加一个setViewportView(JTextArea),再把JScrollPane添加到面板中
下拉菜单——JComboBox
初始化时使用一个字符串数组
其它没啥好说的,就是一个下拉菜单而已
主要方法讲解
JComboBox<>(Object[] item)
<>之中是范型,item是一个Object数组,大多情况是字符串数组
getSelectedItem()
返回当前选中的Object
getSelectedIndex()
返回当前选中的Object的序列
一定要做的事
没啥,也就记得用数组初始化它就行
记得添加
事件驱动——ActionListener
这是swing的精髓!
ActionListener是监听默认的鼠标点击事件,大多用于按钮和JMenuItem
在jdk1.8之后lambda表达式的引入大大简化了匿名内部类的使用,也就简化了ActionListener的使用。
ActionListener是一个接口(Interface),如果不使用匿名内部类的话请implements它而不是extends。
需要重写actionPerformed()方法,方法体就是按键之后执行的语句。
举例
举一个简单的例子,一个按钮叫作exampleBtn,点击后控制台输出hello,world。
exampleBtn.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e){
System.out.println("hello,world");
}
});
如果要写一个类的话:
class myActionListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e){
System.out.println("hello,world");
}
}
exampleBtn.addActionListener(new myActionListener());
其中,actionPerformed()方法的参数ActionEvent e是触发该方法的组件,如果该组件是一个按钮,在方法体中就可以这样写来获得触发者的一个实例
JButton example = (JButton)e.getSource();
JDK1.8以上可以使用lamnda表达式,以上可改写为
exampleBtn.addActionListener(e -> System.out.println("hello,world));
不要问我怎么改写,我也不知道,我是用IntelliJ IDEA,里面可以自动改写为lambda表达式。
swing的事件驱动模式
老师让讲一下事件驱动,google了一下……大致说说吧,主要还得听课。
swing的事件处理机制大致分为四步:
1). 注册事件源(组件)的事件监听器
2). 用户操作事件源(组件)产生事件
3). 事件被注册的监听器监听到
4). 监听器对事件进行处理
其中,一二步是用户做的,三四步是由JVM虚拟机执行。
第一步注册监听器就是add一个ActionListener,第二步操作事件源产生事件,也就是用户按下按钮或者注册的其它事件。第三步由JVM虚拟机监听到事件之后,第四步JVM虚拟机调用actionPerformed方法。
大致就这样了,其它我大概也不懂了……
键盘监听——KeyListener
也算是精髓之一了吧……(瞎吹)
用于监听当前的键盘事件并作出反应
addKeyListener的定义如下
public void addKeyListener(KeyListener l)
同ActionListener一样,KeyListener也是个接口
KeyListener中有三个需要实现的方法:
-
keyPressed(KeyEvent e) 按下某个键时调用此方法。
-
keyReleased(KeyEvent e) 释放某个键时调用此方法。
-
keyTyped(KeyEvent e) 键入某个键时调用此方法。
然而我们可能并不需要这么多乱七八糟的方法,我只想按一个键执行一个动作而已……
好在有一个简单的解决方案——KeyAdapter类,只需要重写有用的方法即可。
一般重写KeyPressed()。
举例
examplePanel.addKeyLietener(new KeyAdapter(){
@Override
public void keyPressed(KeyEvent e){
if(e.getKeyChar() == 'c'){
System.out.println("hello,world);
}
}
});
自己新建类的版本就不说了
其中,参数KeyEvent e代表按键的事件(按的是哪个键)。
获取具体键位有三种方法:
- getKeyChar():处理的是比较高层的事件,返回的是每欠敲击键盘后得到的字符(中文输入法下就是汉字)。
- getKeyCode():键盘上每一个按钮都有对应码(Code),可用来查知用户按了什么键,返回当前按钮的数值
- getKeyText():返回与此事件中的键关联的字符。比如getKeyText(e.getKeyCode())就返回你所按下的键盘
一般如果是abc啊123啊什么的就使用getKeyChar()就差不多了,如果是什么空格键回车键之类的,就使用getKeyCode(),具体的键码对照表自己百度。getKeyText()没用过。
大坑
默认的键盘焦点(focus)是落在JFrame上的,所以如果把键盘事件绑定在其它的组件上而不设置的话,就会出现“叫天天不应”之类的奇怪现象。所以一般添加完键盘事件后,调用该组件的requestFocus()方法即可。
画画!——paintComponent
每一个组件都有一个paintCompoent()方法,用于绘制组件的默认外观,我们可以重写此方法来达到在组件上面“乱涂乱画”的效果。
paintComponent()方法定义如下
public void paintComponent(Graphics g)
当重写使,请在第一行首先调用super.paintComponent(g);来重新绘制组件,以达到每次重绘都可以清除上一次画画留下的“糟粕”的效果。
Graphics这个类的功能比较垃圾,也就能设置个线条颜色,连个粗细都设置不了,所以一般会先讲Graphics g强转成为Graphics2D,功能较为强大。
Graphics2D g2 = (Graphics2D)g;
画线举例
class MyJPanel extends JPanel{
public MyJPanel(){
...
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setColor(Color.RED);
g2.setStroke(new Stroke(3));
g2.drawLine(0, 0, 20, 20);
}
}
以上的例子就是将颜色设置为红色,粗细设置为3,在(0, 0)到(20, 20)之间划一条线。
如果组件的外形和某个变量有关,那个变量改变之后想要更新组件,只需调用该组件的repaint()方法即可。
其它什么画圆啊、画矩形啊都有对应的方法,百度或者查API都行。
其它可能比较重要的组件
JList——列表
JTable——表格
这两个在初始化的时候可以使用DefaultListModel和DefaultTableModel作为参数初始化,好处是列表和表格中显示的数据和DefaultListModel或者DefaultTableModel绑定,只需要修改其中的一个即可。这样省去了刷新界面的麻烦。
最后不得不说一句……Thread.stop()真TM好使!!!
虽然是一个已经过时的API,但是!想停就停,绝不含糊!!!
比interrupt()效率不只高到哪里去了。
也许这就是为什么,stop()早已过时,但一直没有被删除的原因吧……
(但是一些涉及重要数据的线程就不要用这个方法了,无关紧要的像计时啊之类的用用还是很爽的)
谨以此blog献给我的小傻