Java学习笔记
AWT编程
AWT菜单
- 前面介绍了创建GUI界面的方式:将AWT组件按某种布局摆放在容器中即可。创建AWT菜单的方式与此完全类似:将菜单条、菜单、菜单项组合在一起即可。
菜单条、菜单和菜单项
- AWT的菜单由如下几个类组合而成
- MenuBar:菜单条、菜单的容器
- Menu:菜单组件,菜单项的容器。它也是MenuItem的子类,所以可作为菜单项使用
- PopupMenu:上下文菜单组件(右键菜单组件)
- MenuItem:菜单项组件
- CheckboxMenuItem:复选框菜单组件
- MenuShortcut:菜单快捷键组件
- Menu、MenuItem的构造器都可接受一个字符串参数,该字符串作为其对应菜单、菜单项上的标签文本。除此之外,MenuItem还可以接受一个MenuShortcut对象,该对象用于指定该菜单的快捷键。MenuShortcut类使用虚拟键代码来创建快捷键。例如,Ctrl+A:
MenuShortcut ms = new MenuShortcut(KeyEvent.VK_A);
- 如果该快捷键还需要Shift键的帮助,则可使用如下代码:
MenuShortcut ms = new MenuShortcut(KeyEvent.VK_A, true);
- 有时候程序还希望对某个菜单进行分组,将功能相似的菜单分成一组,此时需要使用菜单分隔符。AWT中添加菜单分隔符有如下两种用法。
- 调用Menu对象的addSeparator()方法来添加菜单分隔符
- 使用添加new MenuItem("-")的方式来添加菜单分隔符
- 创建了MenuItem、Menu和MenuBar对象之后,调用Menu的add()方法将多个MenuItem组合成菜单(也可将另一个Menu组合进来,形成二级菜单),在调用MenuBar的add()方法将多个Menu组合成菜单条,最后调用Frame对象的setMenuBar()方法为该窗口添加菜单条。
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class SimpleMenu {
private Frame f = new Frame("test");
private MenuBar mb = new MenuBar();
Menu file = new Menu("文件");
Menu edit = new Menu("编辑");
MenuItem newItem = new MenuItem("新建");
MenuItem saveItem = new MenuItem("保存");
//创建exitItem菜单项,指定使用“Ctrl+X”快捷键
MenuItem exitItem = new MenuItem("退出",
new MenuShortcut(KeyEvent.VK_X));
CheckboxMenuItem autoWrap = new CheckboxMenuItem("自动换行");
MenuItem copyItem = new MenuItem("复制");
MenuItem pasteItem = new MenuItem("粘贴");
Menu format = new Menu("格式");
//创建commentItem菜单项,指定使用“Ctrl+Shift+/”快捷键
MenuItem commentItem = new MenuItem("注释",
new MenuShortcut(KeyEvent.VK_SLASH, true));
MenuItem cancelItem = new MenuItem("取消注释");
private TextArea ta = new TextArea(6, 40);
public void init()
{
//以lambda表达式创建菜单事件监听器
ActionListener menuListener = e ->
{
String cmd = e.getActionCommand();
ta.append("单击“"+cmd+"”菜单"+"\n");
if(cmd.equals("退出"))
System.exit(0);
};
//为commentItem菜单项添加事件监听器
commentItem.addActionListener(menuListener);
exitItem.addActionListener(menuListener);
//为file菜单添加菜单项
file.add(newItem);
file.add(saveItem);
file.add(exitItem);
//为edit菜单添加菜单项
edit.add(autoWrap);
//使用addSeparator方法来添加菜单分割线
edit.addSeparator();
edit.add(copyItem);
edit.add(pasteItem);
//为format菜单添加菜单项
format.add(commentItem);
format.add(cancelItem);
//使用添加new MenuItem("-")的方式添加菜单分割线
edit.add(new MenuItem("-"));
edit.add(format);
mb.add(file);
mb.add(edit);
//为f窗口设置菜单条
f.setMenuBar(mb);
//以匿名内部类的方式创建事件监听器对象
f.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.add(ta);
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
new SimpleMenu().init();
}
}
右键菜单
- 右键菜单使用PopupMenu对象表示,创建右键菜单的步骤如下:
- 创建PopupMenu的实例
- 创建多个MenuItem的多个实例,依次将这些实例加入PopupMenu中
- 将PopupMenu加入到目标组件中
- 为需要出现上下文菜单的组件编写鼠标监听器,当用户释放鼠标右键时弹出菜单
- 下面程序创建了一个右键菜单,该右键菜单就是“借用”前面SimpleMenu中的edit菜单下的所有菜单项。
import java.awt.*;
import java.awt.event.*;
public class PopupMenuTest {
private TextArea ta = new TextArea(4, 30);
private Frame f = new Frame("测试");
PopupMenu pop = new PopupMenu();
CheckboxMenuItem autoWrap =
new CheckboxMenuItem("自动换行");
MenuItem copyItem = new MenuItem("复制");
MenuItem pasteItem = new MenuItem("粘贴");
Menu format = new Menu("格式");
MenuItem commentItem = new MenuItem("注释",
new MenuShortcut(KeyEvent.VK_SLASH, true));
MenuItem cancelItem = new MenuItem("取消注释");
public void init()
{
ActionListener menuListener = e ->
{
String cmd = e.getActionCommand();
ta.append("单击“"+cmd+"”菜单"+"\n");
if(cmd.equals("退出"))
{
System.exit(0);
}
};
commentItem.addActionListener(menuListener);
pop.add(autoWrap);
pop.addSeparator();
pop.add(copyItem);
pop.add(pasteItem);
format.add(commentItem);
format.add(cancelItem);
pop.add(new MenuItem("-"));
pop.add(format);
Panel p = new Panel();
p.setPreferredSize(new Dimension(300, 160));
p.add(pop);
p.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
if(e.isPopupTrigger()) {
pop.show(p, e.getX(), e.getY());
}
}
});
f.add(p);
f.add(ta, BorderLayout.NORTH);
f.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
new PopupMenuTest().init();
}
}
在AWT中绘图
- 很多程序如各种小游戏都需要在窗口中绘制各种图形,除此之外,即使在开发Java EE项目时,有时候也必须“动态”的地向客户端生成各种图形、图标,比如图形验证码、统计图等。这都需要利用AWT的绘图功能。
画图的实现原理
- 在Component类里提供了和绘图有关的三个方法
paint(Graphics g):
绘制组件的外观update(Graphics g):
调用paint()方法,刷新组件外观repaint():
调用update()方法,刷新组件外观
- 上面三个方法的调用关系为:repaint()方法调用update()方法,update()方法调用paint()方法。
- Container类中的update()方法先以组件的背景色填充整个组件区域,然后调用paint()方法重画组件,Container类的update()方法代码如下
public void update(Graphics g) {
if (isShowing()) {
//以组件的背景色填充整个组件区域
if (! (peer instanceof LightweightPeer)) {
g.clearRect(0, 0, width, height);
}
paint(g);
}
}
- 普通组件的update()方法则是直接调用paint()方法。
public void update(Graphics g) {
paint(g);
}
- 如果程序希望AWT系统重新绘制组件,调用该组件的repaint()方法即可。而paint()和update()方法通常被重写。在通常情况下,程序通过重写paint()方法实现在AWT组件上绘图。
- 重写update()或paint()方法时,该方法里包含了一个Graphics类型的参数,通过该参数就可以实现绘图功能。
使用Graphics类
- Graphics是一个抽象的画笔对象,Graphics可以在组件上绘制丰富多彩的几何图形和位图。Graphics类提供了如下几个方法用于绘制几何图形和位图。
drawLine():
绘制直线drawString():
绘制字符串drawRect():
绘制矩形drawRoundRect():
绘制圆角矩形drawOval():
椭圆drawPolygon():
多边形边框drawArc():
圆弧drawPolyline():
折线fillRect():
填充矩形fillRoundRect():
填充圆角矩形fillOval():
椭圆fillPolygon():
多边形fillArc():
填充圆弧和其两个端点到中心连线所包围的区域drawImage():
绘制位图
- 除此之外,Graphics还提供饿了setColor()和setFont()两个方法用于设置画笔的颜色和字体(仅当绘制字符串时有效),其中前者需要传入一个Color参数,可以使用RGB、CMYK等方式设置一个颜色;而后者需要传入一个Font参数,Font参数需要指定字体名、字体样式、字体大小三个属性。
AWT普通组件也可以通过Color()和Font()方法改变它的前景色和字体。且所有组件都有一个setBackground()方法用于设置组件的背景色。
- AWT专门提供了一个Canvas类作为绘图的画布,程序可以通过创建Canvas的子类,并重写其paint()方法来实现绘图。
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Random;
public class SimpleDraw {
private final String RECT_SHAPE = "rect";
private final String OVAL_SHAPE = "oval";
private Frame f = new Frame("简单绘图");
private Button rect = new Button("绘制矩形");
private Button oval = new Button("绘制圆形");
private MyCanvas drawArea = new MyCanvas();
private String shape = "";
class MyCanvas extends Canvas
{
//重写方法,实现绘画
public void paint(Graphics g)
{
Random rand = new Random();
if (shape.equals(RECT_SHAPE))
{
//设置画笔颜色
g.setColor(new Color(rand.nextInt(255), rand.nextInt(255), rand.nextInt(255)));
//随机地绘制一个矩形框
g.drawRect(rand.nextInt(200),
rand.nextInt(120), 40, 60);
}
if (shape.equals(OVAL_SHAPE))
{
//设置画笔颜色
g.setColor(new Color(rand.nextInt(255), rand.nextInt(255), rand.nextInt(255)));
g.fillOval(rand.nextInt(200),
rand.nextInt(120), 50, 40);
}
}
}
public void init()
{
Panel p = new Panel();
rect.addActionListener(e ->
{
shape = RECT_SHAPE;
drawArea.repaint();
});
oval.addActionListener(e ->
{
shape = OVAL_SHAPE;
drawArea.repaint();
});
p.add(rect);
p.add(oval);
drawArea.setPreferredSize(new Dimension(250, 180));
f.add(drawArea);
f.add(p, BorderLayout.SOUTH);
f.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
new SimpleDraw().init();
}
}
- Java也可用于开发一些动画。所谓动画,就是间隔一定时间(通常小于0.1秒)重新绘制新的图像,两次绘制的图像之间差异较小,肉眼看上去就成了所谓的动画。为了实现间隔一定的时间就重新调用组件的repaint()方法,可以借助于Swing提供的Timer类,Timer类是一个定时器,它有如下一个构造器:
Timer(int delay, ActionListener listener):
每间隔delay毫秒,系统自动触发ActionListener监听器里的事件处理器(actionPerformed()方法)
- 下面程序示范了一个简单地弹球游戏,其中小球和球拍分别以圆形区域和矩形区域代替,小球开始以随机速度向下运动,遇到边框或球拍时小球反弹;球拍则由用户控制,当用户按下向左、向右键时,球拍将会向左、向右移动。
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
public class PinBall {
//桌面的宽度
private final int TABLE_WIDTH = 300;
//桌面高度
private final int TABLE_HEIGHT = 400;
//球拍的垂直位置
private final int RACKET_Y = 340;
//定义球拍的宽度、高度
private final int RACKET_HEIGHT = 20;
private final int RACKET_WIDTH = 60;
//小球大小
private final int BALL_SIZE = 16;
private Frame f = new Frame("love you~");
Random rand = new Random();
//小球纵向运行速度
private int ySpeed = 10;
//返回一个-0.5~0.5的比率,用于控制小球的运行方向
private double xyRate = rand.nextDouble() - 0.5;
//小球横向运行速度
private int xSpeed = (int)(ySpeed*xyRate*2);
//ballX和ballY代表小球的坐标
private int ballX = rand.nextInt(200) + 20;
private int ballY = rand.nextInt(10) + 20;
//racketX代表球拍的水平位置
private int racketX = rand.nextInt(200);
private MyCanvas tableArea = new MyCanvas();
Timer timer;
private int score = 0;
//游戏是否结束的旗标
private boolean isLose = false;
public void init()
{
//设置桌面区域的最佳大小
tableArea.setPreferredSize(new Dimension(TABLE_WIDTH, TABLE_HEIGHT));
f.add(tableArea);
//定义键盘监听器
KeyAdapter keyProcessor = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_LEFT)
{
if(racketX > 0)
racketX -= 10;
}
if (e.getKeyCode() == KeyEvent.VK_RIGHT)
{
if(racketX < TABLE_WIDTH-RACKET_WIDTH)
racketX += 10;
}
}
};
//增加“×”关闭
f.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
//为窗口和tableArea对象分别添加键盘监听器
f.addKeyListener(keyProcessor);
tableArea.addKeyListener(keyProcessor);
//定义每0.1秒执行一次的事件监听器
ActionListener taskPerformer = evt ->
{
//如果小球碰到左右边框
if(ballX <= 0 || ballX >= TABLE_WIDTH-BALL_SIZE)
xSpeed=-xSpeed;
//如果小球高度超出了球拍的位置,且横向不在球拍范围之内,则游戏结束
if (ballY >= RACKET_Y -BALL_SIZE &&(ballX<racketX || ballX>racketX + RACKET_WIDTH))
{
timer.stop();
isLose = true;
tableArea.repaint();
}
//如果小球位于球拍之内, 且到达球拍位置,小球反弹
else if (ballY <= 0 || (ballY >= RACKET_Y- BALL_SIZE && ballX> racketX&&ballX <=racketX +RACKET_WIDTH))
{
ySpeed=-ySpeed;
}
//小球坐标变化
ballY += ySpeed;
ballX += xSpeed;
tableArea.repaint();
score ++;
};
timer = new Timer(100, taskPerformer);
timer.start();
f.pack();
f.setVisible(true);
}
class MyCanvas extends Canvas
{
//重写paint方法
public void paint(Graphics g)
{
if (isLose) {
g.setColor(new Color(255,0,0));
g.setFont(new Font("Times", Font.BOLD, 25));
g.drawString("游戏结束!\n你的得分是"+score , 50, 200 );
}
else
{
g.setColor(new Color(222, 118, 222, 255));
g.fillOval(ballX,ballY,BALL_SIZE,BALL_SIZE);
//设置颜色,并绘制球拍
g.setColor(new Color(80, 80, 200, 200));
g.fillRect(racketX, RACKET_Y, RACKET_WIDTH, RACKET_HEIGHT);
}
}
}
public static void main(String[] args) {
new PinBall().init();
}
}
- 运行时会有轻微的闪烁,这是因为AWT组件的绘图没有采用双缓冲技术,当重写paint()方法来绘制图形时,所有图形都是直接绘制在GUI组件上的,所以多次重新调用paint()方法进行绘制会发生闪烁现象。使用Swing组件就可避免这种闪烁,Swing组件没有提供Canvas对应的组件,使用Swing的Panel组件作为画布即可。
处理位图
- 如果仅仅绘制一些简单的几何图形,程序的图形效果依然比较单调。AWT也允许在组件上绘制位图,Graphics提供了drawImage方法用于控制位图,该方法需要一个Image参数——代表位图,通过该方法就可以绘制出指定的位图
Image抽象类和BufferedImage实现类
- Image类代表位图,但他是一个抽象类,无法直接创建Image对象,为此Java为他提供了一个BufferedImage子类,这个子类是一个可访问图像数据缓冲区的Image实现类。该类提供了一个简单的构造器,用于创建一个BufferedImage对象。
BufferedImage(int width, int height, int imageType):
创建指定大小、指定图像类型的BufferedImage对象,其中imageType可以使BufferedImage.TYPE_INT_RGB、BufferedImage.TYPE_BYTE_GRAY等值。
- 除此之外,BufferedImage还提供了一个getGraphics()方法返回该对象的Graphics对象,从而允许通过改Graphics对象向Image中添加图形
- 借助BufferedImage可以在AWT中实现缓冲技术——当需要向GUI组件上绘制图形时,不要直接绘制到该GUI组件上,而是先将图形绘制到BufferedImage对象中,然后再调用组件的drawImage一次性地将BufferedImage对象绘制在特定组件上。
- 下面程序通过BufferedImage类实现了图形缓冲,并实现了一个简单的手绘程序。
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
public class HandDraw {
//画图区的宽度
private final int AREA_WIDTH = 500;
//画图区高度
private final int AREA_HEIGHT = 400;
//下面的preX、preY保存了上一次鼠标拖动事件的鼠标坐标
private int preX = -1;
private int preY = -1;
//定义一个右键菜单用于设置画笔颜色
PopupMenu pop = new PopupMenu();
MenuItem redItem = new MenuItem("red");
MenuItem greenItem = new MenuItem("green");
MenuItem blueItem = new MenuItem("blue");
//定义一个BufferedImage对象
BufferedImage image = new BufferedImage(AREA_WIDTH, AREA_HEIGHT, BufferedImage.TYPE_INT_RGB);
//获取image对象的Graphics
Graphics g = image.getGraphics();
private Frame f = new Frame("简单手绘程序");
private DrawCanvas drawArea = new DrawCanvas();
//用于保存画笔颜色
private Color foreColor = new Color(255, 0, 0);
public void init()
{
ActionListener menuListener = e->
{
if(e.getActionCommand().equals("green"))
foreColor = new Color(0, 255, 0);
if(e.getActionCommand().equals("red"))
foreColor = new Color(255, 0, 0);
if(e.getActionCommand().equals("blue"))
foreColor = new Color(0, 0, 255);
};
//为三个菜单添加事件监听器
redItem.addActionListener(menuListener);
blueItem.addActionListener(menuListener);
greenItem.addActionListener(menuListener);
//将菜单组合成右键菜单
pop.add(redItem);
pop.add(greenItem);
pop.add(blueItem);
//将右键菜单添加到drawArea对象中
drawArea.add(pop);
//将Image对象的背景色填充为白色
g.fillRect(0, 0, AREA_WIDTH, AREA_HEIGHT);
drawArea.setPreferredSize(new Dimension(AREA_WIDTH, AREA_HEIGHT));
//监听鼠标移动动作
drawArea.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
if(preX > 0 && preY > 0)
{
g.setColor(foreColor);
g.drawLine(preX, preY, e.getX(), e.getY());
}
preX = e.getX();
preY = e.getY();
drawArea.repaint();
}
});
drawArea.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger())
{
pop.show(drawArea, e.getX(), e.getY());
}
preX = -1;
preY = -1;
}
});
f.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.add(drawArea);
f.pack();
f.setVisible(true);
}
class DrawCanvas extends Canvas
{
//重写paint
@Override
public void paint(Graphics g)
{
g.drawImage(image, 0, 0, null);
}
}
public static void main(String[] args) {
new HandDraw().init();
}
}
Java 9增强的ImageIO
- 如果希望可以访问磁盘上的位图文件,例如GIF、JPG等格式的位图,则需要利用ImageIO工具类。ImageIO利用ImageReader和ImageWriter读写图形文件,通常程序无需关心该类底层细节,只需要利用该工具类来读写图形文件即可。
- ImageIO类并不支持读写全部格式的图形文件,程序可以通过ImageIO类的如下几个静态方法来访问该类所支持读写的图形文件格式
static String[] getReaderFileSuffixes():
返回一个String数组,该数组列出ImageIO所有能读的图形文件的文件后缀static String[] getReaderFormatNames():
返回一个String数组,该数组列出ImageIO所有能读的图形文件的非正式格式名称static String[] getWriterFileSuffixes():
返回一个String数组,该数组列出ImageIO所有能写的图形文件的文件后缀static String[] getWriterFormatNames():
返回一个String数组,该数组列出ImageIO所有能写的图形文件的非正式格式名称。
import javax.imageio.ImageIO;
public class ImageIOTest {
public static void main(String[] args) {
String[] readFormat = ImageIO.getReaderFormatNames();
for (String tmp:readFormat) {
System.out.println(tmp);
}
System.out.println("--------");
String[] writeFormat = ImageIO.getWriterFormatNames();
for (String tmp:writeFormat) {
System.out.println(tmp);
}
}
}
- 输出结果如下:
lancibe@lancibe-PC:~/java/test/src/chapter11/src$ java ImageIOTest
JPG
jpg
tiff
bmp
BMP
gif
GIF
WBMP
png
PNG
JPEG
tif
TIF
TIFF
wbmp
jpeg
--------
JPG
jpg
tiff
bmp
BMP
gif
GIF
WBMP
png
PNG
JPEG
tif
TIF
TIFF
jpeg
wbmp
- ImageIO类包含两个静态方法,read()和write(),通过这两个方法即可完成对位图的读写,调用write()方法输出图形文件时需要指定输出的图像格式,例如GIF、JPEG等。下面程序可以将一个原始位图缩小成另一个位图后输出。
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
public class ZoomImage {
private final int WIDTH = 80;
private final int HEIGHT = 60;
//定义一个BufferedImage对象,用于保存缩小之后的位图
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
public void zoom()
throws Exception
{
Image srcImage = ImageIO.read(new File("image/board.jpg"));
g.drawImage(srcImage, 0, 0, WIDTH, HEIGHT, null);
ImageIO.write(image, "jpeg", new File(System.currentTimeMillis() + ".jpg"));
}
public static void main(String[] args)
throws Exception
{
new ZoomImage().zoom();
}
}
- 利用ImageIO读取磁盘上的位图,然后将这些位图绘制在AWT组件上,就可以做出更加丰富多彩的图形界面程序。
剪切板
- 当进行复制、剪切、粘贴等简单操作时,它们的实现过程是一个看似简单的过程:复制、剪切把一个程序中的数据放置到剪贴板中,而粘贴则是读取剪贴板中的内容数据,并将该数据放入另一个程序中。
- 剪贴板的复制、剪切、粘贴过程看似简单,但实现起来则存在一些具体问题——假设从一个文字处理程序中复制文本,然后将这段文本复制到另一个文字处理程序中,肯定希望该文字能够保持原来的风格,也就是说,剪贴板中必须保留文字原来的格式信息;如果只是将文字复制到纯文本域中,则可以无需包含文字原来的格式信息。除此之外,可能还希望将图像等其他对象复制到剪贴板中。为了处理这种复杂的剪贴板操作,数据提供者(复制、剪切内容的源程序)允许使用多种格式的剪贴板数据,而数据的使用者(粘贴内容的目标程序)则可以从多种格式中选择所需的格式。
- AWT支持两种剪贴板:本地剪贴板和系统剪贴板。如果在同一个虚拟机的不同窗口之间进行数据传递,则使用AWT自己的本地剪贴板就可以了。本地剪贴板与运行平台无关,可以传输任意格式的数据,如果需要在不同的虚拟机之间传递数据,或者需要在Java程序与第三方程序之间传递数据,那就要使用系统剪贴板了。
数据传递的类和接口
- AWT中剪贴板相关操作的接口和类被放在java.awt.datatransfer包下,下面是该包下重要的接口和类的相关说明。
- Clipboard:代表一个剪贴板实例,这个剪贴板既可以是本地剪贴板也可以是系统剪贴板
- ClipboardOwner:剪贴板内容的所有者接口,当剪贴板内容的所有权被修改时,系统将会触发该所有者的lostOwnership事件处理器
- Transferable:该接口的实例代表放进剪贴板中的传输对象
- DataFlaver:用于表述剪贴板中的数据格式
- StringSelection:Transferable的实现类,用于传输文本字符串。
- FlavorListener:数据格式监听器接口
- FlavorEvent:该类的实例封装了数据格式改变的事件。
传递文本
-
传递文本是最简单的情形,因为AWT以及提供了一个StringSelection用于传输文本字符串。将一段文本内容(字符串对象)放进剪贴板的步骤如下:
-
创建一个Clipboard实例,创建系统剪贴板如下:
Clipboard clipboard = Toolkit.getDefaultToolkit.getSystemClipboard();
创建本地剪贴板如下:
Clipboard clipboard = new Clipboard();
-
将需要放入剪贴板中的字符串封装成StringSelection对象:
StringSelection st = new StringSelection(targetStr);
-
调用剪贴板对象的setContents()方法将StringSelection对象放入剪切板中,该方法需要两个参数,第一个时Transferable对象,第二个是ClipboardOwner对象,代表剪贴板数据的所有者,通常我们无须关心剪贴数据的所有者,设为null。
clipboard.setContents(st, null);
-
-
从剪贴板中取出数据则比较简单,调用Clipboard对象的getData(DataFlavor flavor)方法即可取出剪贴板中指定格式的内容,如果指定flavor的数据不存在,该方法将引发UnsupportedFlavorException异常。为避免出现异常,应先使用Clipboard对象的isDataFlavorAvailable(DataFlavor flavor)方法作为if的判据来判断指定的flavor的数据是否存在。
if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor))
String content = (String)clipboard.getData(DataFlavor.stringFlavor)
- 下面程序利用系统剪贴板进行复制、粘贴的简单程序。
import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
public class SimpleClipboard {
private Frame f = new Frame("test");
private Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
private TextArea jtaCopyTo = new TextArea(5, 20);
private TextArea jtaPaste = new TextArea(5, 20);
private Button btCopy = new Button("复制");
private Button btPaste = new Button("粘贴");
public void init()
{
Panel p = new Panel();
p.add(btCopy);
p.add(btPaste);
btCopy.addActionListener(event ->
{
//将一个多行文本域里的字符串封装成StringSelection对象
StringSelection contents = new StringSelection(jtaCopyTo.getText());
//将该对象放入剪贴板
clipboard.setContents(contents, null);
});
btPaste.addActionListener(e ->
{
if(clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor))
{
try
{
String content = (String)clipboard.getData(DataFlavor.stringFlavor);
jtaPaste.append(content);
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
});
//创建一个水平排列的Box容器
Box box = new Box(BoxLayout.X_AXIS);
//将两个多行文本域放在Box容器中
box.add(jtaCopyTo);
box.add(jtaPaste);
//将按钮所在的Panel、Box容器添加到Frame窗口中
f.add(p, BorderLayout.SOUTH);
f.add(box,BorderLayout.CENTER);
f.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
new SimpleClipboard().init();
}
}
使用系统剪贴板传递图像
-
前面介绍过,Transferable接口代表可以放入剪贴板的传输对象,所以如果希望将图像反复剪贴板内,则必须提供一个Transferable接口的实现类。该实现类其实很简单,他封装了一个image对象,并且向外表现为imageFlavor内容。
-
JDK为Transferable接口仅提供了一个StringSelection实现类,用于封装字符串内容。但JDK在DataFlavor类中提供了一个imageFlavor常亮,用于代表图像格式的DataFlavor,并负责执行所有的复杂操作,以便进行Java图像和剪贴板图像的转换。
-
下面程序实现了一个ImageSelection类,该类实现了Transferable接口,并实现了该接口所包含的三个方法。
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
public class ImageSelection implements Transferable {
private Image image;
//构造器,负责持有一个Image对象
public ImageSelection(Image image)
{
this.image = image;
}
//返回该Transferable对象所持有的所有DataFlavor
@Override
public DataFlavor[] getTransferDataFlavors()
{
return new DataFlavor[]{
DataFlavor.imageFlavor};
}
//取出该Transferable对象里实际的数据
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
if(flavor.equals(DataFlavor.imageFlavor))
return image;
else
throw new UnsupportedFlavorException(flavor);
}
//返回该Transferable对象是否支持指定的DataFlavor
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.equals(DataFlavor.imageFlavor);
}
}
- 有了ImageSelection封装类之后,程序就可以将指定的Image对象包装成ImageSelection对象放入剪贴板中。下面程序对前面的HandDraw程序进行了改进,改进后的程序允许将用户手绘的图像复制到剪贴板中,也可以把剪贴板中的图像粘贴到该程序中。
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
public class CopyImage {
//系统剪贴板
private Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
//使用ArrayList数据结构来保存所有粘贴进来的Image——就是当成图层处理
List<Image> imageList = new ArrayList<Image>();
//画图区的宽度
private final int AREA_WIDTH = 500;
//画图区高度
private final int AREA_HEIGHT = 400;
//下面的preX、preY保存了上一次鼠标拖动事件的鼠标坐标
private int preX = -1;
private int preY = -1;
//定义一个右键菜单用于设置画笔颜色
PopupMenu pop = new PopupMenu();
MenuItem redItem = new MenuItem("red");
MenuItem greenItem = new MenuItem("green");
MenuItem blueItem = new MenuItem("blue");
//定义一个BufferedImage对象
BufferedImage image = new BufferedImage(AREA_WIDTH, AREA_HEIGHT, BufferedImage.TYPE_INT_RGB);
//获取image对象的Graphics
Graphics g = image.getGraphics();
private Frame f = new Frame("简单手绘程序");
private DrawCanvas drawArea = new DrawCanvas();
//用于保存画笔颜色
private Color foreColor = new Color(255, 0, 0);
public void init()
{
ActionListener menuListener = (e->
{
if(e.getActionCommand().equals("green"))
foreColor = new Color(0, 255, 0);
if(e.getActionCommand().equals("red"))
foreColor = new Color(255, 0, 0);
if(e.getActionCommand().equals("blue"))
foreColor = new Color(0, 0, 255);
});
//为三个菜单添加事件监听器
redItem.addActionListener(menuListener);
blueItem.addActionListener(menuListener);
greenItem.addActionListener(menuListener);
//将菜单组合成右键菜单
pop.add(redItem);
pop.add(greenItem);
pop.add(blueItem);
//将右键菜单添加到drawArea对象中
drawArea.add(pop);
//将Image对象的背景色填充为白色
g.fillRect(0, 0, AREA_WIDTH, AREA_HEIGHT);
drawArea.setPreferredSize(new Dimension(AREA_WIDTH, AREA_HEIGHT));
//监听鼠标移动动作
drawArea.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
if(preX > 0 && preY > 0)
{
g.setColor(foreColor);
g.drawLine(preX, preY, e.getX(), e.getY());
}
preX = e.getX();
preY = e.getY();
drawArea.repaint();
}
});
drawArea.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger())
{
pop.show(drawArea, e.getX(), e.getY());
}
preX = -1;
preY = -1;
}
});
f.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.add(drawArea);
Panel p = new Panel();
Button copy = new Button("复制");
Button paste = new Button("粘贴");
copy.addActionListener(e -> {
//将image对象封装成ImageSelection对象
ImageSelection contents = new ImageSelection(image);
//将ImageSelection对象放入剪贴板
clipboard.setContents(contents, null);
});
paste.addActionListener(e -> {
//如果剪贴板中包含imageFlavor内容
if (clipboard.isDataFlavorAvailable(DataFlavor.imageFlavor))
{
try
{
//取出剪贴板中的imageFlavor内容,并将其添加到List集合中
imageList.add((Image)clipboard.getData(DataFlavor.imageFlavor));
drawArea.repaint();
}
catch (Exception exception)
{
exception.printStackTrace();
}
}
});
p.add(copy);
p.add(paste);
f.add(p, BorderLayout.SOUTH);
f.pack();
f.setVisible(true);
}
class DrawCanvas extends Canvas
{
@Override
public void paint(Graphics g) {
g.drawImage(image, 0, 0, null);
for(Image img:imageList)
g.drawImage(img, 0, 0, null);
}
}
public static void main(String[] args) {
new CopyImage().init();
}
}
通过系统剪贴板传递Java对象
- 系统剪贴板不仅支持传输文本、图像的基本内容,而且支持传输序列化的Java对象和远程对象,复制到剪贴板中的序列化的Java对象和远程对象可以使用另一个Java程序(不在同一个虚拟机内的程序)来读取。DataFlavor中提供了javaSerializedObjectMimeType、javaRemoteObjectMimeType两个字符串常量来表示序列化的Java对象和远程对象的MIME类型,这两种MIME,类型提供了复制对象、读取对象所包含的复杂操作,程序只需创建对应的Transferable实现类即可。
- 下面程序实现了一个SerialSelection类,该类与前面的ImageSelection、LocalObjectSelection实现类相似,都需要实现Transferable接口,实现该接口的三个方法,并持有一个可序列化对象。
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.io.Serializable;
public class SerialSelection implements Transferable {
//持有一个可序列化对象
private Serializable obj;
//创建该类对象时,传入被持有的对象
public SerialSelection(Serializable obj)
{
this.obj = obj;
}
@Override
public DataFlavor[] getTransferDataFlavors() {
DataFlavor[] flavors = new DataFlavor[2];
//获取被封装对象的类型
Class clazz = obj.getClass();
try
{
flavors[0] = new DataFlavor(DataFlavor.javaSerializedObjectMimeType + ";class="+clazz.getName());
flavors[1] = DataFlavor.stringFlavor;
return flavors;
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
return null;
}
}
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
if(!isDataFlavorSupported((flavor)))
{
throw new UnsupportedFlavorException(flavor);
}
if(flavor.equals(DataFlavor.stringFlavor))
{
return obj.toString();
}
return obj;
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.equals(DataFlavor.stringFlavor) || flavor.getPrimaryType().equals("application")
&& flavor.getSubType().equals("x-java-serialized-object")
&& flavor.getRepresentationClass().isAssignableFrom(obj.getClass());
}
}
- 上面程序也创建了一个DataFlavor对象,该对象使用的MIME类型为
"application/x-java-serialized-object;class="+clazz.getName()
,他表示封装可序列化的Java对象的数据格式 - 有了上面的SerialSelection类后,程序就可以把一个可序列化的对象封装成SerialSelection对象,并将该对象放入系统剪贴板中,另一个Java程序也可以从系统剪贴板中读取该对象。下面是复制、读取Dog对象的程序。
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.Serializable;
public class CopySerializable {
Frame f = new Frame("复制对象");
Button copy = new Button("复制");
Button paste = new Button("粘贴");
TextField name = new TextField(15);
TextField age = new TextField(15);
TextArea ta = new TextArea(3, 30);
//创建系统剪贴板
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
public void init()
{
Panel p = new Panel();
p.add(new Label("姓名"));
p.add(name);
p.add(new Label("年龄"));
p.add(age);
f.add(p, BorderLayout.NORTH);
f.add(ta);
Panel bp = new Panel();
copy.addActionListener(e -> copyDog());
paste.addActionListener(e ->
{
try
{
readDog();
}
catch (Exception ee)
{
ee.printStackTrace();
}
});
bp.add(copy);
bp.add(paste);
f.add(bp, BorderLayout.SOUTH);
f.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.pack();
f.setVisible(true);
}
public void copyDog()
{
Dog d = new Dog(name.getText(), Integer.parseInt(age.getText()));
SerialSelection ls = new SerialSelection(d);
clipboard.setContents(ls, null);
}
public void readDog()
{
DataFlavor peronFlavor = new DataFlavor(DataFlavor.javaSerializedObjectMimeType + ";class=Dog");
if(clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor))
{
Dog d = (Dog)clipboard.getData(peronFlavor);
ta.setText(d.toString());
}
}
public static void main(String[] args) {
new CopySerializable().init();
}
}
拖放功能
- 拖放是常见的操作,可以完成复制、剪切功能,但这种复制剪切操作无需剪贴板支持,程序将数据从拖放源直接传递给拖放目标。这种通过拖放实现的复制、剪切效果也被称为复制、移动。
- 拖放操作还可以与三种键组合使用,用以完成特殊功能。
- 与Ctrl键组合使用:表示该拖放操作完成复制功能
- 与Shift键组合使用:表示该拖放操作完成移动功能
- 与Ctrl、Shift键组合使用:表示为目标对象建立快捷方式。(在UNIX平台上称为链接)
- 在拖放操作中,数据从拖放源直接传递给拖放目标,因此拖放操作主要涉及两个对象:拖放源和拖放目标。AWT已经提供了对拖放源和拖放目标的支持,分别由DragSource和DropTarget两个类来表示。
拖放目标
- 在GUI界面中创建拖放目标非常简单,AWT提供的DropTarget类来表示拖放目标,可以表示该类提供的如下构造器来创建一个拖放目标。
DropTarget(Component c, int ops, DropTargetListener dtl):
将c组件创建成一个拖放目标,该拖放目标默认可接受ops值所指定的拖放操作。其中DropTargetListener是拖放操作的关键,他负责对拖放操作作出相应的响应。ops可接受如下几个值- DnDconstants.ACTION_COPY:表示复制
- DnDconstants.ACTION_COPY_OR_MOVE:表示复制或移动
- DnDconstants.ACTION_LINK:表示建立快捷方式
- DnDconstants.ACTION_MOVE:表示移动
- DnDconstants.ACTION_NONE:表示无任何操作
- 例如,下面代码将一个JFrame对象创建成拖放目标
//将当前窗口创建成拖放目标
new DropTarget(jf, DnDconstants.ACTION_COPY, new ImageDropTargetListener());
- 正如上面代码提到的,创建拖放目标时需要传入一个DropTargetListener监听器,该监听器负责处理用户的拖放动作。该监听器包含如下五种事件处理器。
dragEnter(DropTargetDragEvent dtde):
当光标进入拖放目标时触发DropTargetListener监听器的该方法dragExit(DropTargetEvent dtde):
当光标移出拖放目标时将触发DropTargetListener监听器的该方法dragOver(DropTargetEvent dtde):
当光标在拖放目标上移动时将触发DropTargetListener监听器的该方法drop(DropTargetDropEvent dtde):
当用户在拖放目标上松开鼠标键时,拖放结束时将触发DropTargetListener监听器的该方法dropActionChanged(DropTargetDragEvent dtde):
当用户在拖放目标上改变了拖放操作,例如按下或松开Ctrl等辅助键时将触发DropTargetListener监听器的该方法。
- 通常程序不想为上面每个方法提供相应,即不想重写DropTargetListener的每个方法,我们还可以通过继承DropTargetAdapter适配器来创建拖放监听器。下面程序利用拖放目标创建了一个简单的图片浏览工具,当用户把一个或多个图片文件拖入该窗口时,该窗口将会自动打开每个图片文件。
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.*;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class DropTargetTest {
final int DESKTOP_WIDTH = 480;
final int DESKTOP_HEIGHT = 360;
final int FRAME_DISTANCE = 30;
JFrame jf = new JFrame("测试拖放目标——把目标文件拖入该窗口");
//定义一个虚拟桌面
private JDesktopPane desktop = new JDesktopPane();
//保存下一个内部窗口的坐标点
private int nextFrameX;
private int nextFrameY;
//定义内部窗口为虚拟桌面的1/2大小
private int width = DESKTOP_WIDTH / 2;
private int height = DESKTOP_HEIGHT / 2;
public void init()
{
desktop.setPreferredSize(new Dimension(DESKTOP_WIDTH, DESKTOP_HEIGHT));
//将当前窗口创建成拖放目标
new DropTarget(jf, DnDConstants.ACTION_COPY, new ImageDropTargetListener());
jf.add(desktop);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
class ImageDropTargetListener extends DropTargetAdapter
{
@Override
public void drop(DropTargetDropEvent event) {
//接收复制操作
event.acceptDrop(DnDConstants.ACTION_COPY);
//获取拖放的内容
Transferable transferable = event.getTransferable();
DataFlavor[] flavors = transferable.getTransferDataFlavors();
//便利拖放内容里的所有数据格式
for(int i = 0 ; i < flavors.length ; i++)
{
DataFlavor d = flavors[i];
try
{
//如果拖放内容的数据格式是文件列表
if(d.equals(DataFlavor.javaFileListFlavor))
{
//取出拖放操作里的文件列表
List fileList = (List)transferable.getTransferData(d);
for(Object f : fileList)
{
//显示每一个文件
showImage((File)f, event);
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
//强制拖放操作结束,停止阻塞拖放目标
event.dropComplete(true);
}
}
//显示每个文件的工具方法
private void showImage(File f, DropTargetDropEvent event)
throws IOException
{
Image image = ImageIO.read(f);
if(image == null)
{
//强制拖放操作结束,停止阻塞拖放目标
event.dropComplete(true);
JOptionPane.showInternalMessageDialog(desktop, "系统不支持该类型文件");
//方法返回,不会继续操作
return;
}
ImageIcon icon = new ImageIcon(image);
//创建内部窗口并显示图片
JInternalFrame iframe = new JInternalFrame(f.getName(), true, true, true, true);
JLabel imageLabel = new JLabel(icon);
iframe.add(new JScrollPane(imageLabel));
desktop.add(iframe);
//设置内部窗口的原始位置(内部窗口默认大小0*0,放在0,0位置)
iframe.reshape(nextFrameX, nextFrameY, width, height);
//使该窗口可见
iframe.show();
//计算下一个窗口内部位置
nextFrameX += FRAME_DISTANCE;
nextFrameY += FRAME_DISTANCE;
if(nextFrameX + width > desktop.getWidth())
nextFrameX = 0;
if(nextFrameY + height > desktop.getHeight())
nextFrameY = 0;
}
}
public static void main(String[] args) {
new DropTargetTest().init();
}
}