Java用户界面工具包简史:
Java1.1,包含了一个用于基本GUI程序设计的类库,名为抽象窗口工具包(Abstract Window Toolkit,AWT)基本AWT库将处理用户界面元素的任务委托给各个目标平台上的原生GUI工具包,由原生GUI工具包负责用户界面元素的创建和行为。
但不同平台难以获得一致性体验,且不同平台的AWT用户界面库中存在着不同的bug“一次编写,到处调试”
1996年,Netscape创建了 IFC(Internet Foundation Class)GUI库:采取与AWT完全不同的工作方式,将按钮,菜单等用户界面元素绘制在空白窗口上,底层窗口系统所需唯一功能就是能够显示一个窗口,并在窗口中绘制。
Sun与Netscape合作完善了这种方法,创建了Swing用户界面库(不基于对等元素的GUI工具包,建立在AWT架构之上,使用AWT的基本机制(特别是事件处理),提供更为强大的用户界面组件)
显示窗体
顶层窗口(没有包含在其他窗口中的窗体)称为窗体(frame),AWT库中有一个称为Frame的类,用于描述这个顶层窗口。Swing版本的类名为JFrame,扩展了Frame类,它是极少数几个不绘制在画布上的Swing组件之一。因此它的修饰部件(按钮,标题栏等)由用户的窗口系统绘制,而不由Swing绘制。
基本组件:eg:JButton,JTextField
容器:eg:JFrame,JPanel
创建窗体:
显示空白窗体:
默认窗体大小0×0像素(无意义)
这里构造器设置窗体大小为300×200像素,main方法中,构造一个SimpleFrame对象并使它可见。
所有Swing组件必须由事件分配线程配置,这是控制线程,它将鼠标点击和按键等事件传递给用户接口组件。
EventQueue.invokeLater(()->
{
statement
});
定义用户关闭窗体的响应动作。这里只是简单的退出:
(在包含多个窗体的程序中,不能在用户关闭其中一个窗体时就让程序退出。默认情况下,用户关闭窗体只是将窗体隐藏起来)
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
仅仅构造窗体,窗体是不会自动显示的,窗体起初不可见,给程序员在窗体第一次显示之前向其中添加组件的机会,为了显示窗体,main方法中需要调用窗体的setVisible方法
frame.setVisible(true);
初始化语句结束后,main方法退出,退出main方法并没有终止程序,终止的只是主线程,事件分派线程会保持程序处于激活状态,直到关闭窗口或调用System.exit方法终止程序。
窗体属性:
JFrame类中一些处理窗口的方法:
- setLocation方法和setBounds方法用于设置窗体的位置
- setIconImage方法用于告诉窗口系统在标题栏,任务切换窗口等位置显示哪个图标。
- setTitle方法用于改变标题栏的文字
- setResizable利用一个boolean值确定是否允许用户改变窗体大小
如图示:
需要在Component类(所有GUI对象的祖先)和window类(Frame类的父类)中寻找调整窗口大小和位置的方法。
如:Component类中有setLocation方法:重新定位组件——setLocation(x,y)窗口左上角要位于水平向右x像素,垂直向下y像素位置,坐标(0,0)是屏幕左上方位置。setBound(x,y,width,heigth)调整组件大小和位置。
组件类很多方法以获取/设置方法对形式出现,如:
public String getTitle()
public void setTitle(String title)
这样一对获取/设置方法被称为属性,属性有一个名和一个类型,如Frame类有一个名为title且类型为String的属性。
特别的:对于类型为boolean的属性,获取方法以is开头,如
public boolean isResizable()
public void setResizable(boolean resizable)
要确定适当的窗体大小,首先要得出屏幕的大小,调用Toolkit类的静态方法getDefault-Toolkit得到一个Toolkit对象(Toolkit类相当于一个基地,包含大量与原生窗口系统交互的方法),然后调用getScreenSize方法,这个方法以Dimension对象形式返回屏幕大小,Dimension对象同时用公共实例变量width,heigth保存屏幕的宽度和高度,使用屏幕大小的一个适当百分数指定窗口大小。如图示
在组件中显示信息
其中根窗口,层级窗口和玻璃窗格人们并不太关心,它们用来组织菜单栏和内容窗格以及实现观感,Swing程序员最关心的是内容窗格,添加到窗口的组件都会自动添加到内容窗格中。
Component c=new ...
frame.add(c);
要在一个组件上绘制,需要定义一个扩展JComponent的类,并覆盖其中的paintComponent方法。
paintComponent方法中有一个Graphics类型的参数,Graphics对象保存着用于绘制图像和文本的一组设置。Java中所有的设置必须通过Graphics对象完成,其中包含了绘制图案,图像和文本的方法。
无论何种原因,只要窗口需要重新绘制,事件处理器就会通知组件,从而引发执行所有组件的paintComponent方法,绝对不要自己调用paintComponent方法。只要应用的某个部分需要重新绘制,就会自动调用这个方法,不要人为干预这个自动处理过程。
触发这个自动响应的动作如:扩大窗口,极小化窗口又恢复窗口大小会引发绘制,其他窗口弹出并覆盖了已有窗口,让上层窗口消失时被覆盖的窗口已被破坏,需要重新绘制(图形系统不保存下层像素)等。
强制重新绘制屏幕:调用repaint方法,此方法将引发采用适当配置的Graphics对象调用所有组件的paintComponent方法。
Graphics对象的度量单位是像素,坐标(0,0)指示所绘制组件的左上角。
处理2D图形
Java1.2版本以来,paintComponent等方法会自动地接收一个Graphics2D类对象,只需要使用一个强制类型转换即可得到2D类对象
Java2D库采用面向对象方式组织几何图形,具体来说,提供了直线,矩形,椭圆类:Line2D,Rectangle2D,Ellipse2D,等多种复杂图形,且都实现了Shape接口。
绘制图形:首先创建一个实现了Shape接口的类的对象,然后调用Graphics2D类的draw方法
Java2D库针对像素采用浮点坐标,而非整数坐标。内部计算都采用单精度float。但考虑到操作float有时不方便(一些方法返回值可能为double,以及常量小数在Java默认为double,而double转换为float必须强制类型转换),因而为每个图形类提供两个版本。
使用double类可以避免处理float类型值,但float类型更节省存储空间
一些图形的绘制
使用颜色
使用graphics2D类的setPaint方法可以为图形上下文上的所有后续的绘制操作选择颜色。
可以用一种颜色填充一个封闭图形的内部,只需要将调用draw替换为调用fill
(fill方法会在右侧和下方少绘制一个像素,如绘制矩形:new Rectangle2D.Double(0,0,10,20),绘制的矩形将包括x=10和y=20的像素,若填充这个矩形则不会绘制x=10和y=20的像素)
设置背景颜色,需要使用Component类中的setBackground方法。
var component =new MyComponent();
component.setBackground(Color.PINK);
还有一个setForeground方法,用来指定在组件上进行绘制时使用的默认颜色。
使用字体
通过字体名指定字体,字体名由字体族名(如:”Helvetica“)和一个可选后缀(如”Bold“)组成。
获得计算机内可用字体:
import java.awt.*;
public class ListFonts
{
public static void main(String[] args)
{
String[] fontNames=GraphicsEnvironment.//该类描述了用户系统的图形上下文
getLocalGraphicsEnvironment().//为了得到上述类的对象,需要调用静态的getLocalGraphicsEnvironment方法
getAvailableFontFamilyNames();//GraphicsEnvironment类的方法:返回一个字符串数组,其中包含了所有可用字体名
for(String fontName:fontNames)
{
System.out.println(fontName);
}
}
}
使用某种字体绘制字符:
var sansbold14=new Font("SansSerif",Font.Bold,14);
//指定字体名,字体风格,字体大小
//字体大小即第三个参数,是以点数目计算的字体大小。(排版中普遍使用点数指示字体大小,每英寸包含72个点)
//字体风格第二个参数,允许:常规(Font.PLAIN),加粗(Font.BOLD),斜体(Font.ITALIC),加粗斜体(Font.BOLD+Font.ITALIC)
常规字体大小为1点,可以使用deriveFont方法得到所需大小字体
deriveFont方法有两个重载版本:
float参数:设置字体大小(fa.deriveFont(14.0F))
int参数:设置字体风格( fa.deriveFont(14)结果为斜体,14的二进制表示的是ITALIC位为1,BOLD位为0)
若想让字符串居中显示:需要得到一个表示屏幕设备字体属性的对象,需要调用Graphics2D中的getFontRenderContext方法,将返回一个FontRenderContext类的对象,将这个对象传递给Font类的getStringBounds方法,此方法将返回包围字符串的矩形。
显示图像
使用ImageIcon类从文件中读取图像
Image image=new ImageIcon(filename).getImage();
变量image包含了一个封装了图像数据的对象的引用,可以使用Graphics类的drawImage方法显示这个图像
public void paintComponent(Graphics g)
{
g.drawImage(image,x,y.null);
}
平铺显示:
for(int i=0;i*imageWidth<=getWidth();++i)
for(int j=0;j*imageHeight<=getHeight();++j)
{
if(i+j>0) g.copyArea(0,0,imageWidth,imageHeight,i*imageWidth,j*imageHeight);
}
事件处理
任何支持GUI的操作环境都要不断地监视按键或点击鼠标这样的事件,事件再报告给正在运行的程序,每个程序将决定如何对这些事件做出响应。
基本概念
在JavaAWT中,事件源(如按钮或滚动条等)有一些方法,允许注册事件监听器,这些对象会对事件做出所需的响应。通知一个事件监听器发生了某个事件时,这个事件的相关信息会封装在一个事件对象中。Java中,所有事件对象最终派生于java.util.EventObject类,不同的事件源可以产生不同类型的事件。
只要按钮产生了一个动作事件”按钮点击“,listener对象就会得到通知,要实现ActionListener接口,监听器类必须有一个名为actionPerformed方法,该方法接收一个ActionEvent对象作为参数。
点击按钮->JButton对象创建一个ActionEvent对象->调用所有监听器的listener.actionPerformed(event),并传入事件对象
一个示例:点击按钮修改面板的背景颜色
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ButtonFrame extends JFrame {
private JPanel buttonPanel;
private static final int DEFAULT_WIDTH=300;
private static final int DEFAULT_HEIGHT=200;
public ButtonFrame()
{
setSize(DEFAULT_WIDTH,DEFAULT_WIDTH);
//create buttons
var yellowButton =new JButton("Yellow");
var blueBuuton=new JButton("Blue");
var redButton=new JButton("Red");
buttonPanel=new JPanel();
buttonPanel.add(yellowButton);
buttonPanel.add(blueBuuton);
buttonPanel.add(redButton);
add(buttonPanel);//add panel to frame
//create button actions
var yellowAction=new ColorAction(Color.YELLOW);
var blueAction=new ColorAction(Color.BLUE);
var redAction=new ColorAction(Color.RED);
//associate actions with buttons
yellowButton.addActionListener(yellowAction);
blueBuuton.addActionListener(blueAction);
redButton.addActionListener(redAction);
}
private class ColorAction implements ActionListener
{
private Color backgroundColor;
public ColorAction(Color c)
{
backgroundColor=c;
}
public void actionPerformed(ActionEvent event)
{
buttonPanel.setBackground(backgroundColor);
}
}
}
AWT事件继承层次
- Java中所有事件的父类都是java.util.EventObject
- AWT事件类的父类为java.util.AWTEvent,这是EventObject的直接子类
- 如果某事件监听器接口有多个方法,而实际只对其中某个或几个方法感兴趣,可使用相对应的适配器类
AWT将事件分为底层事件和语义事件,语义事件是指用户动作的事件:如点击按钮,因此ActionEvent是一种语义事件。底层事件是使语义事件得以发生的事件:点击按钮时包括了按下鼠标,移动鼠标,和松开鼠标。调节滚动条是一种于语义事件,但拖动鼠标是底层事件。
常见语义事件类型
- ActionEvent(按钮点击,菜单选择,选择列表项,在文本域中键入enter)
- AdjustmentEvent(用户调节滚动条)
- MouseEvent(鼠标键被按下,释放或拖动)
- FocusEvent(某个组件获得焦点或失去焦点)
- ItemEvent(用户从复选框或列表项中选择一项)
- KeyEvent(一个键被按下或释放)
- MouseWheelEvent(鼠标滚轮被转动)
- WindowEvent(窗口状态被改变)
- AWT监听器接口,事件和事件源列表P471
对应监听器:~Listener eg:ActionListener
适配器类:
~Adapter eg:MouseAdapter;FocusAdapter
每个含有多个方法的AWT监听器接口都配有一个适配器类,这个类实现了接口中的所有方法,但每个方法并不做任何事情
鼠标事件:
用户点击鼠标按钮时,将会调用三个监听器方法:
- 鼠标第一次被按下时调用mousePressed;
- 鼠标被释放时调用mouseReleased;
- 最后调用mouseClicked
如果只对最终的点击事件感兴趣,可以忽略前两个方法。
以MouseEvent类对象为参数,调用getX(),getY()方法可以获得鼠标被按下时鼠标指针所在的x,y坐标。想要区分单击,双击和三击(!),需要使用getClicked方法。
当鼠标在窗口上移动时,窗口会收到一连串的鼠标移动事件。PS:有两个独立的接口MouseLIstener和MouseMotionListener,这样做有利于提高效率,当用户移动鼠标时,会有大量鼠标事件,只关心鼠标点击的监听器就不会被多余的鼠标移动事件所干扰。
用户在移动鼠标的同时按下鼠标,就会生成mouseDragged调用而不是mouseMoved调用。只有鼠标在一个组件内部停留才会调用mouseMoved方法。
mouseEntered,mouseExited在鼠标进入或移出组件时被调用。