Java 中文官方教程 2022 版(二十四)(1)https://developer.aliyun.com/article/1486791
这是TopLevelTransferHandlerDemo.java
的源代码:
/** * Demonstration of the top-level {@code TransferHandler} * support on {@code JFrame}. */ public class TopLevelTransferHandlerDemo extends JFrame { private static boolean DEMO = false; private JDesktopPane dp = new JDesktopPane(); private DefaultListModel listModel = new DefaultListModel(); private JList list = new JList(listModel); private static int left; private static int top; private JCheckBoxMenuItem copyItem; private JCheckBoxMenuItem nullItem; private JCheckBoxMenuItem thItem; private class Doc extends InternalFrameAdapter implements ActionListener { String name; JInternalFrame frame; TransferHandler th; JTextArea area; public Doc(File file) { this.name = file.getName(); try { init(file.toURI().toURL()); } catch (MalformedURLException e) { e.printStackTrace(); } } public Doc(String name) { this.name = name; init(getClass().getResource(name)); } private void init(URL url) { frame = new JInternalFrame(name); frame.addInternalFrameListener(this); listModel.add(listModel.size(), this); area = new JTextArea(); area.setMargin(new Insets(5, 5, 5, 5)); try { BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream())); String in; while ((in = reader.readLine()) != null) { area.append(in); area.append("\n"); } reader.close(); } catch (Exception e) { e.printStackTrace(); return; } th = area.getTransferHandler(); area.setFont(new Font("monospaced", Font.PLAIN, 12)); area.setCaretPosition(0); area.setDragEnabled(true); area.setDropMode(DropMode.INSERT); frame.getContentPane().add(new JScrollPane(area)); dp.add(frame); frame.show(); if (DEMO) { frame.setSize(300, 200); } else { frame.setSize(400, 300); } frame.setResizable(true); frame.setClosable(true); frame.setIconifiable(true); frame.setMaximizable(true); frame.setLocation(left, top); incr(); SwingUtilities.invokeLater(new Runnable() { public void run() { select(); } }); nullItem.addActionListener(this); setNullTH(); } public void internalFrameClosing(InternalFrameEvent event) { listModel.removeElement(this); nullItem.removeActionListener(this); } public void internalFrameOpened(InternalFrameEvent event) { int index = listModel.indexOf(this); list.getSelectionModel().setSelectionInterval(index, index); } public void internalFrameActivated(InternalFrameEvent event) { int index = listModel.indexOf(this); list.getSelectionModel().setSelectionInterval(index, index); } public String toString() { return name; } public void select() { try { frame.toFront(); frame.setSelected(true); } catch (java.beans.PropertyVetoException e) {} } public void actionPerformed(ActionEvent ae) { setNullTH(); } public void setNullTH() { if (nullItem.isSelected()) { area.setTransferHandler(null); } else { area.setTransferHandler(th); } } } private TransferHandler handler = new TransferHandler() { public boolean canImport(TransferHandler.TransferSupport support) { if (!support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { return false; } if (copyItem.isSelected()) { boolean copySupported = (COPY & support.getSourceDropActions()) == COPY; if (!copySupported) { return false; } support.setDropAction(COPY); } return true; } public boolean importData(TransferHandler.TransferSupport support) { if (!canImport(support)) { return false; } Transferable t = support.getTransferable(); try { java.util.List<File> l = (java.util.List<File>)t.getTransferData(DataFlavor.javaFileListFlavor); for (File f : l) { new Doc(f); } } catch (UnsupportedFlavorException e) { return false; } catch (IOException e) { return false; } return true; } }; private static void incr() { left += 30; top += 30; if (top == 150) { top = 0; } } public TopLevelTransferHandlerDemo() { super("TopLevelTransferHandlerDemo"); setJMenuBar(createDummyMenuBar()); getContentPane().add(createDummyToolBar(), BorderLayout.NORTH); JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, list, dp); sp.setDividerLocation(120); getContentPane().add(sp); //new Doc("sample.txt"); //new Doc("sample.txt"); //new Doc("sample.txt"); list.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); list.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) { return; } Doc val = (Doc)list.getSelectedValue(); if (val != null) { val.select(); } } }); final TransferHandler th = list.getTransferHandler(); nullItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { if (nullItem.isSelected()) { list.setTransferHandler(null); } else { list.setTransferHandler(th); } } }); thItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { if (thItem.isSelected()) { setTransferHandler(handler); } else { setTransferHandler(null); } } }); dp.setTransferHandler(handler); } private static void createAndShowGUI(String[] args) { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { } TopLevelTransferHandlerDemo test = new TopLevelTransferHandlerDemo(); test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); if (DEMO) { test.setSize(493, 307); } else { test.setSize(800, 600); } test.setLocationRelativeTo(null); test.setVisible(true); test.list.requestFocus(); } public static void main(final String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { //Turn off metal's use of bold fonts UIManager.put("swing.boldMetal", Boolean.FALSE); createAndShowGUI(args); } }); } private JToolBar createDummyToolBar() { JToolBar tb = new JToolBar(); JButton b; b = new JButton("New"); b.setRequestFocusEnabled(false); tb.add(b); b = new JButton("Open"); b.setRequestFocusEnabled(false); tb.add(b); b = new JButton("Save"); b.setRequestFocusEnabled(false); tb.add(b); b = new JButton("Print"); b.setRequestFocusEnabled(false); tb.add(b); b = new JButton("Preview"); b.setRequestFocusEnabled(false); tb.add(b); tb.setFloatable(false); return tb; } private JMenuBar createDummyMenuBar() { JMenuBar mb = new JMenuBar(); mb.add(createDummyMenu("File")); mb.add(createDummyMenu("Edit")); mb.add(createDummyMenu("Search")); mb.add(createDummyMenu("View")); mb.add(createDummyMenu("Tools")); mb.add(createDummyMenu("Help")); JMenu demo = new JMenu("Demo"); demo.setMnemonic(KeyEvent.VK_D); mb.add(demo); thItem = new JCheckBoxMenuItem("Use Top-Level TransferHandler"); thItem.setMnemonic(KeyEvent.VK_T); demo.add(thItem); nullItem = new JCheckBoxMenuItem("Remove TransferHandler from List and Text"); nullItem.setMnemonic(KeyEvent.VK_R); demo.add(nullItem); copyItem = new JCheckBoxMenuItem("Use COPY Action"); copyItem.setMnemonic(KeyEvent.VK_C); demo.add(copyItem); return mb; } private JMenu createDummyMenu(String str) { JMenu menu = new JMenu(str); JMenuItem item = new JMenuItem("[Empty]"); item.setEnabled(false); menu.add(item); return menu; } }
添加剪切、复制和粘贴(CCP)
原文:
docs.oracle.com/javase/tutorial/uiswing/dnd/cutpaste.html
到目前为止,我们的讨论主要集中在拖放支持上。然而,将剪切、复制或粘贴(ccp)连接到传输处理程序是一件容易的事情。这需要以下步骤:
- 确保组件上安装了传输处理程序。
- 创建一种方式,通过该方式可以调用
TransferHandler
的 ccp 支持。通常,这涉及向输入和动作映射添加绑定,以便在特定按键响应中调用TransferHandler
的 ccp 操作。 - 创建 ccp 菜单项和/或按钮。(此步骤是可选的,但建议执行。)对于文本组件,这很容易做到,但对于其他组件,需要更多的工作,因为您需要逻辑来确定在哪个组件上触发操作。查看非文本组件中的 CCP 获取更多信息。
- 决定在哪里执行粘贴。也许在当前选择的上方或下方。在
importData
方法中安装逻辑。
接下来,我们将看一个包含文本组件的剪切和粘贴示例。
在文本组件中进行 CCP
原文:
docs.oracle.com/javase/tutorial/uiswing/dnd/textpaste.html
如果您正在使用 Swing 文本组件(文本字段、密码字段、格式化文本字段或文本区域)之一实现剪切、复制和粘贴,您的工作非常简单。这些文本组件利用了DefaultEditorKit
,它提供了内置的剪切、复制和粘贴操作。默认编辑工具包还处理了记住上次焦点在哪个组件的工作。这意味着如果用户使用菜单或键盘快捷键启动其中一个操作,正确的组件将接收该操作 —— 不需要额外的代码。
下面的演示TextCutPaste
包含三个文本字段。如您在屏幕截图中所见,您可以剪切、复制和粘贴到任何一个文本字段或从中粘贴。它们还支持拖放操作。
试一试:
- 点击启动按钮以使用Java™ Web Start运行
TextCutPaste
(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。 - 在其中一个文本字段中选择文本。使用编辑菜单或键盘快捷键从源处剪切或复制文本。
- 将光标定位到要粘贴文本的位置。
- 使用菜单或键盘快捷键粘贴文本。
- 使用拖放执行相同的操作。
这是创建编辑菜单的代码,通过将DefaultEditorKit
中定义的内置剪切、复制和粘贴操作与菜单项连接起来。这适用于任何继承自JComponent
的组件:
/** * Create an Edit menu to support cut/copy/paste. */ public JMenuBar createMenuBar () { JMenuItem menuItem = null; JMenuBar menuBar = new JMenuBar(); JMenu mainMenu = new JMenu("Edit"); mainMenu.setMnemonic(KeyEvent.VK_E); menuItem = new JMenuItem(new DefaultEditorKit.CutAction()); menuItem.setText("Cut"); menuItem.setMnemonic(KeyEvent.VK_T); mainMenu.add(menuItem); menuItem = new JMenuItem(new DefaultEditorKit.CopyAction()); menuItem.setText("Copy"); menuItem.setMnemonic(KeyEvent.VK_C); mainMenu.add(menuItem); menuItem = new JMenuItem(new DefaultEditorKit.PasteAction()); menuItem.setText("Paste"); menuItem.setMnemonic(KeyEvent.VK_P); mainMenu.add(menuItem); menuBar.add(mainMenu); return menuBar; }
接下来我们将看看如何使用不具有DefaultEditorKit
内置支持的组件实现相同的功能。
非文本组件中的 CCP
原文:
docs.oracle.com/javase/tutorial/uiswing/dnd/listpaste.html
如果您正在使用 Swing 组件之一来实现剪切、复制和粘贴,而不是文本组件,您需要进行一些额外的设置。首先,您需要在动作映射中安装剪切、复制和粘贴操作。以下方法显示了如何执行此操作:
private void setMappings(JList list) { ActionMap map = list.getActionMap(); map.put(TransferHandler.getCutAction().getValue(Action.NAME), TransferHandler.getCutAction()); map.put(TransferHandler.getCopyAction().getValue(Action.NAME), TransferHandler.getCopyAction()); map.put(TransferHandler.getPasteAction().getValue(Action.NAME), TransferHandler.getPasteAction());
当设置编辑菜单时,您还可以选择添加菜单加速器,以便用户可以输入 Control-C 来启动复制操作,例如。在下面的代码片段中,粗体文本显示了如何为剪切操作设置菜单加速器:
menuItem = new JMenuItem("Cut"); menuItem.setActionCommand((String)TransferHandler.getCutAction(). getValue(Action.NAME)); menuItem.addActionListener(actionListener); menuItem.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK)); menuItem.setMnemonic(KeyEvent.VK_T); mainMenu.add(menuItem);
如果您已为 CCP 操作设置了菜单加速器,则下一步是多余的。如果您尚未设置菜单加速器,则需要将 CCP 绑定添加到输入映射中。以下代码片段显示了如何执行此操作:
// only required if you have not set the menu accelerators InputMap imap = this.getInputMap(); imap.put(KeyStroke.getKeyStroke("ctrl X"), TransferHandler.getCutAction().getValue(Action.NAME)); imap.put(KeyStroke.getKeyStroke("ctrl C"), TransferHandler.getCopyAction().getValue(Action.NAME)); imap.put(KeyStroke.getKeyStroke("ctrl V"), TransferHandler.getPasteAction().getValue(Action.NAME));
一旦绑定已安装并且编辑菜单已设置,还有另一个问题需要解决:当用户启动剪切、复制或粘贴时,哪个组件应该接收该操作?在文本组件的情况下,DefaultEditorKit
会记住上次焦点所在的组件,并将操作转发给该组件。以下类,TransferActionListener
,为非文本 Swing 组件执行相同的功能。这个类可以被放入大多数应用程序中:
public class TransferActionListener implements ActionListener, PropertyChangeListener { private JComponent focusOwner = null; public TransferActionListener() { KeyboardFocusManager manager = KeyboardFocusManager. getCurrentKeyboardFocusManager(); manager.addPropertyChangeListener("permanentFocusOwner", this); } public void propertyChange(PropertyChangeEvent e) { Object o = e.getNewValue(); if (o instanceof JComponent) { focusOwner = (JComponent)o; } else { focusOwner = null; } } public void actionPerformed(ActionEvent e) { if (focusOwner == null) return; String action = (String)e.getActionCommand(); Action a = focusOwner.getActionMap().get(action); if (a != null) { a.actionPerformed(new ActionEvent(focusOwner, ActionEvent.ACTION_PERFORMED, null)); } } }
最后,您必须决定如何处理粘贴操作。在拖放的情况下,您将数据插入到放置位置。在粘贴的情况下,您没有用户指向所需粘贴位置的好处。您需要决定对您的应用程序来说什么是最合理的解决方案——在当前选择之前还是之后插入数据可能是最好的解决方案。
以下演示,ListCutPaste,展示了如何在一个JList
实例中实现 CCP。如您在屏幕截图中所见,有三个列表,您可以在这些列表之间剪切、复制和粘贴。它们还支持拖放。对于这个演示,粘贴的数据将插入到当前选择之后。如果没有当前选择,则数据将附加到列表末尾。
试一试:
- 点击“启动”按钮以使用Java™ Web Start运行 ListCutPaste(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
- 在其中一个列表中选择一个项目。使用编辑菜单或键盘快捷键从源中剪切或复制列表项。
- 选择要粘贴项目的列表项。
- 使用菜单或键盘等效方式粘贴文本。项目将在当前选择后粘贴。
- 使用拖放执行相同操作。
使用和创建 DataFlavor
原文:
docs.oracle.com/javase/tutorial/uiswing/dnd/dataflavor.html
DataFlavor
类允许你指定数据的内容类型。在从importData
方法中获取数据时,你需要指定一个DataFlavor
。有几种预定义的类型供你使用:
imageFlavor
代表java.awt.Image
格式的数据。在拖动图像数据时使用。stringFlavor
代表最基本的文本形式数据 —java.lang.String
。这是大多数应用程序中最常用的数据类型。javaFileListFlavor
代表以java.util.List
格式表示的java.io.File
对象。这对于拖动文件的应用程序非常有用,比如在顶级拖放课程中讨论的TopLevelTransferHandler
示例。
对于大多数应用程序,这就是你需要了解的关于数据类型的全部内容。然而,如果你需要除了这些预定义类型之外的类型,你可以创建自己的类型。如果你创建了一个自定义组件并希望它参与数据传输,你将需要创建一个自定义数据类型。指定数据类型的构造函数是DataFlavor(Class, String)
。例如,为java.util.ArrayList
类创建一个数据类型:
new DataFlavor(ArrayList.class, "ArrayList");
要为整数数组创建一个数据类型:
new DataFlavor(int[].class, "Integer Array");
使用这种机制传输数据使用了Object
序列化,因此你用于传输数据的类必须实现Serializable
接口,以及与之一起序列化的任何内容。如果不是所有内容都是可序列化的,你将在拖放或复制到剪贴板时看到NotSerializableException
。
使用DataFlavor(Class, String)
构造函数创建数据类型允许你在应用程序之间传输数据,包括本地应用程序。如果你想创建一个只在应用程序内部传输数据的数据类型,可以使用javaJVMLocalObjectMimeType
和DataFlavor(String)
构造函数。例如,要指定一个从JColorChooser
仅在你的应用程序内传输颜色的数据类型,你可以使用这段代码:
String colorType = DataFlavor.javaJVMLocalObjectMimeType + ";class=java.awt.Color"; DataFlavor colorFlavor = new DataFlavor(colorType);
为一个只在你的应用程序中起作用的ArrayList
创建一个数据类型:
new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + ";class=java.util.ArrayList");
要为整数数组创建一个数据类型:
new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + ";class=\"" + int[].class.getName() + "\"");
包含特殊字符的 MIME 类型,比如**[或;**,必须将这些字符用引号括起来。
一个Transferable
可以被实现以支持多种风味。例如,你可以同时使用本地和序列化风味,或者你可以同时使用两种形式的相同数据,比如ArrayList
和整数数组风味,或者你可以创建一个接受不同类型数据的TransferHandler
,比如颜色和文本。
当你创建一个DataFlavors
数组以从Transferable
的getTransferDataFlavors
方法返回时,风味应按照首选顺序插入,最首选的风味应出现在数组的元素 0 位置。一般来说,首选顺序是从最丰富或最复杂的数据形式到简单集合的形式 — 最有可能被其他对象理解的形式。
将所有内容整合在一起 - 拖放和剪切复制粘贴
原文:
docs.oracle.com/javase/tutorial/uiswing/dnd/together.html
我们已经展示了如何实现拖放支持以及如何实现剪切、复制、粘贴支持。如何在一个组件中同时实现两者?
你需要在TransferHandler
的importData
方法中实现这两种功能,就像这样:
if (transferSupport.isDrop()) { // put data in transferSupport.getDropLocation() } else { // determine where you want the paste to go (ex: after current selection) // put data there }
在非文本组件中的 CCP 页面上讨论的ListCutPaste
示例支持拖放和剪切复制粘贴。这是它的importData
方法(用粗体标记了if
-else
的逻辑):
public boolean importData(TransferHandler.TransferSupport info) { String data = null; //If we cannot handle the import, bail now. if (!canImport(info)) { return false; } JList list = (JList)info.getComponent(); DefaultListModel model = (DefaultListModel)list.getModel(); //Fetch the data -- bail if this fails try { data = (String)info.getTransferable().getTransferData(DataFlavor.stringFlavor); } catch (UnsupportedFlavorException ufe) { System.out.println("importData: unsupported data flavor"); return false; } catch (IOException ioe) { System.out.println("importData: I/O exception"); return false; } if (info.isDrop()) { //This is a drop JList.DropLocation dl = (JList.DropLocation)info.getDropLocation(); int index = dl.getIndex(); if (dl.isInsert()) { model.add(index, data); return true; } else { model.set(index, data); return true; } } else { //This is a paste int index = list.getSelectedIndex(); // if there is a valid selection, // insert data after the selection if (index >= 0) { model.add(list.getSelectedIndex()+1, data); // else append to the end of the list } else { model.addElement(data); } return true; } }
这是唯一需要安装if
-else
逻辑来区分拖放和剪切复制粘贴的地方。
解决常见数据传输问题
原文:
docs.oracle.com/javase/tutorial/uiswing/dnd/problems.html
当使用数据传输时,可能会遇到一些问题。
问题: 我的拖动手势识别器在与表格/列表/树/文本一起使用时无法正常工作。
不要在这些组件上使用自己的拖动手势识别器。使用setDragEnabled(true)
和TransferHandler
。
问题: 我无法将数据放置到我的空JTable
中。
你需要在表格上调用setFillsViewportHeight(true)
。查看空表格拖放获取更多信息。
Java 中文官方教程 2022 版(二十四)(3)https://developer.aliyun.com/article/1486798