[DnD]Swing Drag And Drop 分析笔记

DnD(Drag And Drop)对于程序的用户友好度至关重要

常见的, 把一个文件直接拖入程序里, 把item从一个list拖到另一个list

最几天在学习Swing的DnD机制, 被搞得一塌糊涂, 晕头转向的忙活了好几个晚上, 终于理出一点头绪来:

总体来说, DnD 包括这几个类: DragSource, DragGestureRecognizer, DragGestureListener, DragSourceAdapter, DropTargetAdapter, TransferHandler, Transferable, DataFlavor

DragSoure: 环境变量, 生成DragGestureRecognizer
DragGestureRecognizer: 环境变量, 绑定DragGestureListener给指定的Jcomponent
DragGestureListener: 拖拽事件开始的源头
DragSourceAdapter: JComponent Source Listener: dragEnter, dragOver, dragExit, dragDropEnd
DropTargetAdapter: JComponent Target Listener: dropEnter,, drapOver, dragExit, drop

TransferHandler: drag & drop event handler, 从官方的源代码设计来看, DragSourceAdapter, DropTargetAdapter都包含在里面, 类似代理器的设计模式

Transferable: DnD事件的统一数据接口

DataFlavor:DnD事件的统一数据


先以简单的例子来演示DnD的机制

一 Drag
1.给 JTextField 添加DnD:

JTextField txt1=new JTextField();
txt1.setDragEnabled(true);

虽然只需以上2行代码即可, 但其实Swing在背后给我们做了很多事情
首先, jframe生成的时候, DataSourse和DragGestureRecognizer已经生成,
并给所有textfield, textarea之类的控件添加了DnD事件,
其中所有的jtextfield控件都绑定给
[b]"javax.swing.plaf.basic.BasicTextUI$TextTransferHandler@4a63d8"[/b],
而这个类的内部也生成有DragSoure和DropTarget所有接口的实现

然后, 当你在jtextfield里[b]选中所有文字, 再拖拽的时候[/b], 就会激活DnD事件
具体顺序(Drag):
TransferHandler exportAsDrag->getSourceActions->createTransferabled->exportDone->dragGestureRecognized

多尝试几次, 你会发现, 其实[b]不选中所有文件而直接拖拽[/b], 也会激活DnD事件
TransferHandler dragGestureRecognized
更不可思议的是, 哪怕 txt1.setDragEnabled(false)也一样, 这是很有用的


2.给 Jlabel 添加DnD
官方给出的演示

/**
* add label drag action with mousePressed
* @author YS
*/
public class DragAndDropDemo2 {

JFrame frame;
Container container;
JLabel lbl1;
JLabel lbl2;
JTextField txt1;
JTextField txt2;
JLabel lbl3;
JTextArea txt3;

public DragAndDropDemo2() {
frame = new JFrame("DRAG AND DROP DEMO");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
container = frame.getContentPane();
frame.setBounds(100, 100, 400, 300);
addComponent();
frame.setVisible(true);
}

private void addComponent() {
lbl1 = new JLabel("text1");
lbl2 = new JLabel("text2");
txt1 = new JTextField();
txt2 = new JTextField();
lbl1.setBounds(10, 10, 40, 20);
lbl2.setBounds(10, 40, 40, 20);
txt1.setBounds(60, 10, 300, 20);
txt2.setBounds(60, 40, 300, 20);
txt1.setDragEnabled(true);
txt2.setDragEnabled(true);

container.add(lbl1);
container.add(lbl2);
container.add(txt1);
container.add(txt2);

addLabelDragEvent(lbl1);
}

private void addLabelDragEvent(JLabel lbl1) {
lbl1.addMouseListener(new MouseAdapter() {

@Override
public void mousePressed(MouseEvent e) {
JComponent com=(JComponent)e.getSource();
TransferHandler hander=new TransferHandler("text");
com.setTransferHandler(hander);
hander.exportAsDrag(com, e, DnDConstants.ACTION_COPY);
}
});
}

public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable() {

public void run() {
new DragAndDropDemo2();
}
});
}
}

由于label不能设计setDragEnabled(true), 所以需要人为的激活DnD事件, 一开始时, jframe并没有为jlabel绑定transferhanlder, 所以必须自己创建, 在new TransferHandler("text")的同时, drag & drop事件也已经生成了, 带"text"参数, 是创建能接受文本内容的handler, 具体的swing已经为我们做好了, 能用预设的最好都用预设的, 然后在mousePressed时激活exportAsDrag, 然后再转到 handler内部的dragGestureRecognized接口实现, swing已经帮我们作好获取label里面文本并生成transferable的工作了, 最终调用内部的startDrag开始拖拽

自己写另外一种实现

