JRuby 中的 Swing GUI。






4.50/5 (3投票s)
如何在 JRuby 中创建基于 Swing 的 GUI。
引言
尽管 Ruby 是一种脚本语言,但其 GUI 工具包并不少。除了其标准工具包 Tk 之外,许多其他 GUI 工具包都有 Ruby 绑定(如 FXRuby、qtRuby 和 wxRuby)。其他工具包在此列出
除了 Shoes(由 _why 设计)之外,其他 GUI 工具包都是独立于 Ruby 开发的,并且它们各自的 Ruby 绑定可能实现也可能不实现 GUI 工具包的最新功能。例如,qtRuby 可用于 QT4,但不能用于 QT5(工具包的最新版本)。
Java 为 Ruby GUI 开发提供了另一组选项。使用 JRuby,您可以访问 Java 标准 GUI 模块 Swing 和 SWT,以及 Java FX(Java GUI 系列的最新成员)。
使用 Swing 进行 Ruby GUI 开发的优点是它可以在安装了 Java 的任何地方运行。另一个优点是您的 Ruby 脚本可以免费使用所有可用的 Java 库。缺点是您需要安装 JRuby,即在 JVM 上运行的 Ruby 版本。
一旦您决定为您的 Ruby 应用程序 GUI 使用 Java,您仍然有多种选择。您可以使用纯 Swing 或其 JRuby 包装器之一(如 MonkeyBars 或 Cheri - 这些包装器处于不同程度的完整性)。您可以使用裸的 SWT。最后,您可以使用 Limelight、Profligacy 或 Swiby 等项目 - 所有这些项目都试图使 JRuby GUI 开发更加愉快,但它们也处于不同程度的完整性。
对于 Web 应用程序开发者来说,当然还有 Rails。
对于没有 UI 编程经验的程序员来说,在 Ruby 脚本中使用裸的 Swing 可能会令人生畏,另一方面,我相信有其他 GUI 框架(如 Windows Forms)经验的人不会有太大困难。
JRuby Swing 脚本的基本结构
JRuby Swing 脚本的可能大纲如下
- 使用 `java_import` 导入所有必要的 Java 类。为简洁起见,将所有导入放在一个名为 'java_imports.rb' 的单独文件中,并在每个脚本中 `require` 该文件。
- 创建一个名为 `TopFrame` 的 `JFrame` Java 类的子类作为顶层应用程序容器。在 `TopFrame#initialize` 中,您需要调用 `super()`,否则 `JFrame` 将无法正确初始化。应用程序组件在名为 `TopFrame#init_components()` 的单独方法中初始化。`set_default_close_operation()` 告诉应用程序在 `TopFrame` 实例关闭时退出。
- 回到 `TopFrame#initialize()` 中,您调用 `init_components()`、`pack()` 和 `set_visible()`。
- 使用 `SwingUtilities#invoke_later()` 在 Swing 事件分发线程 (Event Dispatch Thread) 上创建一个新的 `TopFrame` 实例。
这是 'java_imports.rb' 代码
# java_imports.rb
java_import javax.swing.JFrame
java_import javax.swing.SwingUtilities
java_import java.awt.Dimension
java_import javax.swing.JSplitPane
java_import javax.swing.JTextArea
java_import java.awt.BorderLayout
java_import javax.swing.JMenuBar
java_import javax.swing.JMenu
java_import javax.swing.JMenuItem
java_import java.lang.System
java_import javax.swing.JToolBar
java_import javax.swing.JButton
java_import javax.swing.ImageIcon
java_import java.awt.Toolkit
java_import java.awt.datatransfer.StringSelection
java_import java.awt.datatransfer.Transferable
java_import java.awt.datatransfer.DataFlavor
java_import javax.swing.JScrollPane
java_import javax.swing.JTree
java_import javax.swing.tree.DefaultTreeModel
java_import javax.swing.tree.DefaultMutableTreeNode
java_import javax.swing.tree.TreeModel
java_import javax.swing.event.TreeWillExpandListener
java_import javax.swing.tree.TreeSelectionModel
java_import javax.swing.JFileChooser
java_import javax.swing.JProgressBar
java_import java.awt.event.ActionListener
java_import javax.swing.UIManager
JavaFile = java.io.File
JavaThread = java.lang.Thread
您会注意到导入的 Java 类比下面 JRuby 脚本所需的要多 - 这是因为我对本项目中的所有 Ruby 脚本都使用相同的导入文件。
01_swing_template.rb 代码
#01_swing_template.rb
require 'java_imports.rb'
class TopFrame < JFrame
def initialize
super
init_components()
pack()
set_visible(true)
end
def init_components()
set_default_close_operation(JFrame::EXIT_ON_CLOSE)
set_preferred_size(Dimension.new(400, 300))
end
end
SwingUtilities.invoke_later do
TopFrame.new
end
`JFrame::EXIT_ON_CLOSE` 允许您的应用程序在 `JFrame` 实例关闭时退出。
`pack()` 方法会调整框架的大小,使其所有内容都达到或超过其首选大小。
`set_visible()` 将 `JFrame` 显示在屏幕上。
如果一切正常,运行此脚本将在屏幕上显示一个空的 Swing JFrame。
为什么不忘记 `SwingUtilities.invoke_later()`,直接在主脚本线程中实例化 `TopFrame` 呢?
有许多关于 Swing 线程问题的链接,这里有一些
- 在事件分发线程 (Event Dispatch Thread) 之外构造 Swing/AWT 部件是否安全?
- 使用 Swing EDT 的未写规则
- Substance 中对 EDT 违规的更严格检查
- Swing 线程和事件分发线程
- 调试 Swing
调用 `invokeLater()` 还是不调用?
我写了两个脚本,一个不使用 `SwingUtilities#invokeLater()`,一个使用。这是第一个
#02_without_swing_utilities.rb require 'java_imports.rb' puts '----------------------------------------' puts 'Before JFrame.new():' puts "Thread name: #{JavaThread.current_thread.name}" puts "Is event dispatch thread: #{SwingUtilities.event_dispatch_thread?}" JavaThread.current_thread.thread_group.list frame = JFrame.new() frame.set_default_close_operation(JFrame::EXIT_ON_CLOSE) frame.set_preferred_size(Dimension.new(400, 300)) button = JButton.new('Button with action listener') button.add_action_listener do |event| puts '----------------------------------------' puts 'In action listener:' puts "Thread name: #{JavaThread.current_thread.name}" puts "Is event dispatch thread: #{SwingUtilities.event_dispatch_thread?}" JavaThread.current_thread.thread_group.list end frame.get_content_pane.add(button, BorderLayout::WEST) frame.pack() puts '----------------------------------------' puts 'Before JFrame.setVisible():' puts "Thread name: #{JavaThread.current_thread.name}" puts "Is event dispatch thread: #{SwingUtilities.event_dispatch_thread?}" JavaThread.current_thread.thread_group.list frame.set_visible(true)
输出是:
---------------------------------------- Before JFrame.new(): Thread name: main Is event dispatch thread: false java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main] Thread[Ruby-0-JIT-1,1,main] Thread[Ruby-0-JIT-2,1,main] java.lang.ThreadGroup[name=Ruby Threads#11483240,maxpri=10] ---------------------------------------- Before JFrame.setVisible(): Thread name: main Is event dispatch thread: false java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main] Thread[Ruby-0-JIT-1,1,main] Thread[Ruby-0-JIT-2,1,main] Thread[AWT-EventQueue-0,6,main] java.lang.ThreadGroup[name=Ruby Threads#11483240,maxpri=10] ---------------------------------------- In action listener: Thread name: AWT-EventQueue-0 Is event dispatch thread: true java.lang.ThreadGroup[name=main,maxpri=10] Thread[AWT-EventQueue-0,6,main] Thread[DestroyJavaVM,5,main] java.lang.ThreadGroup[name=Ruby Threads#11483240,maxpri=10]
在调用 `JFrame` 构造函数之前,当前线程是 `main`,没有 EDT。在调用 `JFrame#setVisible()` 之前,当前线程仍然是 `main`,但列表中出现了 EDT。在按钮操作监听器中,EDT 是当前线程。
另一个脚本使用 `SwingUtilities#invoke_later()` 来初始化 `JFrame`
#03_with_swing_utilities.rb require 'java_imports.rb' class TopFrame < JFrame def initialize super init_components() pack() set_visible(true) puts '----------------------------------------' puts 'In JFrame constructor:' puts "Thread name: #{JavaThread.current_thread.name}" puts "Is event dispatch thread: #{SwingUtilities.event_dispatch_thread?}" JavaThread.current_thread.thread_group.list end def init_components() set_default_close_operation(JFrame::EXIT_ON_CLOSE) set_preferred_size(Dimension.new(400, 300)) button = JButton.new('Button with action listener') button.add_action_listener do |event| puts '----------------------------------------' puts 'In action listener:' puts "Thread name: #{JavaThread.current_thread.name}" puts "Is event dispatch thread: #{SwingUtilities.event_dispatch_thread?}" JavaThread.current_thread.thread_group.list end get_content_pane.add(button, BorderLayout::EAST) end end puts '----------------------------------------' puts 'Before SwingUtilties.invokeLater():' puts "Thread name: #{JavaThread.current_thread.name}" puts "Is event dispatch thread: #{SwingUtilities.event_dispatch_thread?}" JavaThread.current_thread.thread_group.list SwingUtilities.invoke_later do TopFrame.new end puts '----------------------------------------' puts 'After SwingUtilities.invokeLater():' puts "Thread name: #{JavaThread.current_thread.name}" puts "Is event dispatch thread: #{SwingUtilities.event_dispatch_thread?}" JavaThread.current_thread.thread_group.list
输出是:
---------------------------------------- Before SwingUtilties.invokeLater(): Thread name: main Is event dispatch thread: false java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main] Thread[Ruby-0-JIT-1,1,main] Thread[Ruby-0-JIT-2,1,main] java.lang.ThreadGroup[name=Ruby Threads#11483240,maxpri=10] ---------------------------------------- After SwingUtilities.invokeLater(): Thread name: main Is event dispatch thread: false java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main] Thread[Ruby-0-JIT-1,1,main] Thread[Ruby-0-JIT-2,1,main] Thread[AWT-EventQueue-0,6,main] java.lang.ThreadGroup[name=Ruby Threads#11483240,maxpri=10] ---------------------------------------- In JFrame constructor: Thread name: AWT-EventQueue-0 Is event dispatch thread: true java.lang.ThreadGroup[name=main,maxpri=10] Thread[AWT-EventQueue-0,6,main] Thread[DestroyJavaVM,5,main] java.lang.ThreadGroup[name=Ruby Threads#11483240,maxpri=10] ---------------------------------------- In action listener: Thread name: AWT-EventQueue-0 Is event dispatch thread: true java.lang.ThreadGroup[name=main,maxpri=10] Thread[AWT-EventQueue-0,6,main] Thread[DestroyJavaVM,5,main] java.lang.ThreadGroup[name=Ruby Threads#11483240,maxpri=10]
调用 `SwingUtilities#invoke_later()` 之前和之后,当前线程是 `main`,但在调用 `SwingUtilities#invoke_later()` 之后,列表中出现了 EDT。在 `JFrame` 构造函数和 `JButton` 操作监听器中,EDT 都是当前线程。
JRUby Swing 应用程序的外观和感觉
Swing 应用程序在 GUI 元素方面有多种选择,称为“外观和感觉”。您可以使用此脚本列出可用的应用程序皮肤
# 4_look_and_feel_options.rb java_import javax.swing.UIManager UIManager.get_installed_look_and_feels().each do |info| puts info.get_name() end
在我的 Linux 机器上,输出是
Metal Nimbus CDE/Motif GTK+
您可以使用这些名称中的任何一个来设置(并更改!)您的应用程序皮肤。方法如下
# 05_set_lool_and_feel.rb java_import javax.swing.JFrame java_import javax.swing.SwingUtilities java_import java.awt.Dimension java_import java.awt.BorderLayout java_import javax.swing.JButton java_import javax.swing .JLabel java_import javax.swing.UIManager class TopFrame < JFrame def initialize super init_components() pack() set_visible(true) end def init_components() set_default_close_operation(JFrame::EXIT_ON_CLOSE) set_preferred_size(Dimension.new(400, 300)) button = JButton.new('Swing JButton look and feel') get_content_pane.add(button, BorderLayout::NORTH) label = JLabel.new(BorderLayout::NORTH) label.text = 'JLabel look and feel' get_content_pane.add(label) end end SwingUtilities.invoke_later do UIManager.get_installed_look_and_feels().each do |info| if info.name == 'GTK+' UIManager.set_look_and_feel(info.class_name) end end TopFrame.new end
以上列出的外观和感觉选项在我的机器上的样子
添加小部件
在向表单添加小部件时,这是标准程序
- 创建要在表单中使用的小部件的实例
- 设置要添加的小部件的所需属性
- 获取父容器的内容窗格。对于 `JFrame`,您可以使用 `JFrame#get_content_pane()` 获取其内容窗格的引用。
- 使用内容窗格的 `add()` 方法将您的部件添加到父容器中。
关于创建小部件实例及其范围,取决于您的需求。如果小部件在添加到容器后永远不会被更改或使用,您可以直接将其构造函数调用放在 `add()` 方法中。如果您需要设置或修改小部件的某些属性,您可以将小部件实例存储到局部变量中,然后自定义其属性,最后使用 `add()` 方法添加代表该小部件的局部变量。如果表单上的小部件需要相互通信,您可以将它们设置为继承 `JFrame` 的类的实例变量。
这是一个向 `JFrame` 添加两个小部件(一个 `JButton` 和一个 `JLabel`)的简单示例。
# 06_adding_widgets.rb require 'java_imports.rb' class TopFrame < JFrame attr_accessor :button, :label def initialize super init_components() pack() set_visible(true) end def init_components() set_default_close_operation(JFrame::EXIT_ON_CLOSE) content_pane = get_content_pane() @button = JButton.new('Button text') @label = JLabel.new('Label text') content_pane.add(@button, BorderLayout::NORTH) content_pane.add(@label, BorderLayout::SOUTH) end end SwingUtilities.invoke_later do TopFrame.new end
表单看起来是这样的
`TopFrame` 类继承 `JFrame`,并有两个实例变量 `@button` 和 `@label`(仅作演示 - 它们之间没有交互)。所有小部件都在 `TopFrame#init_components()` 中设置。
`JFrame.content_pane` 有一个布局管理器,可以帮助您在上面放置小部件。默认布局管理器是 `BorderLayout` - 您可以使用它将小部件放置在框架的 `NORTH`、`EAST`、`SOUTH`、`WEST` 和 `CENTER` 位置。还有其他更灵活的布局管理器。
Jruby 会进行一些 Java 方法的转换,让您可以编写更像 Ruby 的代码。例如,Java 的 `JFrame.getContentPane()` 在 JRuby 中变为 `get_content_pane()`。同样,Java 的 `SwingUtilities.invokeLater()` 在 Ruby 中变为 `SwingUtilities.invoke_later()`。这不是规则 - 您可以使用您偏好的选项。
为应用程序添加菜单
菜单在桌面(以及不仅仅是桌面)应用程序中无处不在。为 Swing 应用程序添加菜单栏与其他小部件的添加方式略有不同。这是代码
# 7_menu_bar.rb require 'java_imports.rb' class TopFrame < JFrame attr_accessor :main_menu_bar def initialize super init_components() pack() set_visible(true) end def init_components() set_default_close_operation(JFrame::EXIT_ON_CLOSE) set_preferred_size(Dimension.new(600, 400)) @main_menu_bar = MainMenuBar.new() set_jmenu_bar(@main_menu_bar) end end class MainMenuBar < JMenuBar attr_accessor :file_menu, :new_menu_item, :exit_menu_item, :edit_menu, :cut_menu_item, :copy_menu_item def initialize super() init_components() end def init_components @file_menu = JMenu.new('File') @new_menu_item = JMenuItem.new('New') @new_menu_item.add_action_listener do |event| puts 'the New menu item clicked' end @file_menu.add(@new_menu_item) @exit_menu_item = JMenuItem.new('Exit') @exit_menu_item.add_action_listener do |event| System.exit(0) end @file_menu.add(@exit_menu_item) add(@file_menu) @edit_menu = JMenu.new('Edit') @cut_menu_item = JMenuItem.new('Cut') @cut_menu_item.add_action_listener do |event| puts 'The Cut menu item clicked' puts 'Source: ' + event.source.to_s puts 'Action command: ' + event.action_command end @edit_menu.add(@cut_menu_item) @copy_menu_item = JMenuItem.new('Copy') @copy_menu_item.add_action_listener do |event| puts 'The Copy menu item clicked' end @edit_menu.add(@copy_menu_item) add(@edit_menu) end end SwingUtilities.invoke_later do TopFrame.new end
带有主菜单的 Jframe
如您所见,您使用 `JFrame#setJMenuBar()` 而不是 `JFrame#getComponentPane#add()`。其余的与预期一样。`JMenuItem` 实例(如“打开”、“复制”或“退出”)被添加到 `JMenu` 实例(如“文件”或“编辑”)中,后者又被添加到 `JMenuBar` 中以构建菜单层次结构。
更多关于 JRuby 处理 Java 方法
`TopFrame#init_components()` 中的 `set_default_close_operation()` 是一个 Java setter 方法,就像 `set_preferred_size()` 一样。JRuby 允许您像属性一样使用 getter 和 setter 方法,在这种情况下,代码片段将如下所示
def init_components() self.default_close_operation = JFrame::EXIT_ON_CLOSE self.preferred_size = Dimension.new(600, 400) @main_menu_bar = MainMenuBar.new() self.jmenu_bar = @main_menu_bar end
我使用 `self` 限定了方法调用,并且可以将它们用作属性。所有查找(`jmeny_bar => set_jmenu_bar() => setJMenuBar()`)都由 JRuby 完成。
JRuby 做的另一件事是转换其代码块为匿名 Java 类。例如,`JButton#addActionListener()` 需要一个实现 `ActionListener` Java 接口的类。`ActionListener` 指定只有一个方法:`actionPerformed()`。与其创建一个单独的实现 `ActionListener` 的 JRuby 类并将其实例传递给 `JButton`,不如只将一个代码块传递给 `add_action_listener()`
@cut_menu_item.add_action_listener do |event| puts 'The Cut menu item clicked' puts 'Source: ' + event.source.to_s puts 'Action command: ' + event.action_command end
`do` 和 `end` 之间的代码成为 `actionPerformed` 方法体,并代表 `ActionEvent` 实例的 `|event|`。请注意,方法名称未指定。
工具栏
工具栏也是桌面应用程序的常见元素。这里有一个例子
# 08_toolbar.rb require 'java_imports.rb' class TopFrame < JFrame attr_accessor :main_toolbar, :text_area def initialize super init_components() pack() set_visible(true) end def init_components() set_default_close_operation(JFrame::EXIT_ON_CLOSE) set_preferred_size(Dimension.new(600, 400)) content_pane = get_content_pane() puts get_layout().class @text_area = JTextArea.new() content_pane.add(@text_area, BorderLayout::CENTER) @main_toolbar = MainToolBar.new(@text_area) content_pane.add(@main_toolbar, BorderLayout::NORTH) end end class MainToolBar < JToolBar attr_accessor :copy_button, :paste_button, :text_container, :clipboard def initialize(text_container) super() init_components() set_floatable(false) @text_container = text_container @clipboard = Toolkit.default_toolkit.system_clipboard end def init_components() copy_icon = ImageIcon.new('assets/edit-copy.png') @copy_button = JButton.new(copy_icon) @copy_button.add_action_listener do |event| selection = StringSelection.new(@text_container.selected_text) @clipboard.set_contents(selection, selection) end add(@copy_button) paste_icon = ImageIcon.new('assets/edit-paste.png') @paste_button = JButton.new(paste_icon) @paste_button.add_action_listener do |event| transferable = @clipboard.get_contents(nil) if transferable.is_data_flavor_supported(DataFlavor::stringFlavor) text = transferable.get_transfer_data(DataFlavor::stringFlavor) position = @text_container.caret_position @text_container.insert(text, position) end end add(@paste_button) end end SwingUtilities.invoke_later do TopFrame.new end
Java 中的工具栏由 `JToolBar` 类表示。`JToolBar` 是一个容器,可以包含其他小部件,如 `JButton`、`JCheckBox`、`JRadiobutton`、`JLabel`、`JComboBox` 等。上面的工具栏有两个按钮,我使用了来自 Tango Icon Library 的图标。图标位于 `/assets` 子目录下。第一个工具栏按钮 (`@copy_button`) 将 `JFrame` 右侧的 `JTextArea` 中选定的文本复制到系统剪贴板。第二个工具栏按钮 (`@paste_button`) 将剪贴板内容复制到 `JTextArea`。看起来是这样的。
使用 JTree
`JTree` 小部件是更复杂的 Swing 组件之一 - 它允许您表示分层数据。`JTree` 的真正优势在于其底层模型。这是一个可以与 `JTree` 实例一起使用的自定义模型
require 'java_imports.rb' class FileSystemTreeModel include TreeModel attr_accessor :root def initialize(path) @root = JavaFile.new(path) end def get_root() return @root end def is_leaf(node) return node.file? end def get_child_count(parent) children = parent.list if children == nil return 0 else return children.length end end def get_child(parent, index) children = parent.list if children == nil or children.length <= index return nil else return parent.list_files[index] end end def get_index_of_child(parent, child) entries = parent.list hash = Hash[entries.map.with_index.to_a] puts hash[child] return hash[child] end def value_for_path_changed(path, new_value) end def add_tree_model_listener(listener) end def remove_tree_model_listener(listener) end end
该模型实现了 `TreeModel` 接口,以反映计算机磁盘上的文件系统树。类必须实现的第一个方法是 `#get_root()`。我将 `root` 实现为公共的,以便可以在模型实例化后更改它,但这并非必需。下一个实现的方法在模型节点是叶子节点(即没有子节点)时返回 `true`。如果节点是一个文件,那么它也是一个叶子节点,因为它没有子节点。
`#get_child_count()` 返回给定节点上的子节点计数。这允许延迟 `JTree` 初始化。`#get_child(parent, index)` 按索引返回子节点,`get_index_of_child(parent, child)` 返回子节点的索引。最后三个方法未实现,因为此 `JTree` 不可更改。
请注意,`FileSystemTreeModel` 类没有显式实现遍历文件系统所需的递归。
`JTree` 类本身足够简单
require 'pathname' require 'java_imports.rb' require 'file_system_tree_model.rb' class TopFrame < JFrame attr_accessor :fs_tree, :scroll_pane def initialize super init_components() pack() set_visible(true) end def init_components() set_default_close_operation(JFrame::EXIT_ON_CLOSE) set_preferred_size(Dimension.new(300, 400)) content_pane = get_content_pane() @fs_tree = FileSystemTree.new() @scroll_pane = JScrollPane.new(@fs_tree) content_pane.add(@scroll_pane, BorderLayout::CENTER) end end class FileSystemTree < JTree def initialize super() init_components() end def init_components() home_dir = System.get_property('user.home') model = FileSystemTreeModel.new(home_dir) set_model(model) end def convertValueToText(value, selected, expanded, leaf, row, has_focus) return value.name end end SwingUtilities.invoke_later do UIManager.get_installed_look_and_feels().each do |info| if info.name == 'GTK+' UIManager.set_look_and_feel(info.class_name) end end TopFrame.new end
`JTree` 被嵌入到 `JScrollPane` 中。有一点值得一提的是,`FileSystemTree` 重写了 `JTree#convertValueToText()` 方法,以显示文件名作为节点文本(而不是其路径)。在这种情况下,方法的第一个参数是 Java `File` 类,您可以使用 `value.name()` 显示基础文件名作为节点文本,而不是使用完整路径。
这是 JTree 的实际应用
一个更复杂的例子
最后,一个更复杂的例子,一个 `JFrame`,它有一个菜单栏,一个工具栏,一个拆分窗格,左侧有一个滚动窗格,右侧有一个文本区域。一个 `JTree` 被嵌入到滚动窗格中。
# 10_hand_made_frame.rb require 'java_imports.rb' require 'file_system_tree_model.rb' class TopFrame < JFrame attr_accessor :split_pane, :main_menu_bar, :toolbar, :clipboard def initialize super init_components() pack() set_visible(true) end def init_components() set_default_close_operation(JFrame::EXIT_ON_CLOSE) set_preferred_size(Dimension.new(600, 400)) content_pane = get_content_pane() @main_menu_bar = MainMenuBar.new() set_jmenu_bar(@main_menu_bar) @split_pane = TreeSplitPane.new() content_pane.add(@split_pane, BorderLayout::CENTER) @toolbar = MainToolBar.new() content_pane.add(@toolbar, BorderLayout::NORTH) @clipboard = Toolkit.default_toolkit.system_clipboard copy_listener = ActionListener.impl do |event| selection = StringSelection.new(@split_pane.text_area.text) @clipboard.set_contents(selection, selection) end @toolbar.copy_button.add_action_listener(copy_listener) @main_menu_bar.copy_menu_item.add_action_listener(copy_listener) open_directory_listener = ActionListener.impl do |event| file_chooser = JFileChooser.new() file_chooser.file_selection_mode = JFileChooser::DIRECTORIES_ONLY file_chooseraccept_all_file_filter_used = false return_value = file_chooser.show_open_dialog(self) if return_value == JFileChooser::APPROVE_OPTION root_dir = file_chooser.selected_file.path model = FileSystemTreeModel.new(root_dir) @split_pane.tree.set_model(model) end end @main_menu_bar.open_menu_item.add_action_listener(open_directory_listener) @toolbar.open_button.add_action_listener(open_directory_listener) end end class TreeSplitPane < JSplitPane attr_accessor :tree, :text_area, :scroll_pane def initialize super() init_components() set_left_component(@scroll_pane) set_right_component(@text_area) java_send :setDividerLocation, [Java::int], 250 end def init_components() @tree = FileSystemTree.new() @tree.selection_model.selection_mode = TreeSelectionModel::SINGLE_TREE_SELECTION @tree.add_tree_selection_listener do |event| node = @tree.last_selected_path_component if node.instance_of?(JavaFile) info = <<-INFO Path: #{node.path} Name: #{node.name} Size: #{node.length} INFO else info = '' end @text_area.text = info end @scroll_pane = JScrollPane.new(@tree) @text_area = JTextArea.new() @text_area.editable = false end end class MainMenuBar < JMenuBar attr_accessor :file_menu, :open_menu_item, :exit_menu_item, :edit_menu, :copy_menu_item def initialize super() init_components() end def init_components @file_menu = JMenu.new('File') @open_menu_item = JMenuItem.new('Open') @file_menu.add(@open_menu_item) @exit_menu_item = JMenuItem.new('Exit') @exit_menu_item.add_action_listener do |event| System.exit(0) end @file_menu.add(@exit_menu_item) add(@file_menu) @edit_menu = JMenu.new('Edit') @copy_menu_item = JMenuItem.new('Copy') @edit_menu.add(@copy_menu_item) add(@edit_menu) end end class MainToolBar < JToolBar attr_accessor :open_button, :copy_button def initialize() super() init_components() set_floatable(false) end def init_components() open_file_icon = ImageIcon.new('assets/document-open.png') @open_button = JButton.new(open_file_icon) add(@open_button) copy_icon = ImageIcon.new('assets/edit-copy.png') @copy_button = JButton.new(copy_icon) add(@copy_button) end end class FileSystemTree < JTree def initialize super() init_components() end def init_components() home_dir = System.get_property('user.home') model = FileSystemTreeModel.new(home_dir) set_model(model) end def convertValueToText(value, selected, expanded, leaf, row, has_focus) return value.name end end SwingUtilities.invoke_later do UIManager.get_installed_look_and_feels().each do |info| if info.name == 'GTK+' UIManager.set_look_and_feel(info.class_name) end end TopFrame.new end
这是框架
大多数功能在前面的示例中都已见过,唯一需要注意的是
java_send :setDividerLocation, [Java::int], 250
来解决歧义的 Java 方法调用。请注意,在这种情况下,您需要逐字使用 Java 方法名称。
设计器生成的表单
许多人更喜欢使用 WYSIWYG 设计器工具来设计 Swing 表单。可以使用 Eclipse Window Builder 等工具设计表单,将生成的代码编译成类文件,然后将该文件包含在您的 JRuby 脚本中。