第12章:拖放与剪贴板 - 跨应用的交互
作者:步子哥 (steper@foxmail.com)
12.1 剪贴板(Clipboard)
文本的复制与粘贴
// 创建剪贴板
Clipboard clipboard = new Clipboard(display);
// 复制文本
String text = "Hello, SWT!";
TextTransfer textTransfer = TextTransfer.getInstance();
clipboard.setContents(new Object[]{text}, new Transfer[]{textTransfer});
// 粘贴文本
TextTransfer textTransfer = TextTransfer.getInstance();
String text = (String) clipboard.getContents(textTransfer);
if (text != null) {
System.out.println("剪贴板文本:" + text);
}
// 释放剪贴板
clipboard.dispose();
费曼解释:剪贴板的原理
剪贴板 = 临时笔记本
- 你在笔记本上写下一些东西(复制文本)
- 你可以把笔记本的内容抄到其他地方(粘贴文本)
- 用完后,笔记本归还(释放剪贴板)
类比:
剪贴板 = 复印店
- 你把文件给复印店(复制文本)
- 复印店把文件复印出来
- 你可以拿复印件去别的地方(粘贴文本)
- 用完后,复印件可以丢弃(释放剪贴板)
图片的复制与粘贴
// 创建剪贴板
Clipboard clipboard = new Clipboard(display);
// 复制图片
Image image = new Image(display, "path/to/image.png");
ImageTransfer imageTransfer = ImageTransfer.getInstance();
clipboard.setContents(new Object[]{image.getImageData()}, new Transfer[]{imageTransfer});
image.dispose();
// 粘贴图片
ImageTransfer imageTransfer = ImageTransfer.getInstance();
ImageData imageData = (ImageData) clipboard.getContents(imageTransfer);
if (imageData != null) {
Image image = new Image(display, imageData);
// 使用图片...
image.dispose();
}
// 释放剪贴板
clipboard.dispose();
费曼解释:图片剪贴板的工作原理
图片剪贴板 = 照片贴纸
- 你拍了一张照片(复制图片)
- 把照片贴在贴纸上(剪贴板)
- 你可以把贴纸贴到其他地方(粘贴图片)
- 用完后,贴纸可以丢弃(释放剪贴板)
类比:
图片剪贴板 = 电子相册
- 你拍了一张照片(复制图片)
- 把照片上传到相册(剪贴板)
- 你可以从相册下载照片到其他地方(粘贴图片)
- 用完后,相册的照片可以删除(释放剪贴板)
自定义数据类型
// 创建自定义数据类型
class CustomDataTransfer extends ByteArrayTransfer {
private static final String TYPE_NAME = "custom_data";
private static final int TYPE_ID = registerType(TYPE_NAME);
private static CustomDataTransfer instance = new CustomDataTransfer();
private CustomDataTransfer() {}
public static CustomDataTransfer getInstance() {
return instance;
}
@Override
protected int[] getTypeIds() {
return new int[]{TYPE_ID};
}
@Override
protected String[] getTypeNames() {
return new String[]{TYPE_NAME};
}
@Override
protected void javaToNative(Object object, TransferData transferData) {
byte[] bytes = object.toString().getBytes();
super.javaToNative(bytes, transferData);
}
@Override
protected Object nativeToJava(TransferData transferData) {
byte[] bytes = (byte[]) super.nativeToJava(transferData);
return new String(bytes);
}
}
// 使用自定义数据类型
Clipboard clipboard = new Clipboard(display);
// 复制自定义数据
CustomDataTransfer customTransfer = CustomDataTransfer.getInstance();
String customData = "这是自定义数据";
clipboard.setContents(new Object[]{customData}, new Transfer[]{customTransfer});
// 粘贴自定义数据
CustomDataTransfer customTransfer = CustomDataTransfer.getInstance();
String customData = (String) clipboard.getContents(customTransfer);
if (customData != null) {
System.out.println("自定义数据:" + customData);
}
// 释放剪贴板
clipboard.dispose();
费曼解释:自定义数据类型的作用
自定义数据类型 = 自定义笔记本
- 普通笔记本(文本剪贴板):只能写字
- 自定义笔记本(自定义数据类型):可以写字、画图、贴照片...
- 你需要什么类型,就创建什么类型的笔记本
类比:
自定义数据类型 = 自定义快递包装
- 普通快递包装(文本剪贴板):只能装文件
- 自定义快递包装(自定义数据类型):可以装文件、食品、电子产品...
- 你需要装什么,就创建什么类型的包装
代码示例:剪贴板
public class ClipboardExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("剪贴板示例");
shell.setLayout(new GridLayout(2, false));
// 文本区域 1
Label label1 = new Label(shell, SWT.NONE);
label1.setText("文本 1:");
Text text1 = new Text(shell, SWT.BORDER | SWT.MULTI);
GridData textData1 = new GridData();
textData1.horizontalAlignment = GridData.FILL;
textData1.verticalAlignment = GridData.FILL;
textData1.grabExcessHorizontalSpace = true;
textData1.grabExcessVerticalSpace = true;
textData1.heightHint = 100;
text1.setLayoutData(textData1);
text1.setText("这是文本 1。\n你可以复制这段文本。");
// 文本区域 2
Label label2 = new Label(shell, SWT.NONE);
label2.setText("文本 2:");
Text text2 = new Text(shell, SWT.BORDER | SWT.MULTI);
GridData textData2 = new GridData();
textData2.horizontalAlignment = GridData.FILL;
textData2.verticalAlignment = GridData.FILL;
textData2.grabExcessHorizontalSpace = true;
textData2.grabExcessVerticalSpace = true;
textData2.heightHint = 100;
text2.setLayoutData(textData2);
// 按钮面板
Composite buttonPanel = new Composite(shell, SWT.NONE);
buttonPanel.setLayout(new FillLayout(SWT.HORIZONTAL));
GridData buttonData = new GridData();
buttonData.horizontalSpan = 2;
buttonData.horizontalAlignment = GridData.FILL;
buttonPanel.setLayoutData(buttonData);
// 复制按钮
Button copyButton = new Button(buttonPanel, SWT.PUSH);
copyButton.setText("复制文本 1");
copyButton.addListener(SWT.Selection, event -> {
Clipboard clipboard = new Clipboard(display);
try {
String text = text1.getText();
TextTransfer textTransfer = TextTransfer.getInstance();
clipboard.setContents(new Object[]{text}, new Transfer[]{textTransfer});
System.out.println("已复制到剪贴板");
} finally {
clipboard.dispose();
}
});
// 粘贴按钮
Button pasteButton = new Button(buttonPanel, SWT.PUSH);
pasteButton.setText("粘贴到文本 2");
pasteButton.addListener(SWT.Selection, event -> {
Clipboard clipboard = new Clipboard(display);
try {
TextTransfer textTransfer = TextTransfer.getInstance();
String text = (String) clipboard.getContents(textTransfer);
if (text != null) {
text2.setText(text);
System.out.println("已从剪贴板粘贴");
} else {
System.out.println("剪贴板为空");
}
} finally {
clipboard.dispose();
}
});
// 清空剪贴板按钮
Button clearButton = new Button(buttonPanel, SWT.PUSH);
clearButton.setText("清空剪贴板");
clearButton.addListener(SWT.Selection, event -> {
Clipboard clipboard = new Clipboard(display);
try {
clipboard.setContents(new Object[0], new Transfer[0]);
System.out.println("已清空剪贴板");
} finally {
clipboard.dispose();
}
});
// 查看剪贴板按钮
Button viewButton = new Button(buttonPanel, SWT.PUSH);
viewButton.setText("查看剪贴板");
viewButton.addListener(SWT.Selection, event -> {
Clipboard clipboard = new Clipboard(display);
try {
TextTransfer textTransfer = TextTransfer.getInstance();
String text = (String) clipboard.getContents(textTransfer);
if (text != null) {
System.out.println("剪贴板内容:" + text);
} else {
System.out.println("剪贴板为空");
}
} finally {
clipboard.dispose();
}
});
shell.setBounds(100, 100, 600, 300);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
12.2 拖放(DnD)基础
DragSource 与 DropTarget
// 拖放源:文本框
Text sourceText = new Text(shell, SWT.BORDER);
sourceText.setText("拖动我!");
DragSource dragSource = new DragSource(sourceText, DND.DROP_MOVE | DND.DROP_COPY);
dragSource.setTransfer(new Transfer[]{TextTransfer.getInstance()});
dragSource.addDragListener(new DragSourceAdapter() {
@Override
public void dragSetData(DragSourceEvent event) {
if (TextTransfer.getInstance().isSupportedType(event.dataType)) {
event.data = sourceText.getText();
}
}
@Override
public void dragStart(DragSourceEvent event) {
if (sourceText.getText().isEmpty()) {
event.doit = false; // 文本为空,不允许拖动
}
}
});
// 拖放目标:文本框
Text targetText = new Text(shell, SWT.BORDER | SWT.MULTI);
DropTarget dropTarget = new DropTarget(targetText, DND.DROP_MOVE | DND.DROP_COPY);
dropTarget.setTransfer(new Transfer[]{TextTransfer.getInstance()});
dropTarget.addDropListener(new DropTargetAdapter() {
@Override
public void dragEnter(DropTargetEvent event) {
if (event.detail == DND.DROP_NONE) {
event.detail = DND.DROP_COPY;
}
}
@Override
public void drop(DropTargetEvent event) {
if (TextTransfer.getInstance().isSupportedType(event.currentDataType)) {
String text = (String) event.data;
targetText.setText(text);
}
}
});
费曼解释:DragSource 和 DropTarget 的含义
DragSource = 发货方
- 货物(数据)
- 发货人(DragSource)
- 把货物打包(dragSetData)
- 发货(拖动)
DropTarget = 收货方
- 货物(数据)
- 收货人(DropTarget)
- 接收货物(drop)
- 卸货(获取数据)
类比:
DragSource = 递纸条
- 你写了一张纸条(数据)
- 你是发送方(DragSource)
- 你把纸条递给同学(拖动)
DropTarget = 接纸条
- 同学接了你的纸条(数据)
- 同学是接收方(DropTarget)
- 同学阅读纸条(drop)
拖放的类型:文本、文件、自定义数据
// 文本拖放
TextTransfer textTransfer = TextTransfer.getInstance();
dragSource.setTransfer(new Transfer[]{textTransfer});
dropTarget.setTransfer(new Transfer[]{textTransfer});
// 文件拖放
FileTransfer fileTransfer = FileTransfer.getInstance();
dragSource.setTransfer(new Transfer[]{fileTransfer});
dropTarget.setTransfer(new Transfer[]{fileTransfer});
// 自定义数据拖放
CustomDataTransfer customTransfer = CustomDataTransfer.getInstance();
dragSource.setTransfer(new Transfer[]{customTransfer});
dropTarget.setTransfer(new Transfer[]{customTransfer});
// 多种类型拖放
dragSource.setTransfer(new Transfer[]{TextTransfer.getInstance(), FileTransfer.getInstance()});
dropTarget.setTransfer(new Transfer[]{TextTransfer.getInstance(), FileTransfer.getInstance()});
费曼解释:多种拖放类型的作用
多种拖放类型 = 多种快递服务
- 文本快递(TextTransfer):运送文字
- 文件快递(FileTransfer):运送文件
- 自定义快递(CustomDataTransfer):运送自定义数据
类比:
多种拖放类型 = 餐厅的外卖
- 文字外卖(TextTransfer):菜单文字
- 文件外卖(FileTransfer):菜品图片
- 自定义外卖(CustomDataTransfer):特殊菜品
代码示例:文本拖放
public class TextDragDropExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("文本拖放示例");
shell.setLayout(new GridLayout(2, false));
// 拖放源
Label sourceLabel = new Label(shell, SWT.NONE);
sourceLabel.setText("拖放源:");
Text sourceText = new Text(shell, SWT.BORDER);
sourceText.setText("拖动这段文本!");
GridData sourceData = new GridData();
sourceData.horizontalAlignment = GridData.FILL;
sourceText.setLayoutData(sourceData);
// 设置 DragSource
DragSource dragSource = new DragSource(sourceText, DND.DROP_MOVE | DND.DROP_COPY);
dragSource.setTransfer(new Transfer[]{TextTransfer.getInstance()});
dragSource.addDragListener(new DragSourceAdapter() {
@Override
public void dragSetData(DragSourceEvent event) {
if (TextTransfer.getInstance().isSupportedType(event.dataType)) {
event.data = sourceText.getText();
}
}
@Override
public void dragStart(DragSourceEvent event) {
if (sourceText.getText().isEmpty()) {
event.doit = false;
}
}
});
// 拖放目标
Label targetLabel = new Label(shell, SWT.NONE);
targetLabel.setText("拖放目标:");
Text targetText = new Text(shell, SWT.BORDER | SWT.MULTI);
GridData targetData = new GridData();
targetData.horizontalAlignment = GridData.FILL;
targetData.verticalAlignment = GridData.FILL;
targetData.grabExcessHorizontalSpace = true;
targetData.grabExcessVerticalSpace = true;
targetData.heightHint = 100;
targetText.setLayoutData(targetData);
// 设置 DropTarget
DropTarget dropTarget = new DropTarget(targetText, DND.DROP_MOVE | DND.DROP_COPY);
dropTarget.setTransfer(new Transfer[]{TextTransfer.getInstance()});
dropTarget.addDropListener(new DropTargetAdapter() {
@Override
public void dragEnter(DropTargetEvent event) {
if (event.detail == DND.DROP_NONE) {
event.detail = DND.DROP_COPY;
}
}
@Override
public void drop(DropTargetEvent event) {
if (TextTransfer.getInstance().isSupportedType(event.currentDataType)) {
String text = (String) event.data;
targetText.setText(text);
System.out.println("拖放成功:" + text);
}
}
});
// 清空按钮
Button clearButton = new Button(shell, SWT.PUSH);
clearButton.setText("清空目标");
clearButton.addListener(SWT.Selection, event -> {
targetText.setText("");
});
shell.setBounds(100, 100, 500, 200);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
12.3 拖放的事件监听
DragStartEvent
dragSource.addDragListener(new DragSourceAdapter() {
@Override
public void dragStart(DragSourceEvent event) {
System.out.println("拖动开始");
// 检查是否允许拖动
if (!allowDrag()) {
event.doit = false; // 不允许拖动
}
}
});
费曼解释:DragStartEvent 的含义
DragStartEvent = 发货检查
- 你要发货(拖动开始)
- 快递员检查:货物是否允许发货(DragStartEvent)
- 不允许发货 = 检查未通过(event.doit = false)
- 允许发货 = 检查通过(event.doit = true)
类比:
DragStartEvent = 纸条检查
- 你要递纸条(拖动开始)
- 老师检查:纸条内容是否允许传递(DragStartEvent)
- 不允许传递 = 检查未通过(event.doit = false)
- 允许传递 = 检查通过(event.doit = true)
DragEnterEvent、DragOverEvent
dropTarget.addDropListener(new DropTargetAdapter() {
@Override
public void dragEnter(DropTargetEvent event) {
System.out.println("拖动进入目标区域");
// 设置默认操作
if (event.detail == DND.DROP_NONE) {
event.detail = DND.DROP_COPY;
}
}
@Override
public void dragOver(DropTargetEvent event) {
System.out.println("拖动在目标区域上方");
// 可以根据位置改变操作
if (event.y < 50) {
event.detail = DND.DROP_MOVE; // 上半部分:移动
} else {
event.detail = DND.DROP_COPY; // 下半部分:复制
}
}
@Override
public void dragLeave(DropTargetEvent event) {
System.out.println("拖动离开目标区域");
}
});
费曼解释:这些事件的含义
dragEnter = 快递到达目的地
- 快递车到达了目的地(拖动进入目标区域)
- 通知收货人:快递到了(dragEnter)
dragOver = 快递在目的地上方
- 快递车在目的地上方行驶(拖动在目标区域上方)
- 收货人看到快递车,准备接收(dragOver)
dragLeave = 快递离开目的地
- 快递车离开了目的地(拖动离开目标区域)
- 收货人知道:快递走了(dragLeave)
类比:
dragEnter = 纸条到达同学那里
- 你把纸条递给同学(拖动进入目标区域)
- 同学看到纸条:收到了(dragEnter)
dragOver = 纸条在同学上方
- 纸条在同学上方晃动(拖动在目标区域上方)
- 同学准备接纸条(dragOver)
dragLeave = 纸条离开同学那里
- 纸条离开了同学那里(拖动离开目标区域)
- 同学知道:纸条走了(dragLeave)
DropEvent
dropTarget.addDropListener(new DropTargetAdapter() {
@Override
public void drop(DropTargetEvent event) {
System.out.println("放下数据");
// 获取数据
if (TextTransfer.getInstance().isSupportedType(event.currentDataType)) {
String text = (String) event.data;
System.out.println("拖放的数据:" + text);
}
}
@Override
public void dropAccept(DropTargetEvent event) {
System.out.println("接受拖放");
// 检查是否接受拖放
if (!acceptDrop()) {
event.detail = DND.DROP_NONE; // 不接受拖放
}
}
});
费曼解释:DropEvent 的含义
drop = 收货
- 快递员把货物递给收货人(drop)
- 收货人签字确认(dropAccept)
- 收货人检查货物是否接收(dropAccept)
类比:
drop = 接纸条
- 你把纸条递给同学(drop)
- 同学决定是否接受(dropAccept)
- 同学检查纸条内容是否允许接收(dropAccept)
代码示例:拖放事件监听
public class DragDropEventsExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("拖放事件监听");
shell.setLayout(new GridLayout(2, false));
// 拖放源
Text sourceText = new Text(shell, SWT.BORDER);
sourceText.setText("拖动这段文本,查看事件!");
GridData sourceData = new GridData();
sourceData.horizontalAlignment = GridData.FILL;
sourceText.setLayoutData(sourceData);
DragSource dragSource = new DragSource(sourceText, DND.DROP_MOVE | DND.DROP_COPY);
dragSource.setTransfer(new Transfer[]{TextTransfer.getInstance()});
dragSource.addDragListener(new DragSourceAdapter() {
@Override
public void dragStart(DragSourceEvent event) {
System.out.println("dragStart:拖动开始");
if (sourceText.getText().isEmpty()) {
event.doit = false;
System.out.println("dragStart:不允许拖动(文本为空)");
}
}
@Override
public void dragSetData(DragSourceEvent event) {
System.out.println("dragSetData:设置数据");
if (TextTransfer.getInstance().isSupportedType(event.dataType)) {
event.data = sourceText.getText();
System.out.println("dragSetData:数据 = " + sourceText.getText());
}
}
@Override
public void dragFinished(DragSourceEvent event) {
System.out.println("dragFinished:拖动完成");
if (event.detail == DND.DROP_MOVE) {
sourceText.setText(""); // 移动:清空源文本
}
}
});
// 拖放目标
Text targetText = new Text(shell, SWT.BORDER | SWT.MULTI);
GridData targetData = new GridData();
targetData.horizontalAlignment = GridData.FILL;
targetData.verticalAlignment = GridData.FILL;
targetData.grabExcessHorizontalSpace = true;
targetData.grabExcessVerticalSpace = true;
targetData.heightHint = 150;
targetText.setLayoutData(targetData);
DropTarget dropTarget = new DropTarget(targetText, DND.DROP_MOVE | DND.DROP_COPY);
dropTarget.setTransfer(new Transfer[]{TextTransfer.getInstance()});
dropTarget.addDropListener(new DropTargetAdapter() {
@Override
public void dragEnter(DropTargetEvent event) {
System.out.println("dragEnter:拖动进入目标区域");
if (event.detail == DND.DROP_NONE) {
event.detail = DND.DROP_COPY;
}
}
@Override
public void dragOver(DropTargetEvent event) {
System.out.println("dragOver:拖动在目标区域上方,位置 = (" + event.x + ", " + event.y + ")");
}
@Override
public void dragLeave(DropTargetEvent event) {
System.out.println("dragLeave:拖动离开目标区域");
}
@Override
public void dropAccept(DropTargetEvent event) {
System.out.println("dropAccept:接受拖放");
}
@Override
public void drop(DropTargetEvent event) {
System.out.println("drop:放下数据");
if (TextTransfer.getInstance().isSupportedType(event.currentDataType)) {
String text = (String) event.data;
targetText.setText(text);
System.out.println("drop:数据 = " + text);
}
}
});
shell.setBounds(100, 100, 600, 300);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
12.4 实战案例:拖放文件到应用
public class FileDragDropExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("拖放文件到应用");
shell.setLayout(new GridLayout(1, false));
// 拖放目标
Label label = new Label(shell, SWT.WRAP);
label.setText("从文件管理器拖动文件到这里:");
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
Text fileText = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL);
GridData textData = new GridData();
textData.horizontalAlignment = GridData.FILL;
textData.verticalAlignment = GridData.FILL;
textData.grabExcessHorizontalSpace = true;
textData.grabExcessVerticalSpace = true;
textData.heightHint = 200;
fileText.setLayoutData(textData);
// 设置 DropTarget
DropTarget dropTarget = new DropTarget(fileText, DND.DROP_COPY);
dropTarget.setTransfer(new Transfer[]{FileTransfer.getInstance()});
dropTarget.addDropListener(new DropTargetAdapter() {
@Override
public void dragEnter(DropTargetEvent event) {
if (event.detail == DND.DROP_NONE) {
event.detail = DND.DROP_COPY;
}
}
@Override
public void drop(DropTargetEvent event) {
if (FileTransfer.getInstance().isSupportedType(event.currentDataType)) {
String[] fileNames = (String[]) event.data;
StringBuilder sb = new StringBuilder();
for (String fileName : fileNames) {
File file = new File(fileName);
sb.append("文件名:").append(file.getName()).append("\n");
sb.append("路径:").append(file.getAbsolutePath()).append("\n");
sb.append("大小:").append(file.length()).append(" 字节\n");
sb.append("是否是目录:").append(file.isDirectory()).append("\n\n");
}
fileText.setText(sb.toString());
System.out.println("拖放了 " + fileNames.length + " 个文件");
}
}
});
shell.setBounds(100, 100, 600, 400);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
费曼解释:文件拖放的应用场景
文件拖放 = 把文件从文件夹拖到应用
- 文件管理器 = 文件夹
- 应用 = 另一个文件夹
- 拖放 = 把文件从一个文件夹搬到另一个文件夹
类比:
文件拖放 = 搬家
- 旧家(文件管理器)
- 新家(应用)
- 搬家(拖放)
- 把家具(文件)从旧家搬到新家
12.5 本章小结
拖放与剪贴板总结
| 概念 | 作用 | 类比 |
|---|---|---|
| Clipboard | 临时存储数据 | 临时笔记本 |
| TextTransfer | 文本数据类型 | 文字快递 |
| FileTransfer | 文件数据类型 | 文件快递 |
| DragSource | 拖放源 | 发货方 |
| DropTarget | 拖放目标 | 收货方 |
| dragStart | 拖动开始 | 发货检查 |
| drop | 放下数据 | 收货 |
费曼测试:你能解释清楚吗?
- 剪贴板的作用?
- 临时存储数据,可以在不同应用之间复制粘贴
- DragSource 和 DropTarget 的区别?
- DragSource:拖放源,发送数据 - DropTarget:拖放目标,接收数据
- 拖放事件的含义?
- dragStart:拖动开始 - dragEnter:进入目标区域 - dragOver:在目标区域上方 - drop:放下数据
- 如何支持多种拖放类型?
- 使用 Transfer 数组,如 new Transfer[]{TextTransfer.getInstance(), FileTransfer.getInstance()}
下一章预告
现在你已经掌握了拖放与剪贴板,
可以让应用与其他应用交互,
提升用户体验。
下一章,我们将学习 SWT 与操作系统的深度集成,
让应用更好地利用系统功能,
比如调用系统浏览器、系统托盘等。
练习题:
- 创建一个文本框,实现文本的复制粘贴功能。
- 创建两个文本框,实现从第一个文本框拖动文本到第二个文本框。
- 创建一个应用,支持从文件管理器拖放文件到应用,并显示文件信息。
(提示:拖放使用 DragSource 和 DropTarget,剪贴板使用 Clipboard)