Java 中文官方教程 2022 版(二十四)
选择放置操作
原文:
docs.oracle.com/javase/tutorial/uiswing/dnd/dropaction.html
每个拖动源(基于 Java 或其他方式)在导出数据时都会公布其支持的操作集。如果支持复制数据,则公布COPY
操作;如果支持从中移动数据,则公布MOVE
操作,依此类推。对于 Swing 组件,源操作通过getSourceActions
方法公布。
当启动拖动操作时,用户可以通过与拖动手势结合使用的键盘修饰符来控制选择哪个源操作用于传输 — 这称为用户操作。例如,默认情况下(未使用修饰符),通常表示移动操作,按住 Control 键表示复制操作,同时按住 Shift 和 Control 表示链接操作。用户操作可通过getUserDropAction
方法获得。
用户操作表示了偏好,但最终决定放置操作的是目标。例如,考虑一个只接受复制数据的组件。再考虑一个支持复制和移动的拖动源。即使用户表示偏好移动操作,仅接受来自源的数据的复制目标的TransferHandler
也可以编码为仅接受数据,使用setDropAction
方法。
这项工作发生在canImport
方法中,目标的TransferHandler
决定是否接受传入数据。如果源支持,一个明确选择COPY
操作的实现可能如下所示:
public boolean canImport(TransferHandler.TransferSupport support) {
// for the demo, we will only support drops (not clipboard paste)
if (!support.isDrop()) {
return false;
}
// we only import Strings
if (!support.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return false;
}
// check if the source actions (a bitwise-OR of supported actions)
// contains the COPY action
boolean copySupported = (COPY & support.getSourceDropActions()) == COPY;
if (copySupported) {
support.setDropAction(COPY);
return true;
}
// COPY is not supported, so reject the transfer
return false;
}
粗体显示的代码片段显示了查询源支持的放置操作的位置。如果支持复制,则调用setDropAction
方法以确保只进行复制操作,并且该方法返回 true。
接下来我们将看一个演示,明确使用setDropAction
设置放置操作。
演示 - 选择放置操作
原文:
docs.oracle.com/javase/tutorial/uiswing/dnd/dropactiondemo.html
以下演示ChooseDropActionDemo
包含三个列表。如屏幕截图所示,左侧的列表标有“从这里拖动”,是拖动源。该列表支持移动和复制,但不实现导入,因此无法将内容拖放到其中。
右侧是两个充当放置目标的列表。顶部列表标有“在此处复制”,只允许将数据复制到其中。底部列表标有“在此处移动”,只允许将数据移动到其中。源列表只允许从中拖动数据。
试一试:
-
点击“启动”按钮以使用Java™ Web Start运行
ChooseDropActionDemo
(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。 -
在源列表中选择一个项目并拖动到上方目标列表。当你拖动到目标上方时,即使你没有按住 Control 键表示要进行复制操作,也会注意到显示复制放置鼠标指针。(请注意,在 Macintosh 平台上,除非按住 Option 键,否则不会显示复制指针。)
-
放下项目。它被插入到目标列表中,但不会从源列表中移除 - 如期望的那样。
-
再次从源列表拖动,但这次放入下方目标列表。放下项目。它被插入到目标列表中并从源列表中移除。
-
在源列表中选择另一项,并在按住 Control 键的同时拖动该项到下方目标列表,表示偏好复制操作。
-
将项目放入列表中。项目不会被插入 - 拖放被拒绝。传输处理程序的
canImport
方法被编码为拒绝复制操作,但也可以实现为返回 true,这样用户操作将占上风,复制将发生。
正如你所猜测的,ChooseDropActionDemo.java
示例包含两个TransferHandler
实现:
/**
* The FromTransferHandler allows dragging from the list and
* supports both copy and move actions. This transfer handler
* does not support import.
*/
class FromTransferHandler extends TransferHandler {
public int getSourceActions(JComponent comp) {
return COPY_OR_MOVE;
}
private int index = 0;
public Transferable createTransferable(JComponent comp) {
index = dragFrom.getSelectedIndex();
if (index < 0 || index >= from.getSize()) {
return null;
}
return new StringSelection((String)dragFrom.getSelectedValue());
}
public void exportDone(JComponent comp, Transferable trans, int action) {
if (action != MOVE) {
return;
}
from.removeElementAt(index);
}
}
/**
* The ToTransferHandler has a constructor that specifies whether the
* instance will support only the copy action or the move action.
* This transfer handler does not support export.
*/
class ToTransferHandler extends TransferHandler {
int action;
public ToTransferHandler(int action) {
this.action = action;
}
public boolean canImport(TransferHandler.TransferSupport support) {
// for the demo, we will only support drops (not clipboard paste)
if (!support.isDrop()) {
return false;
}
// we only import Strings
if (!support.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return false;
}
// check if the source actions contain the desired action -
// either copy or move, depending on what was specified when
// this instance was created
boolean actionSupported = (action & support.getSourceDropActions()) == action;
if (actionSupported) {
support.setDropAction(action);
return true;
}
// the desired action is not supported, so reject the transfer
return false;
}
public boolean importData(TransferHandler.TransferSupport support) {
// if we cannot handle the import, say so
if (!canImport(support)) {
return false;
}
// fetch the drop location
JList.DropLocation dl = (JList.DropLocation)support.getDropLocation();
int index = dl.getIndex();
// fetch the data and bail if this fails
String data;
try {
data = (String)support.getTransferable().getTransferData(DataFlavor.stringFlavor);
} catch (UnsupportedFlavorException e) {
return false;
} catch (java.io.IOException e) {
return false;
}
JList list = (JList)support.getComponent();
DefaultListModel model = (DefaultListModel)list.getModel();
model.insertElementAt(data, index);
Rectangle rect = list.getCellBounds(index, index);
list.scrollRectToVisible(rect);
list.setSelectedIndex(index);
list.requestFocusInWindow();
return true;
}
}
FromTransferHandler
附加到源列表,允许从列表中拖动并支持复制和移动操作。如果尝试将内容放置到此列表中,将会被拒绝,因为FromTransferHandler
未实现canImport
和importData
方法。
ToTransferHandler
被附加到移动和复制目标列表上,包含一个构造函数,指定目标列表是否只允许复制或只允许移动。支持复制操作的实例被附加到复制列表上,支持移动操作的实例被附加到移动列表上。
你可能也对顶层放置示例感兴趣,该示例还说明了如何选择放置操作。
接下来我们来看如何显示放置位置。
显示放置位置
原文:
docs.oracle.com/javase/tutorial/uiswing/dnd/showdroploc.html
通常在拖放操作期间,组件在可以接受数据时会给出视觉反馈。它可能会突出显示放置位置,或者显示插入位置的插入符或水平线。当组件的TransferHandler
的canImport
方法返回 true 时,Swing 会渲染放置位置。
要在程序中控制这一点,您可以使用setShowDropLocation
方法。调用此方法并传入true
会导致始终显示放置位置的视觉反馈,即使放置操作不被接受。调用此方法并传入false
会阻止任何视觉反馈,即使放置操作将被接受。您始终要从canImport
中调用此方法。
演示 - LocationSensitiveDemo 页面包括一个下拉框,让您可以选择始终显示放置位置、从不显示放置位置或默认行为。但首先我们将讨论位置敏感的放置。
位置敏感的拖放
原文:
docs.oracle.com/javase/tutorial/uiswing/dnd/locsensitivedrop.html
有时您有一个复杂的组件,希望用户能够在其中的某些部分进行拖放,但在其他部分不允许。也许是一个只允许在特定列中放置数据的表格;或者是一个只允许在特定节点上放置数据的树。显然,您希望鼠标提供准确的反馈 — 当它悬停在接受拖放的组件的特定部分上时,应该只显示拖放位置。
通过在TransferHandler
类的canImport(TransferHandler.TransferSupport)
方法中安装必要的逻辑,可以轻松实现这一点。它仅适用于这个特定版本的canImport
,因为它在拖动手势悬停在组件边界上时被连续调用。当此方法返回 true 时,Swing 显示拖放光标并且拖放位置在视觉上被指示;当此方法返回 false 时,Swing 显示“无拖动”鼠标光标并且拖放位置不显示。
例如,想象一个允许拖放但不允许在第一列的表格。canImport
方法可能如下所示:
public boolean canImport(TransferHandler.TransferSupport info) {
// for the demo, we will only support drops (not clipboard paste)
if (!info.isDrop()) {
return false;
}
// we only import Strings
if (!info.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return false;
}
// fetch the drop location
JTable.DropLocation dl = (JTable.DropLocation)info.getDropLocation();
int column = dl.getColumn();
// we do not support invalid columns or the first column
if (column == -1 || column == 0) {
return false;
}
return true;
}
粗体显示的代码指示了位置敏感的拖放逻辑:当用户以无法计算列(因此无效)的方式放置数据或者当用户将文本放置在第一列时,canImport
方法返回 false — 因此 Swing 显示“无拖动”鼠标光标。一旦用户将鼠标移出第一列,canImport
返回 true 并且 Swing 显示拖动光标。
接下来,我们展示一个实现了位置敏感拖放的树的演示。
演示 - LocationSensitiveDemo
原文:
docs.oracle.com/javase/tutorial/uiswing/dnd/locsensitivedemo.html
下面的演示LocationSensitiveDemo
展示了一个已配置为支持在除了名为"names"(或其子代)之外的任何节点上进行拖放的JTree
。使用窗口顶部的文本字段作为拖动源(每次从那里拖动时,字符串编号会自动递增)。
树下方的一个下拉框允许您切换显示拖放位置的行为。Swing 的默认行为是仅在区域可以接受拖放时显示拖放位置。您可以覆盖此行为以始终显示拖放位置(即使区域无法接受拖放)或永远不显示拖放位置(即使区域可以接受拖放)。
试试这个:
-
单击启动按钮以使用Java™ Web Start运行
LocationSensitiveDemo
(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。 -
通过在文本字段中"String 0"上按下并将鼠标移动一小段距离来启动拖动。拖动到树中并向下移动。当你将鼠标悬停在大多数节点上时,拖动接受性会通过鼠标指针和节点高亮显示来指示。将文本放到"colors"节点上。新项目将成为该节点的子节点,并成为列出的颜色的同级节点。
-
将"String 1"从文本字段拖动到树中。尝试将其放在"names"节点上。当你拖动到该节点或其子节点时,Swing 不会提供任何拖放位置反馈,也不会接受数据。
-
将"Show drop location"下拉框更改为"Always"。
-
重复步骤 1 和 2。现在拖放位置将显示在"names"节点,但你无法将数据放入该区域。
-
将"Show drop location"下拉框更改为"Never"。
-
重复步骤 1 和 2。拖放位置将不会显示在树的任何部分,尽管你仍然可以将数据放入除了"names"之外的节点。
LocationSensitiveDemo
的canImport
方法如下所示:
public boolean canImport(TransferHandler.TransferSupport info) {
// for the demo, we will only support drops (not clipboard paste)
if (!info.isDrop()) {
return false;
}
String item = (String)indicateCombo.getSelectedItem();
if (item.equals("Always")) {
info.setShowDropLocation(true);
} else if (item.equals("Never")) {
info.setShowDropLocation(false);
}
// we only import Strings
if (!info.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return false;
}
// fetch the drop location
JTree.DropLocation dl = (JTree.DropLocation)info.getDropLocation();
TreePath path = dl.getPath();
// we do not support invalid paths or descendants of the names folder
if (path == null || namesPath.isDescendant(path)) {
return false;
}
return true;
}
第一个显示为粗体的代码片段修改了拖放位置反馈机制。如果是"Always",则始终显示拖放位置。如果是"Never",则永远不显示拖放位置。否则,应用默认行为。
粗体显示的第二个代码片段包含确定树是否接受数据的逻辑。如果路径不是有效路径,或者不是名称路径(或其子路径),它将返回 false,导入将不被接受。
空表格拖放
原文:
docs.oracle.com/javase/tutorial/uiswing/dnd/emptytable.html
将内容拖放到空表格中会带来独特的挑战。遵循正确的步骤:
-
创建空表格。
-
创建并附加
TransferHandler
。 -
通过调用
setDragEnabled(true)
启用数据传输。 -
创建滚动窗格并将表格添加到滚动窗格中。
运行应用程序并尝试将有效数据拖放到表格中,但它拒绝了拖放。怎么回事?
原因在于空表格(不像空列表或空树)不占据滚动窗格中的任何空间。JTable
不会自动拉伸以填充JScrollPane
视口的高度 — 它只会占据所需的垂直空间以容纳其中的行。因此,当您拖动到空表格上时,实际上并不在表格上,因此拖放失败。
您可以通过调用JTable.setFillsViewportHeight(boolean)
来配置表格,以允许在视口中的任何位置进行拖放。此属性的默认值为 false,以确保向后兼容性。
以下示例FillViewportHeightDemo
允许您尝试将数据拖放到空表格中。该演示包含一个具有五列的空表格,其拖放模式设置为插入行,并提供五个逗号分隔值的拖动源,这些值会自动递增。
试试这个:
-
单击“启动”按钮以使用Java™ Web Start运行
FillViewportHeightDemo
(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。 -
从标有“从这里拖动”的文本字段拖动到表格中。
-
拖放到表格上。拖放被拒绝。
-
双击拖动源。它会将当前值(0, 0, 0, 0, 0)放入表格中,并递增文本字段中的值。
-
再次从文本字段拖动到表格中。您可以在行的上方或下方插入,但不能在下方的区域内插入。
-
从“选项”菜单中选择“填充视口高度”以启用“fillsViewportHeight”属性。
-
从“选项”菜单中选择“重置”以清空表格。
-
从文本组件拖动到表格中。现在您可以在视口的任何位置放置数据,并将其插入到第 0 行。
你可以查看FillViewportHeightDemo.java
的源代码,但要记住的主要观点是,通常应该在任何接受拖放数据的表格上调用setFillsViewportHeight(true)
。
放置位置渲染
原文:
docs.oracle.com/javase/tutorial/uiswing/dnd/droplocation.html
这是一个更高级的主题,大多数人不需要担心它。但是,如果您有一个自定义组件,您将需要自行处理放置位置的渲染。
您可以注册以便在dropLocation
属性更改时收到通知。您可以监听此更改,并在组件的自定义渲染器中或在paintComponent
方法中使用getDropLocation
方法自行渲染放置位置。
这里是监听dropLocation
属性的示例:
class Repainter extends PropertyChangeListener {
public void propertyChange(PropertyChangeEvent pce) {
repaintDropLocation(pce.getOldValue());
repaintDropLocation(pce.getNewValue());
}
}
comp.addPropertyChangeListener("dropLocation", newRepainter());
这里是paintComponent
方法的一个示例:
public void paintComponent(Graphics g) {
super.paintComponent(g);
DropLocation loc= getDropLocation();
if (loc == null) {
return;
}
renderPrettyIndicatorAt(loc);
}
顶层拖放
原文:
docs.oracle.com/javase/tutorial/uiswing/dnd/toplevel.html
到目前为止,我们主要关注将TransferHandler
附加到JComponent
子类之一。但是您也可以直接在顶层容器上设置TransferHandler
,例如JFrame
和JDialog
。
这对于导入文件的应用程序特别有用,例如编辑器、IDE、图像处理程序、CD 刻录程序。这样的应用程序通常包括一个菜单、一个工具栏、一个用于编辑文档的区域,以及可能包括一个用于在打开文档之间切换的列表或机制。
我们有这样一个示例,但由于此演示读取文件,我们不提供 Java Web Start 版本——您将不得不自行下载和编译演示。
如下面的屏幕截图所示,TopLevelTransferHandlerDemo
有一个菜单(空的,除了 Demo 子菜单),一个(非功能性的)工具栏,一个(左侧的)显示打开文档列表的区域,以及一个(右侧的)显示每个打开文档内容的区域。在启动时,蓝色文档区域已分配了一个支持文件导入的传输处理程序——因此是唯一可以接受拖放的地方。
试试这个:
-
编译并运行
TopLevelTransferHandlerDemo
示例,如果您想下载一个为 NetBeans 结构化的 zip 文件,请参考示例索引。 -
从您的本机桌面或文件系统中拖动文件并将其放置在右侧的蓝色文档区域。文件将被打开,并填充其内容的框架将出现。文档区域,一个
JDesktopPane
,包含支持导入javaFileListFlavor
的传输处理程序。 -
拖动另一个文件并尝试将其放置在文档区域。您会发现无法将其放置在显示最后一个文件的框架上。您也无法将其放置在列表、菜单或工具栏上。您唯一可以放置的地方是文档区域的蓝色部分或先前打开的框架的菜单栏。在每个内容框架内都有一个文本组件的传输处理程序,它不理解文件拖放——您可以将文本拖放到该区域,但不能拖放文件。
-
从菜单中选择 Demo->Use Top-Level TransferHandler 来在顶层容器——一个
JFrame
上安装传输处理程序。 -
再次尝试拖动演示。接受拖放的区域数量已增加。现在您可以将其放置在应用程序的几乎任何位置,包括菜单栏、工具栏、框架的标题栏,但不能放置在列表(左侧)或先前打开文件的内容区域。
JList
和文本区域的传输处理程序都不知道如何导入文件。 -
通过选择 Demo->从菜单中的列表和文本中删除 TransferHandler 来禁用剩余组件上的传输处理程序。
-
再次拖动演示。现在您可以在应用程序的任何位置放置文件!
-
从菜单中选择 Demo->使用 COPY 操作。
-
再次拖动演示。请注意,鼠标光标现在显示为 COPY 光标 — 这提供了更准确的反馈,因为成功拖放不会从源文件中删除文件。目标可以根据选择拖放操作中描述的可用拖放操作进行选择。
注意在文本组件上禁用默认传输处理程序的一个不良副作用:您无法在编辑区域内再拖放(或剪切/复制/粘贴)文本。要解决此问题,您需要为文本组件实现一个自定义传输处理程序,该处理程序接受文件拖放,并重新实现缺失的文本传输支持。您可能想要查看RFE 4830695,该请求允许在现有的TransferHandler
上添加数据导入。
这是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)
。查看空表格拖放获取更多信息。
课程:编写事件监听器
原文:
docs.oracle.com/javase/tutorial/uiswing/events/index.html
示例索引
本课程为您提供了编写事件监听器的详细信息。您可能不需要阅读本节。您处理事件的首要信息来源应该是相关组件的操作指南部分。每个组件的部分都展示了在实现组件时最常用的事件处理代码。例如,如何使用复选框展示了如何使用项目监听器处理复选框上的鼠标点击。
一些简单的事件处理示例
本节中的程序演示了事件和事件处理。
编写事件监听器的一般信息
本节提供了处理所有类型事件的有用信息。其中一个主题包括使用适配器和内部类来实现事件处理程序的信息。
Swing 组件支持的监听器
这是唯一可以查找到哪些 Swing 组件可以触发哪些类型事件的地方。您可能希望将本节加为书签,以便轻松找到其快速参考表。
为常见处理事件实现监听器
本节提供了编写每种常见类型事件监听器的详细信息和示例。
监听器 API 表
本节展示了一个快速参考表,显示了每个监听器、其适配器类(如果有)以及其方法。
解决常见事件处理问题
如果您遇到与事件处理相关的难以调试的问题,您可能会在这里找到解决方案。
问题和练习
尝试这些问题和练习,测试您在本课程中学到的知识。
如果您有兴趣使用 JavaFX 创建 GUI,请参阅处理 JavaFX 事件。
事件监听器简介
原文:
docs.oracle.com/javase/tutorial/uiswing/events/intro.html
如果你已经阅读了任何组件的操作指南页面,你可能已经了解了事件监听器的基础知识。
让我们看一个可能的最简单的事件处理示例。它被称为蜂鸣器,当你点击按钮时会发出蜂鸣声。
点击“启动”按钮以使用Java™ Web Start运行蜂鸣器(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
您可以在Beeper.java
中找到整个程序。以下是实现按钮事件处理的代码:
public class Beeper ... implements ActionListener {
...
*//where initialization occurs:*
button.addActionListener(this);
...
public void actionPerformed(ActionEvent e) {
*...//Make a beep sound...*
}
}
Beeper
类实现了ActionListener
接口,其中包含一个方法:actionPerformed
。由于Beeper
实现了ActionListener
,一个Beeper
对象可以注册为按钮触发的动作事件的监听器。一旦Beeper
使用Button
的addActionListener
方法注册,每次点击按钮时都会调用Beeper
的actionPerformed
方法。
更复杂的示例
事件模型在前面的示例中展示得很简单,但非常强大和灵活。任意数量的事件监听器对象可以监听来自任意数量的事件源对象的各种事件。例如,一个程序可以为每个事件源创建一个监听器。或者一个程序可以对来自所有源的所有事件拥有一个单一的监听器。一个程序甚至可以对来自单个事件源的单一类型的事件拥有多个监听器。
多个监听器可以注册以接收特定类型的事件来自特定来源。此外,同一个监听器可以监听来自不同对象的通知。
每个事件都由一个对象表示,该对象提供有关事件的信息并标识事件源。事件源通常是组件或模型,但其他类型的对象也可以是事件源。
每当你想要检测特定组件的事件时,请首先查看该组件的操作指南部分。组件操作指南部分的列表在这里。操作指南部分提供了处理你可能关心的事件的示例。例如,在如何使用颜色选择器中,你会找到一个编写更改监听器以跟踪颜色选择器中颜色更改的示例。
以下示例演示了事件监听器可以注册在多个对象上,并且相同的事件可以发送给多个监听器。该示例包含两个事件源(JButton
实例)和两个事件监听器。其中一个事件监听器(名为MultiListener
的类的实例)监听来自两个按钮的事件。当它接收到事件时,它将事件的“动作命令”(设置为按钮标签上的文本)添加到顶部文本区域。第二个事件监听器(名为Eavesdropper
的类的实例)仅监听一个按钮上的事件。当它接收到事件时,它将动作命令添加到底部文本区域。
试试这个:
-
点击启动按钮以使用Java™ Web Start运行 MultiListener(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
-
点击Blah blah blah按钮。只有
MultiListener
对象被注册来监听这个按钮。 -
点击You do not say!按钮。
MultiListener
对象和Eavesdropper
对象都被注册来监听这个按钮。
你可以在MultiListener.java
中找到整个程序。这里是实现按钮事件处理的代码:
public class MultiListener ... implements ActionListener {
...
*//where initialization occurs:*
button1.addActionListener(this);
button2.addActionListener(this);
button2.addActionListener(new Eavesdropper(bottomTextArea));
}
public void actionPerformed(ActionEvent e) {
topTextArea.append(e.getActionCommand() + newline);
}
}
class Eavesdropper implements ActionListener {
...
public void actionPerformed(ActionEvent e) {
myTextArea.append(e.getActionCommand() + newline);
}
}
在上述代码中,MultiListener
和Eavesdropper
都实现了ActionListener
接口,并使用JButton
的addActionListener
方法注册为动作监听器。这两个类的actionPerformed
方法的实现类似:它们只是将事件的动作命令添加到文本区域中。
编写事件监听器的一般信息
当设计程序时,您可能希望在一个不是公共的类中实现您的事件监听器,而是在更隐蔽的地方。私有实现是更安全的实现。
本节讨论了在应用程序中实现事件处理程序时要牢记的几个设计考虑。然后我们向您介绍了事件对象,这些小对象描述了每个事件。特别是,我们谈到了低级事件和语义事件的概念,建议您在可能的情况下优先选择语义事件。本节的其余部分讨论了您可能在一些事件监听器中使用的实现技术,或者在其他人或 GUI 构建器创建的事件监听器中看到的技术。
-
设计考虑
-
获取事件信息:事件对象
-
概念:低级事件和语义事件
-
事件适配器
-
获取事件信息:事件对象
-
原文:
docs.oracle.com/javase/tutorial/uiswing/events/generalrules.html
设计考虑
关于事件监听器最重要的规则是它们应该执行得非常快速。因为所有绘图和事件监听方法都在同一个线程中执行,一个慢速的事件监听器方法会使程序看起来无响应且重绘速度缓慢。如果需要作为事件结果执行一些耗时操作,请通过启动另一个线程(或以某种方式发送请求到另一个线程)来执行操作。有关使用线程的帮助,请参阅 Swing 中的并发性。
您有许多选择来实现事件监听器。我们无法推荐特定的方法,因为一个解决方案并不适用于所有情况。但是,我们可以给您一些提示,并展示一些您可能看到的技术,即使您在程序中没有使用相同的解决方案。
例如,您可能选择为不同类型的事件监听器实现单独的类。这种架构易于维护,但许多类也可能意味着性能降低。
内部类和匿名内部类
如果您有一种非常特定的简单事件监听器,您可能可以通过使用EventHandler
类而根本不创建类来避免。
EventHandler 类
每个事件监听器方法都有一个参数,即从EventObject
类继承的对象。尽管参数总是继承自EventObject
,但其类型通常更加精确地指定。例如,处理鼠标事件的方法的参数是MouseEvent
的实例,其中MouseEvent
是EventObject
的间接子类。
EventObject
类定义了一个非常有用的方法:
Object getSource()
返回触发事件的对象。
注意,getSource
方法返回一个 Object
。事件类有时定义类似于 getSource
的方法,但返回类型更受限制。例如,ComponentEvent
类定义了一个 getComponent
方法,就像 getSource
一样返回触发事件的对象。不同之处在于 getComponent
总是返回一个 Component
。每个事件监听器的操作页面都会提到你应该使用 getSource
还是其他方法来获取事件源。
通常,事件类定义了返回有关事件信息的方法。例如,你可以查询 MouseEvent
对象以获取有关事件发生位置、用户点击次数、按下的修改键等信息。
概念:低级事件和语义事件
事件可以分为两组:低级 事件和 语义 事件。低级事件代表窗口系统发生或低级输入。其他一切都是语义事件。
低级事件的示例包括鼠标和键盘事件,这两者都直接由用户输入引起。语义事件的示例包括动作和项目事件。语义事件可能由用户输入触发;例如,当用户点击按钮时,按钮通常会触发动作事件,当用户按下 Enter 键时,文本字段会触发动作事件。然而,有些语义事件根本不是由低级事件触发的。例如,当表模型从数据库接收新数据时,可能会触发表模型事件。
尽可能监听语义事件而不是低级事件。这样,你可以使你的代码尽可能健壮和可移植。例如,监听按钮上的动作事件,而不是鼠标事件,意味着当用户尝试使用键盘替代方案或外观特定手势激活按钮时,按钮将做出适当反应。处理复合组件(如组合框)时,必须坚持使用语义事件,因为你没有可靠的方法在所有可能用于形成复合组件的外观特定组件上注册监听器。
事件适配器
一些监听器接口包含多个方法。例如,MouseListener
接口包含五个方法:mousePressed
、mouseReleased
、mouseEntered
、mouseExited
和 mouseClicked
。即使你只关心鼠标点击,如果你的类直接实现了 MouseListener
,那么你必须实现所有五个 MouseListener
方法。对于你不关心的事件,方法可以留空。这里是一个例子:
//An example that implements a listener interface directly.
public class MyClass implements MouseListener {
...
someObject.addMouseListener(this);
...
/* Empty method definition. */
public void mousePressed(MouseEvent e) {
}
/* Empty method definition. */
public void mouseReleased(MouseEvent e) {
}
/* Empty method definition. */
public void mouseEntered(MouseEvent e) {
}
/* Empty method definition. */
public void mouseExited(MouseEvent e) {
}
public void mouseClicked(MouseEvent e) {
*...//Event listener implementation goes here...*
}
}
一系列空方法体的结果可能会使代码更难阅读和维护。为了帮助您避免实现空方法体,API 通常为每个具有多个方法的监听器接口包含一个适配器类。(Listener API Table 列出了所有监听器及其适配器。)例如,MouseAdapter
类实现了MouseListener
接口。适配器类实现了其接口所有方法的空版本。
要使用适配器,您创建一个它的子类并仅重写感兴趣的方法,而不是直接实现监听器接口的所有方法。以下是修改前述代码以扩展MouseAdapter
的示例。通过扩展MouseAdapter
,它继承了MouseListener
包含的所有五个方法的空定义。
/*
* An example of extending an adapter class instead of
* directly implementing a listener interface.
*/
public class MyClass extends MouseAdapter {
...
someObject.addMouseListener(this);
...
public void mouseClicked(MouseEvent e) {
*...//Event listener implementation goes here...*
}
}
内部类和匿名内部类
如果您想使用适配器类,但不希望您的公共类继承自适配器类怎么办?例如,假设您编写了一个小程序,并且希望您的Applet
子类包含一些处理鼠标事件的代码。由于 Java 语言不允许多重继承,您的类不能同时扩展Applet
和MouseAdapter
类。解决方案是定义一个内部类,即在Applet
子类内部扩展MouseAdapter
类。
内部类还可以用于直接实现一个或多个接口的事件监听器。
//An example of using an inner class.
public class MyClass extends Applet {
...
someObject.addMouseListener(new MyAdapter());
...
class MyAdapter extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
*...//Event listener implementation goes here...*
}
}
}
性能注意:
在考虑是否使用内部类时,请记住应用程序启动时间和内存占用量通常与加载的类数量成正比。您创建的类越多,程序启动时间越长,占用的内存也越多。作为应用程序开发人员,您必须在其他设计约束条件下平衡这一点。我们并不建议您将应用程序变成一个单一的庞大类,希望缩短启动时间和内存占用量,这将导致不必要的麻烦和维护负担。
您可以创建一个不指定名称的内部类,这被称为匿名内部类。虽然乍一看可能会觉得奇怪,但匿名内部类可以使您的代码更易于阅读,因为类是在引用它的地方定义的。但是,您需要权衡方便性与增加类数量可能带来的性能影响。
以下是使用匿名内部类的示例:
//An example of using an anonymous inner class.
public class MyClass extends Applet {
...
someObject.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
*...//Event listener implementation goes here...*
}
});
...
}
}
注意:
匿名内部类的一个缺点是它们无法被长期持久性机制所看到。有关更多信息,请参阅JavaBeans™ package的 API 文档以及 JavaBeans trail 中的 Bean Persistence 课程。
内部类即使需要访问封闭类的私有实例变量,也可以正常工作。只要不将内部类声明为static
,内部类就可以像在包含类中一样引用实例变量和方法。要使局部变量对内部类可用,只需将变量的副本保存为final
局部变量。
要引用封闭实例,可以使用*EnclosingClass*.this
。有关内部类的更多信息,请参见 Nested Classes。
EventHandler 类
EventHandler
类支持简单、一语句事件监听器的动态生成。虽然EventHandler
仅对一种极其简单的事件监听器类型有用,但由于两个原因值得一提。它适用于:
-
创建一个事件监听器,持久性可以看到,但不会用事件监听器接口和方法堵塞自己的类。
-
不增加应用程序中定义的类的数量可以提高性能。
手动创建EventHandler
很困难。EventHandler
必须小心构建。如果出错,编译时不会通知您,它会在运行时抛出一个晦涩的异常。因此,最好由 GUI 构建器创建EventHandler
。EventHandler
应该仔细记录。否则,您可能会产生难以阅读的代码。
EventHandler
类旨在供交互式工具使用,例如应用程序构建器,允许开发人员在 bean 之间建立连接。通常,连接是从用户界面 bean(事件源)到应用程序逻辑 bean(目标)进行的。这种连接的最有效方式是将应用程序逻辑与用户界面隔离开来。例如,从 JCheckBox 到接受布尔值的方法的连接的EventHandler
可以处理提取复选框状态并直接传递给方法,使方法与用户界面层隔离开来。
内部类是处理用户界面事件的另一种更通用的方式。EventHandler
类仅处理使用内部类可能实现的一部分功能。但是,EventHandler
与长期持久性方案的配合效果比内部类更好。此外,在大型应用程序中,使用EventHandler
可以减少应用程序的磁盘和内存占用。
使用EventHandler
的示例EventHandler
的最简单用法是安装一个调用目标对象上没有参数的方法的监听器。在以下示例中,我们创建一个 ActionListener,调用javax.swing.JFrame
实例上的toFront
方法。
myButton.addActionListener(
(ActionListener)EventHandler.create(ActionListener.class, frame, "toFront"));
当按下myButton
时,语句frame.toFront()
将被执行。通过定义ActionListener
接口的新实现并将其实例添加到按钮中,也可以获得相同的效果,并获得一些额外的编译时类型安全性:
//Equivalent code using an inner class instead of EventHandler.
myButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
frame.toFront();
}
});
下一个最简单的EventHandler
的用法是从监听器接口的方法的第一个参数(通常是事件对象)中提取一个属性值,并将其用于设置目标对象的属性值。在下面的示例中,我们创建了一个ActionListener
,将目标对象(myButton)的nextFocusableComponent
属性设置为事件的source
属性的值。
EventHandler.create(ActionListener.class, myButton, "nextFocusableComponent", "source")
这对应于以下内部类实现:
//Equivalent code using an inner class instead of EventHandler.
new ActionListener() {
public void actionPerformed(ActionEvent e) {
myButton.setNextFocusableComponent((Component)e.getSource());
}
}
也可以创建一个仅将传入事件对象传递给目标动作的EventHandler
。如果第四个EventHandler.create
参数是一个空字符串,那么事件就会被简单地传递下去:
EventHandler.create(ActionListener.class, target, "doActionEvent", "")
这对应于以下内部类实现:
//Equivalent code using an inner class instead of EventHandler.
new ActionListener() {
public void actionPerformed(ActionEvent e) {
target.doActionEvent(e);
}
}
可能最常见的EventHandler
用法是从事件对象的源中提取一个属性值,并将此值设置为目标对象的属性值。在下面的示例中,我们创建了一个ActionListener
,将目标对象的label
属性设置为事件源(事件的source
属性的值)的text
属性的值。
EventHandler.create(ActionListener.class, myButton, "label", "source.text")
这对应于以下内部类实现:
//Equivalent code using an inner class instead of EventHandler.
new ActionListener {
public void actionPerformed(ActionEvent e) {
myButton.setLabel(((JTextField)e.getSource()).getText());
}
}
Swing 组件支持的监听器
原文:
docs.oracle.com/javase/tutorial/uiswing/events/eventsandcomponents.html
您可以通过查看可以在组件上注册的事件监听器的类型来了解组件可以触发哪些事件。例如,JComboBox
类定义了以下监听器注册方法:
-
addActionListener
-
addItemListener
-
addPopupMenuListener
因此,组合框除了继承自JComponent
的监听器方法外,还支持动作、项目和上下文菜单监听器。
Swing 组件支持的监听器分为两类:
-
所有 Swing 组件都支持的监听器
-
其他 Swing 组件支持的监听器
所有 Swing 组件都支持的监听器
因为所有 Swing 组件都是从 AWT Component
类继承的,您可以在任何 Swing 组件上注册以下监听器:
组件监听器
监听组件的大小、位置或可见性的变化。
焦点监听器
监听组件是否获得或失去键盘焦点。
键盘监听器
监听键盘按键;键盘事件仅由当前具有键盘焦点的组件触发。
鼠标监听器
监听鼠标点击、鼠标按下、鼠标释放以及鼠标移动进入或离开组件的绘图区域。
鼠标移动监听器
监听鼠标光标在组件上的位置变化。
鼠标滚轮监听器
监听鼠标滚轮在组件上的移动。
监听组件的包含层次结构的更改事件。
监听组件的包含层次结构的移动和调整事件的变化。
所有 Swing 组件都是从 AWT Container
类继承的,但其中许多并不用作容器。因此,从技术上讲,任何 Swing 组件都可以触发容器事件,通知监听器组件已被添加到容器或从容器中移除。然而,实际上,只有容器(如面板和框架)和复合组件(如组合框)通常会触发容器事件。
JComponent
提供对另外三种监听器类型的支持。您可以注册一个祖先监听器,以便在组件的包含祖先被添加到容器、移除、隐藏、显示或移动时收到通知。这种监听器类型是一个实现细节,早于层次监听器。
另外两种监听器类型是 Swing 组件符合 JavaBeans 规范的一部分。这意味着 Swing 组件支持绑定和约束属性,并通知监听器属性的更改。属性更改监听器 监听绑定属性的更改,并被几个 Swing 组件使用,例如 格式化文本字段,以跟踪组件的绑定属性的更改。此外,属性更改监听器以及 可否决更改监听器 被构建工具用于监听约束属性的更改。有关更多信息,请参考 JavaBeans 路径中的 属性 课程。
Swing 组件支持的其他监听器
以下表格列出了 Swing 组件及其支持的专用监听器,不包括所有 Component
、Container
或 JComponent
支持的监听器。在许多情况下,事件直接从组件中触发。在其他情况下,事件是从组件的数据或选择模型中触发的。要了解您感兴趣的特定组件和监听器的详细信息,请首先转到组件的操作指南部分,然后如有必要再转到监听器的操作指南部分。
此表列出了带有其专用监听器的 Swing 组件
组件 | 动作监听器 | 插入符监听器 | 更改监听器 | 文档监听器, 可撤销编辑监听器 | 项目监听器 | 列表选择监听器 | 窗口监听器 | 其他类型的监听器 |
---|---|---|---|---|---|---|---|---|
按钮 | ![]() |
![]() |
![]() |
|||||
复选框 | ![]() |
![]() |
![]() |
|||||
颜色选择器 | ![]() |
|||||||
组合框 | ![]() |
![]() |
||||||
对话框 | ![]() |
|||||||
编辑窗格 | ![]() |
![]() |
超链接 | |||||
文件选择器 | ![]() |
|||||||
格式化文本字段 | ![]() |
![]() |
![]() |
|||||
框架 | ![]() |
|||||||
内部框架 | 内部框架 | |||||||
列表 | ![]() |
列表数据 | ||||||
菜单 | 菜单 | |||||||
菜单项 | ![]() |
![]() |
![]() |
菜单键 菜单拖动鼠标 | ||||
选项窗格 | ||||||||
密码字段 | ![]() |
![]() |
![]() |
|||||
弹出菜单 | 弹出菜单 | |||||||
进度条 | ![]() |
|||||||
单选按钮 | ![]() |
![]() |
![]() |
|||||
滑块 | ![]() |
|||||||
微调器 | ![]() |
|||||||
选项卡面板 | ![]() |
| 表格 | | | | | | | | 表格模型 表格列模型
单元格编辑器 |
文本区域 | ![]() |
![]() |
||||||
---|---|---|---|---|---|---|---|---|
文本字段 | ![]() |
![]() |
![]() |
|||||
文本面板 | ![]() |
![]() |
超链接 | |||||
切换按钮 | ![]() |
![]() |
![]() |
| 树 | | | | | | | | 树展开 树将展开
树模型
树选择 |
视口(被滚动窗格使用) | ![]() |
---|
实现常见事件的监听器
原文:
docs.oracle.com/javase/tutorial/uiswing/events/handling.html
以下部分详细介绍了实现特定类型事件监听器的细节。我们并没有为您可以编写的每一种事件监听器都提供操作指南。相反,我们涵盖了我们认为您最有可能需要的监听器。如果您对其他监听器感兴趣,您可以在监听器 API 表中找到一些信息。
-
如何编写动作监听器
-
如何编写插入符监听器
-
如何编写变更监听器
-
如何编写组件监听器
-
如何编写容器监听器
-
如何编写文档监听器
-
如何编写焦点监听器
-
如何编写内部窗口监听器
-
如何编写项目监听器
-
如何编写键盘监听器
-
如何编写列表数据监听器
-
如何编写列表选择监听器
-
如何编写鼠标监听器
-
如何编写鼠标移动监听器
-
如何编写鼠标滚轮监听器
-
如何编写属性更改监听器
-
如何编写表格模型监听器
-
如何编写树展开监听器
-
如何编写树模型监听器
-
如何编写树选择监听器
-
如何编写树展开监听器
-
如何编写可撤销编辑监听器
-
如何编写窗口监听器
如何编写一个动作监听器
原文:
docs.oracle.com/javase/tutorial/uiswing/events/actionlistener.html
动作监听器可能是最容易实现的事件处理程序,也是最常见的。您实现一个动作监听器来定义用户执行某些操作时应该执行的操作。
每当用户执行操作时,都会发生一个动作事件。例如:当用户点击一个按钮,选择一个菜单项,在文本字段中按 Enter 键。结果是向所有注册在相关组件上的动作监听器发送一个actionPerformed
消息。
要编写一个动作监听器,请按照以下步骤进行:
-
声明一个事件处理程序类,并指定该类要么实现 ActionListener 接口,要么扩展实现 ActionListener 接口的类。例如:
public class MyClass implements ActionListener {
-
在一个或多个组件上注册事件处理程序类的实例作为监听器。例如:
someComponent.addActionListener(instanceOfMyClass);
-
包含实现监听器接口中方法的代码。例如:
public void actionPerformed(ActionEvent e) { ...//code that reacts to the action... }
通常,要检测用户何时点击屏幕按钮(或执行键盘等效操作),程序必须有一个实现 ActionListener 接口的对象。程序必须使用 addActionListener 方法将此对象注册为按钮(事件源)的动作监听器。当用户点击屏幕按钮时,按钮会触发一个动作事件。这将导致调用动作监听器的 actionPerformed 方法(ActionListener 接口中的唯一方法)。该方法的唯一参数是一个 ActionEvent 对象,提供有关事件及其来源的信息。
让我们编写一个简单的程序,显示用户点击按钮的次数。首先,这是设置 TextField、按钮和 numClicks 变量的代码:
public class AL extends Frame implements WindowListener,ActionListener {
TextField text = new TextField(20);
Button b;
private int numClicks = 0;
在上面的示例中,事件处理程序类是 AL,它实现了 ActionListener。
我们想要处理按钮点击事件,因此我们将一个动作监听器添加到按钮 b 上,如下所示:
b = new Button("Click me");
b.addActionListener(this);
在上述代码中,按钮 b 是一个组件,事件处理程序类 AL 的实例已注册在其上。
现在,我们想要显示文本,显示用户点击按钮的次数。我们可以通过以下代码实现:
public void actionPerformed(ActionEvent e) {
numClicks++;
text.setText("Button Clicked " + numClicks + " times");
现在,当用户点击按钮 b 时,按钮会触发一个动作事件,调用动作监听器的 actionPerformed 方法。每次用户按下按钮时,numClicks 变量都会被追加,并且消息会显示在文本字段中。
这里是完整的程序(AL.java):
import java.awt.*;
import java.awt.event.*;
public class AL extends Frame implements WindowListener,ActionListener {
TextField text = new TextField(20);
Button b;
private int numClicks = 0;
public static void main(String[] args) {
AL myWindow = new AL("My first window");
myWindow.setSize(350,100);
myWindow.setVisible(true);
}
public AL(String title) {
super(title);
setLayout(new FlowLayout());
addWindowListener(this);
b = new Button("Click me");
add(b);
add(text);
b.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
numClicks++;
text.setText("Button Clicked " + numClicks + " times");
}
public void windowClosing(WindowEvent e) {
dispose();
System.exit(0);
}
public void windowOpened(WindowEvent e) {}
public void windowActivated(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
public void windowClosed(WindowEvent e) {}
}
更多示例:Beeper
程序示例可以在本教程的事件简介中找到,事件监听器简介。你可以在 Beeper.java
中找到整个程序。在该部分描述的另一个示例,MultiListener.java
,有两个动作源和两个动作监听器,一个监听器同时监听两个源,另一个只监听一个。
动作监听器 API
ActionListener 接口
因为 ActionListener
只有一个方法,所以它没有对应的适配器类。
方法 | 目的 |
---|---|
actionPerformed(actionEvent) | 在用户执行动作后立即调用。 |
ActionEvent 类
方法 | 目的 |
---|---|
String getActionCommand() | 返回与此动作关联的字符串。大多数可以触发动作事件的对象支持一个名为 setActionCommand 的方法,让您设置这个字符串。 |
| int getModifiers() | 返回一个整数,表示用户在动作事件发生时按下的修饰键。您可以使用 ActionEvent
定义的常量 SHIFT_MASK
、CTRL_MASK
、META_MASK
和 ALT_MASK
来确定按下了哪些键。例如,如果用户 Shift-选择一个菜单项,则以下表达式不为零:
actionEvent.getModifiers() & ActionEvent.SHIFT_MASK
|
Object getSource() (在 java.util.EventObject 中) |
返回触发事件的对象。 |
---|
使用动作监听器的示例
以下表格列出了一些使用动作监听器的示例。
示例 | 描述位置 | 注释 |
---|---|---|
Beeper |
本节和 事件监听器简介 | 包含一个按钮和一个在点击按钮时发出哔声的动作监听器。 |
MultiListener |
事件监听器简介 | 在一个按钮上注册两个不同的动作监听器。同时在两个不同的按钮上注册相同的动作监听器。 |
RadioButtonDemo |
如何使用单选按钮 | 在五个单选按钮上注册相同的动作监听器。监听器使用 getActionCommand 方法来确定哪个单选按钮触发了事件。 |
MenuDemo |
如何使用菜单 | 展示如何监听菜单项的动作事件。 |
TextDemo |
如何使用文本字段 | 在文本字段上注册动作监听器的小程序。 |
IconDemo |
如何使用图标 | 在动作监听器中加载图像。由于加载图像可能需要一些时间,该程序使用SwingWorker 在后台线程中加载图像。 |
TableDialogEditDemo |
如何使用表格 | 通过颜色选择对话框的确定按钮上的工厂方法注册动作监听器。 |
SliderDemo |
如何使用滑块 | 在控制动画循环的计时器上注册动作监听器。 |
如何编写插入符监听器
原文:
docs.oracle.com/javase/tutorial/uiswing/events/caretlistener.html
插入符事件发生在文本组件中的插入符(指示插入点的光标)移动或文本组件中的选择更改时。例如,当文本组件插入或删除文本时,文本组件的文档可以启动插入符事件。您可以使用addCaretListener
方法将插入符监听器附加到任何JTextComponent
子类的实例上。
注意:检测插入符更改的另一种方法是直接将监听器附加到插入符对象本身,而不是附加到管理插入符的文本组件。插入符会触发更改事件(而不是插入符事件),因此您需要编写一个更改监听器而不是插入符监听器。
这是一个名为TextComponentDemo
的应用程序中的插入符事件处理代码:
...
*//where initialization occurs*
CaretListenerLabel caretListenerLabel =
new CaretListenerLabel("Caret Status");
...
textPane.addCaretListener(caretListenerLabel);
...
protected class CaretListenerLabel extends JLabel
implements CaretListener
{
...
//Might not be invoked from the event dispatching thread.
public void caretUpdate(CaretEvent e) {
displaySelectionInfo(e.getDot(), e.getMark());
}
//This method can be invoked from any thread. It
//invokes the setText and modelToView methods, which
//must run in the event dispatching thread. We use
//invokeLater to schedule the code for execution
//in the event dispatching thread.
protected void displaySelectionInfo(final int dot,
final int mark) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (dot == mark) { // no selection
...
}
});
}
}
}
注意:caretUpdate
方法不能保证在事件分派线程中调用。要在caretUpdate
中使用更新 GUI 的任何方法,需要特殊处理以确保它们在事件分派线程上执行。您可以通过将代码包装在Runnable
中并在该Runnable
上调用SwingUtilities.invokeLater
来实现这一点。
您可以在使用 Swing 组件示例索引中找到TextComponentDemo
的源文件链接。有关程序的插入符监听器方面的讨论,请参阅监听插入符和选择更改中的文本组件功能。
插入符监听器 API
插入符监听器接口
因为CaretListener
只有一个方法,所以它没有相应的适配器类。
方法 | 目的 |
---|---|
caretUpdate(CaretEvent) | 当监听组件中的插入符移动或监听组件中的选择更改时调用。 |
插入符事件类
方法 | 目的 |
---|---|
int getDot() | 返回插入符的当前位置。如果选择了文本,则插入符标记选择的一端。 |
int getMark() | 返回选择的另一端。如果没有选择任何内容,则此方法返回的值等于getDot 返回的值。请注意,插入符不一定小于标记。 |
Object getSource()(在java.util.EventObject 中) |
返回触发事件的对象。 |
使用插入符监听器的示例
下表列出了使用插入符监听器的示例。
示例 | 描述位置 | 备注 |
---|---|---|
TextComponentDemo |
监听插入符和选择更改 | 使用监听器标签显示插入符和选择状态。 |
如何编写更改监听器
原文:
docs.oracle.com/javase/tutorial/uiswing/events/changelistener.html
更改监听器类似于属性更改监听器。更改监听器在一个对象上注册 —— 通常是一个组件,但也可以是另一个对象,比如一个模型 —— 当对象发生更改时,监听器会收到通知。与属性更改监听器的主要区别在于,更改监听器不会通知什么发生了变化,而只是通知源对象发生了变化。因此,当只需要知道对象以任何方式发生了变化时,更改监听器是最有用的。
几个 Swing 组件(包括 JTabbedPane、JViewPort)依赖更改事件进行基本功能 —— 滑块、颜色选择器和微调器。要了解滑块中的值何时更改,需要注册更改监听器。同样,需要在颜色选择器上注册更改监听器,以便在用户选择新颜色时获得通知。您需要在微调器上注册更改监听器,以便在微调器的值更改时收到通知。
这是一个滑块更改事件处理代码的示例:
*//...where initialization occurs:*
framesPerSecond.addChangeListener(new SliderListener());
...
class SliderListener implements ChangeListener {
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider)e.getSource();
if (!source.getValueIsAdjusting()) {
int fps = (int)source.getValue();
...
}
}
}
你可以在使用 Swing 组件示例索引中找到SliderDemo
的源文件。
更改监听器 API
ChangeListener 接口
因为ChangeListener
只有一个方法,所以它没有对应的适配器类。
方法 | 目的 |
---|---|
stateChanged(ChangeEvent) | 当被监听组件改变状态时调用。 |
ChangeEvent 类
方法 | 目的 |
---|---|
Object getSource() (在 java.util.EventObject 中) |
返回触发事件的对象。 |
使用更改监听器的示例
以下表格列出了使用更改监听器的示例。
示例 | 描述位置 | 注释 |
---|---|---|
SliderDemo 和 SliderDemo2 |
如何使用滑块 | 在控制动画速度的滑块上注册一个更改监听器。更改监听器会忽略更改事件,直到用户释放滑块。 |
ColorChooserDemo 和 ColorChooserDemo2 |
如何使用颜色选择器 | 在颜色选择器的选择模型上使用更改监听器,以便在用户更改当前颜色时获得通知。 |
SpinnerDemo3 |
检测 Spinner 值的变化 在 如何使用 Spinners 中。 | 使用一个变化监听器在日期字段 Spinner 上改变文本颜色,当 Spinner 的日期发生变化时。 |
SpinnerDemo4 |
检测 Spinner 值的变化 在 如何使用 Spinners 中。 | 使用一个变化监听器在 Spinner 上循环灰度,当 Spinner 的值发生变化时。 |
| ConverterRangeModel
及其子类,
FollowerRangeModel
| 如何使用模型 | 为 Converter
演示中使用的滑块实现自定义模型。两个模型在必要时都明确触发变化事件。
如何编写组件监听器
原文:
docs.oracle.com/javase/tutorial/uiswing/events/componentlistener.html
组件监听器是用于接收组件事件的监听器接口。组件是具有图形表示的对象,可以显示在屏幕上并与用户交互。一些组件的示例是典型图形用户界面中的按钮、复选框和滚动条。
对于对处理组件事件感兴趣的类,要么实现此接口及其所有包含的方法,要么扩展抽象的 ComponentAdapter 类,只覆盖感兴趣的方法。然后,从该类创建的监听器对象将使用组件的 addComponentListener 方法注册到组件上。当组件的大小、位置或可见性发生变化时,监听器对象中的相关方法将被调用,并将 ComponentEvent 传递给它。
一个或多个组件事件是由Component
对象在组件被隐藏、显示、移动或调整大小后立即触发的。
组件隐藏和组件显示事件仅在调用Component
的setVisible
方法时发生。例如,窗口可能被缩小为图标(图标化),而不会触发组件隐藏事件。
要编写一个简单的组件监听器程序,请按照以下步骤进行:
-
声明一个实现了组件监听器的类。例如:
public class ComponentEventDemo ... implements ComponentListener
-
确定您要捕获事件的组件。例如:面板、标签、复选框等。
-
将组件监听器添加到已识别的组件中。例如:
.... label.addComponentListener(this); ..... checkbox.addComponentListener(this); .... panel.addComponentListener(this); ... frame.addComponentListener(this);
-
最后,通过使用组件监听器的四种方法来捕获这些组件的不同事件,如下所示:
public void componentHidden(ComponentEvent e) { displayMessage(e.getComponent().getClass().getName() + " --- Hidden"); } public void componentMoved(ComponentEvent e) { displayMessage(e.getComponent().getClass().getName() + " --- Moved"); } public void componentResized(ComponentEvent e) { displayMessage(e.getComponent().getClass().getName() + " --- Resized "); } public void componentShown(ComponentEvent e) { displayMessage(e.getComponent().getClass().getName() + " --- Shown"); }
以下示例演示了组件事件。窗口包含一个带有标签和复选框的面板。复选框控制标签是否可见。文本区域在窗口、面板、标签或复选框触发组件事件时显示消息。
试一试:
-
点击“启动”按钮以使用Java™ Web Start运行 ComponentEventDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
-
当窗口出现时,一个或多个组件显示事件已被触发。
-
单击复选框以隐藏标签。
标签触发了一个组件隐藏事件。面板触发了组件移动和组件调整大小事件。复选框触发了一个组件移动事件。
-
再次点击复选框以显示标签。
标签触发了一个组件显示事件。面板触发了组件移动和组件调整大小事件。复选框触发了一个组件移动事件。
-
最小化然后还原窗口。
你不会收到组件隐藏或显示事件。如果想要收到最小化事件的通知,应该使用窗口监听器或窗口状态监听器。
-
调整窗口大小。
你将会看到所有四个组件——标签、复选框、面板和框架——发出了组件调整大小(可能还有组件移动)事件。如果框架和面板的布局管理器没有使每个组件尽可能宽,那么面板、标签和复选框就不会被调整大小。
你可以在ComponentEventDemo.java
中找到演示代码。这里只是与处理组件事件相关的代码:
public class ComponentEventDemo ... implements ComponentListener {
static JFrame frame;
JLabel label;
...
public ComponentEventDemo() {
...
JPanel panel = new JPanel(new BorderLayout());
label = new JLabel("This is a label", JLabel.CENTER);
label.addComponentListener(this);
panel.add(label, BorderLayout.CENTER);
JCheckBox checkbox = new JCheckBox("Label visible", true);
checkbox.addComponentListener(this);
panel.add(checkbox, BorderLayout.PAGE_END);
panel.addComponentListener(this);
...
frame.addComponentListener(this);
}
...
public void componentHidden(ComponentEvent e) {
displayMessage(e.getComponent().getClass().getName() + " --- Hidden");
}
public void componentMoved(ComponentEvent e) {
displayMessage(e.getComponent().getClass().getName() + " --- Moved");
}
public void componentResized(ComponentEvent e) {
displayMessage(e.getComponent().getClass().getName() + " --- Resized ");
}
public void componentShown(ComponentEvent e) {
displayMessage(e.getComponent().getClass().getName() + " --- Shown");
}
public static void main(String[] args) {
...
//Create and set up the window.
frame = new JFrame("ComponentEventDemo");
...
JComponent newContentPane = new ComponentEventDemo();
frame.setContentPane(newContentPane);
...
}
}
组件监听器 API
组件监听器接口
所有这些方法也在适配器类ComponentAdapter
中。
方法 | 目的 |
---|---|
componentHidden(ComponentEvent) | 在被监听的组件由于调用setVisible 方法而被隐藏后调用。 |
componentMoved(ComponentEvent) | 在被监听的组件相对于其容器移动后调用。例如,如果移动了一个窗口,窗口会触发一个组件移动事件,但它包含的组件不会。 |
componentResized(ComponentEvent) | 在被监听的组件的大小(矩形边界)发生变化后调用。 |
componentShown(ComponentEvent) | 在被监听的组件由于调用setVisible 方法而变为可见后调用。 |
组件事件类
方法 | 目的 |
---|---|
Component getComponent() | 返回触发事件的组件。你可以使用这个方法代替getSource 方法。 |
使用组件监听器的示例
下表列出了使用组件监听器的示例。
示例 | 描述位置 | 注释 |
---|---|---|
ComponentEventDemo |
本节 | 报告了发生在多个组件上的所有组件事件,以展示组件事件触发的情况。 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
2023-04-12 NumPy 初学者指南中文第三版:11~14
2023-04-12 NumPy 初学者指南中文第三版:6~10
2023-04-12 NumPy 初学者指南中文第三版:1~5
2023-04-12 NumPy 秘籍中文第二版:11~12
2023-04-12 NumPy 秘籍中文第二版:6~10
2023-04-12 NumPy 秘籍中文第二版:1~5
2023-04-12 NumPy 秘籍中文第二版:十二、使用 NumPy 进行探索性和预测性数据分析