/**
* add label drag action with dragGestureRecognizedListener
* @author YS
*/
public class DragAndDropDemo3 {

JFrame frame;
Container container;
JLabel lbl1;
JLabel lbl2;
JTextField txt1;
JTextField txt2;
JLabel lbl3;
JTextArea txt3;

public DragAndDropDemo3() {
frame = new JFrame("DRAG AND DROP DEMO");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
container = frame.getContentPane();
frame.setBounds(100, 100, 400, 300);
addComponent();
frame.setVisible(true);
}

private void addComponent() {
lbl1 = new JLabel("text1");
lbl2 = new JLabel("text2");
txt1 = new JTextField();
txt2 = new JTextField();
lbl1.setBounds(10, 10, 40, 20);
lbl2.setBounds(10, 40, 40, 20);
txt1.setBounds(60, 10, 300, 20);
txt2.setBounds(60, 40, 300, 20);
txt1.setDragEnabled(true);
txt2.setDragEnabled(true);

container.add(lbl1);
container.add(lbl2);
container.add(txt1);
container.add(txt2);

addLabelDragEvent(lbl1);
}

private void addLabelDragEvent(JLabel lbl1) {
DragSource ds=DragSource.getDefaultDragSource();
ds.createDefaultDragGestureRecognizer(lbl1, DnDConstants.ACTION_COPY, new DragGestureAdapter());
}

public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable() {

public void run() {
new DragAndDropDemo3();
}
});
}

class DragGestureAdapter implements DragGestureListener{

public void dragGestureRecognized(DragGestureEvent e) {
System.out.println("Drag dragGestureRecognized");
JComponent c = (JComponent) e.getComponent();
Transferable t=createTransferable(c);
e.startDrag(DragSource.DefaultCopyNoDrop, t, new DragAdapter());
}
}

class DragAdapter extends DragSourceAdapter{

@Override
public void dragEnter(DragSourceDragEvent dsde) {
Component com=(Component)dsde.getDragSourceContext().getComponent();
Set<Class> set=new LinkedHashSet<Class>();
set.add(JLabel.class);
set.add(JTextField.class);
if(set.contains(com.getClass())){
dsde.getDragSourceContext().setCursor(DragSource.DefaultCopyDrop);
}
}

@Override
public void dragExit(DragSourceEvent dse) {
Component com=(Component)dse.getDragSourceContext().getComponent();
Set<Class> set=new LinkedHashSet<Class>();
set.add(JLabel.class);
set.add(JTextField.class);
if(set.contains(com.getClass())){
dse.getDragSourceContext().setCursor(DragSource.DefaultCopyNoDrop);
}
}

}

protected Transferable createTransferable(JComponent c) {
System.out.println("DragGestureAdapter createTransferable");
//return super.createTransferable(c);
String text="";
Component com=c;
String type=com.getClass().getSimpleName();
if("JLabel".equals(type)){
text=type+((JLabel)com).getText();
}else if("JTextField".equals(type)){
text=type+": "+((JTextField)com).getText();
}else if("JTextArea".equals(type)){
text=type+": "+((JTextArea)com).getText();
}
Transferable t=new StringSelection(text);
return t;
}
}

这样也可以实现同样的效果, 原因在于: 其实mouseDragged的时候, 也会判断dragGestureRecognized, 因为这里给label绑定了dragGestureRecognized接口的实现, 所以DnD机制还是开始了, 从startDrag开始
这里要说一下鼠标的手势, 一开始应当指定的是DragSource.DefaultCopyNoDrop, 会是一个不可复制的鼠标, 如果指定DragSource.DefaultCopyDrop的话, 就是一个带+号的复制鼠标, 这样会误人视听, 鼠标在进入别的控件时, 会激发dragEnter事件, 在离开控件时, 激活dragExit, 要注意鼠标的手势

对比以上3个例子, 有几个比较重要的地方,
jtextfield; setDragEnabled(true),全选并拖拽时激发transferhandler的exportDrag, 然后才是createTransferabled, dragDestrueGeconized, 最终startDrag, (startDrag是在dragDestrueGeconized里调用的)

label(官方): 通过mousePress, 人为调用exportDrag, 然后的过程跟jtextfield一样

label(自定义): 通过绑定dragDestrueGeconized给jlabel, 然后dragDestrueGeconized在鼠标拖拽时被激活, transferable必须在dragDestrueGeconized里生成

所以, 可以得到, dragDestrueGeconized接口总会在鼠标拖拽(非mousePress)时

-----------------------------------------------Separator-----------------------------------------------

二 Drop
Drop的触发顺序: 目标控件的DropTarget的 drop(松开鼠标左键)->获取目标控件的TransferHandler, 并调用它的canImport, 如果为true, 则执行acceptDrop ,然后调用importData, 最终dropComplete

仔细分析, 里面其实有2个地方容易混乱, 一个是droptarget的drop方法, 另一个是transferhandler的importData方法,
2个方法都可以给我们重载代码, 但要通过drop模似swing的路线走到importData是比较困难的, 而且importData方法是后面, 所以, 推荐优先尝试在importData里面实现我们要的东西

简单示例
1. 从JTextField拖曳内容给JLabel, TransferHandler实现

/**
* add label drop action with transferhandler("text")
* @author YS
*/
public class DragAndDropDemo4 {

JFrame frame;
Container container;
JLabel lbl1;
JLabel lbl2;
JTextField txt1;
JTextField txt2;
JLabel lbl3;
JTextArea txt3;

public DragAndDropDemo4() {
frame = new JFrame("DRAG AND DROP DEMO");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
container = frame.getContentPane();
frame.setBounds(100, 100, 400, 300);
addComponent();
frame.setVisible(true);
}

private void addComponent() {
lbl1 = new JLabel("text1");
lbl2 = new JLabel("text2");
txt1 = new JTextField();
txt2 = new JTextField();
lbl1.setBounds(10, 10, 40, 20);
lbl2.setBounds(10, 40, 40, 20);
txt1.setBounds(60, 10, 300, 20);
txt2.setBounds(60, 40, 300, 20);
txt1.setDragEnabled(true);
txt2.setDragEnabled(true);

container.add(lbl1);
container.add(lbl2);
container.add(txt1);
container.add(txt2);

addLabelDropEvent(lbl1);
}

private void addLabelDropEvent(JLabel lbl1) {
lbl1.setTransferHandler(new TransferHandler("text"));
}

public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable() {

public void run() {
new DragAndDropDemo4();
}
});
}
}

其实, 由于label一开始是没有transferhandler的, 所以只需要通过系统预设的transferhandler("text"), 与此同时生成了droptarget实例, 方便地实现了label的拖曳赋值
其系统机制: 当鼠标在label上released时, droptarget的drop方法被激活, 然后获取自身的transferhandler, 如果null, 则调用rejectDrop, 否则执行transferhandler.canImport判断这个控件是否能接受drop, 可以的话, 执行transferhandler.importData

2. 从JTextField拖曳内容给JLabel, DropTarget实现

/**
* add label drop action with DropTarget
* @author YS
*/
public class DragAndDropDemo5 {

JFrame frame;
Container container;
JLabel lbl1;
JLabel lbl2;
JTextField txt1;
JTextField txt2;
JLabel lbl3;
JTextArea txt3;

public DragAndDropDemo5() {
frame = new JFrame("DRAG AND DROP DEMO");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
container = frame.getContentPane();
frame.setBounds(100, 100, 400, 300);
addComponent();
frame.setVisible(true);
}

private void addComponent() {
lbl1 = new JLabel("text1");
lbl2 = new JLabel("text2");
txt1 = new JTextField();
txt2 = new JTextField();
lbl1.setBounds(10, 10, 40, 20);
lbl2.setBounds(10, 40, 40, 20);
txt1.setBounds(60, 10, 300, 20);
txt2.setBounds(60, 40, 300, 20);
txt1.setDragEnabled(true);
txt2.setDragEnabled(true);

container.add(lbl1);
container.add(lbl2);
container.add(txt1);
container.add(txt2);

addLabelDropEvent(lbl1);
}

private void addLabelDropEvent(JLabel lbl1) {
lbl1.setDropTarget(new MyDropTargetAdapter());
}

class MyDropTargetAdapter extends DropTarget{

@Override
public void drop(DropTargetDropEvent e) {
try {
e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
Component com=e.getDropTargetContext().getComponent();
Transferable t=e.getTransferable();
String text=(String)t.getTransferData(DataFlavor.stringFlavor);
if(com instanceof JLabel){
((JLabel)com).setText(text);
}else if(com instanceof JTextField){
((JTextField)com).setText(text);
}
e.dropComplete(true);
} catch (Exception ex) {
e.rejectDrop();
ex.printStackTrace();
}
}
}

public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable() {

public void run() {
new DragAndDropDemo5();
}
});
}
}

由于DropTarget的drop已经实现我想要的, 并且没有像swing那样全面的检查并调用transferhandler的canImport和importData, 所以, 整个drop动作就到此结束

注意e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE), 如果没有这个, 就表示没有接受drop, swing就不会给你transable和dataflavor,
但在transferhandler的importData里, 在importData之前, 就swing就已经帮我们判断好acceptDrop还是rejectDrop, 结束后还有dropComplete

通过上面2种方法都可以实现drop动作, 但出于代码的严整性和程序的整体观念考虑, 还是推荐第一种, 在transferhandler的importData里面实现

to be continue...
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值