Ada 编程/库/GUI/GtkAda
GtkAda 是流行的开源 GTK+ 库的 Ada 绑定。可在多个平台上使用。
with Gtk.Main, Gtk.Window;
procedure Simple_Application is
Window : Gtk.Window.Gtk_Window;
begin
Gtk.Main.Init;
Gtk.Window.Gtk_New (Window);
Gtk.Window.Show (Window);
Gtk.Main.Main;
end Simple_Application;
文件选择器对话框是一个方便的工具,当打开或保存文件时,它为用户提供对文件系统的便捷访问。Gtk 提供了一个方便的 FileChooserDialog
类型,它非常易于设置和使用。
作为 C 库,Gtk 的库函数通常具有可变数量的参数。创建文件选择器对话框通常涉及将您想要的按钮作为参数传递到 gtk_file_chooser_dialog_new()
中,以便在对话框的“操作栏”中显示。
作为 Ada 库,GtkAda 的子程序从不具有可变数量的参数。以下摘录显示了如何创建一个工作文件选择器对话框,它将保存文件。
-- the package must have already with'd Gtk.Dialog, Gtk.File_Chooser,
-- and Gtk.File_Chooser_Dialog
declare
-- let's make our lives a little easier
package Dialog renames Gtk.Dialog;
use all type Dialog.Gtk_Response_Type;
package File_Chooser renames Gtk.File_Chooser;
package FCD renames Gtk.File_Chooser_Dialog;
-- the dialog
Filename_Dialog: FCD.Gtk_File_Chooser_Dialog;
-- needed only to discard return value
Discard: Gtk.Widget.Gtk_Widget;
begin
-- create chooser dialog
FCD.Gtk_New
(
Dialog => Filename_Dialog,
Title => "Save file: choose path",
Parent => null,
Action => File_Chooser.Action_Save
);
-- add save, cancel buttons
--
-- notice the different response id associated with each button;
-- this makes it easy to handle user responses to the dialog,
-- while the underscore that precedes the text allows for keyboard shortcuts
-- (alt + character-that-follows-underscore)
--
-- Add_Button is an Ada function, so you have to accept the returned value,
-- which is a Gtk_Widget. This is useful if you want to modify the result.
Discard := Dialog.Add_Button
(
Dialog => Dialog.Gtk_Dialog(Filename_Dialog),
Text => "_Cancel",
Response_Id => Dialog.Gtk_Response_Cancel
);
Discard := Dialog.Add_Button
(
Dialog => Dialog.Gtk_Dialog(Filename_Dialog),
Text => "_Save",
Response_Id => Dialog.Gtk_Response_Accept
);
-- make sure user is OK with overwriting old file
FCD.Set_Do_Overwrite_Confirmation(Filename_Dialog, True);
-- set default filename;
-- Gtk is smart enough to highlight everything before the extension (.txt)
FCD.Set_Current_Name(Filename_Dialog, "new_file.txt");
-- Run the dialog and react accordingly
if FCD.Run(Filename_Dialog) = Dialog.Gtk_Response_Accept then
declare Filename: UTF8_String := FCD.Get_Filename(Filename_Dialog);
begin
-- you have to define the Write_Data function yourself,
-- since only you know how you want to write the data to disk
Write_Data(Filename);
end;
-- in theory we could react to Gtk_Response_Cancel as well,
-- but there doesn't seem to be a need here
end if;
-- destroy the dialog or it will stay visible and annoy you
Gtk.Widget.Destroy(Gtk.Widget.Gtk_Widget(Filename_Dialog));
end;
一个有趣的实现细节是,您想要使用的大多数类型都是 访问类型,用于 标记类型。实际上,Gtk_Dialog
和 Gtk_File_Chooser_Dialog
以上是标记类型。相应的记录将具有几乎相同的名称:Gtk_Dialog_Record
和 Gtk_File_Chooser_Dialog_Record
。其他一些类型则作为名为 GType_Interface
的“接口”实现,例如 Gtk_File_Chooser
;它没有名为 Gtk_File_Chooser_Record
的相应类型。
我们主要提到这一点是为了解释为什么对 Add_Button
的调用中的第一个参数具有 Dialog => Dialog.Gtk_Dialog(Filename_Dialog)
的形式;形式参数的类型为 Gtk_Dialog
,但实际参数的类型为 Gtk_File_Chooser_Dialog
。后者符合前者,但 Ada 要求我们明确说明这一点。
GtkAda 的一个关键概念是“回调”:程序员可以安排,当相对于某个小部件发生事件时,该小部件“回调”到某个函数。我们说明如何向窗口的“按钮栏”添加一个按钮,并设置回调函数,这些函数在用户单击并释放按钮时或在用户按下快捷键(也称为“助记符”)时激活。
您通常需要在单独的包中定义回调,并且每个事件的回调函数必须符合特定的签名。我们感兴趣的签名在 gtk-widget.ads
中找到。
大多数事件允许您以两种不同的方式分配回调。我们将考虑稍微复杂的选择;它提供了一个类型为 GObject
的槽参数。当您设置回调时,您可以传递小部件本身、另一个小部件或您自己创建的自定义 GObject
,其中包含回调处理事件所需的的信息。后者可能是典型的场景,因为大多数界面元素需要与其他小部件和程序数据进行交互,而槽是实现这一目标的简便方法。
- 要使用槽处理
On_Button_Release_Event
,回调必须具有以下签名
type Cb_GObject_Gdk_Event_Button_Boolean is not null access function
(Self : access Glib.Object.GObject_Record'Class;
Event : Gdk.Event.Gdk_Event_Button) return Boolean;
- 要使用槽处理
On_Mnemonic_Activate
,回调必须具有以下签名
type Cb_GObject_Boolean_Boolean is not null access function
(Self : access Glib.Object.GObject_Record'Class;
Arg1 : Boolean) return Boolean;
在每种情况下,Self
都指在分配回调时给出的槽参数。
在这种情况下,我们希望按钮执行相同的操作,无论我们是通过按钮单击还是助记符激活它。由于回调的签名不同,我们不能使用相同的函数来处理两者。但是,在这个简单的示例中,我们可以从另一个函数中调用一个函数。在 callbacks
包体中,我们定义了以下函数(以及相应的包规范)
function Handle_Button_Click
(Self : access Glib.Object.GObject_Record'Class;
Event: Gdk.Event.Gdk_Event_Button
) return Boolean
is
begin
return Handle_Mnemonic(Self, False);
end Handle_Button_Click;
function Handle_Mnemonic
(Self : access Glib.Object.GObject_Record'Class;
Arg : Boolean
) return Boolean
is
begin
-- perform the needed activity with Self
-- ...
return False;
end Handle_Mnemonic;
- 您可能想知道
Event
和Arg
的意义何在。在这个特定应用中,我可能对它们没有用处,可以忽略它们,但在某些情况下,您可能想知道用户在按下鼠标按钮时是否按下了 Control 键(包含在Event
参数中)。
(注意: 据作者所知,当用户通过助记符调用按钮时,Gtk 始终将 False
传递给 Arg
。GtkAda 文档没有提供关于 Arg
应该传递什么信息的信息。)
- 您会注意到回调函数返回一个
Boolean
值;它的目的是指示此回调是否“完全处理”了该事件;如果为 true,则分配给此小部件和此事件的其他处理程序将不会了解该事件已被处理。这通常是您想要的行为,但在本示例中,从助记符返回False
允许 Gtk 提供视觉反馈,就像按钮被单击并释放一样。如果回调返回True
,则不会发生这种情况。
在这里,我们说明了如何创建一个带有助记符的按钮及其标签,以及如何为助记符按键和按下并释放分配回调。这不是一个完整的示例;您需要 with
和 use
包含相关类型的包。
-- declarations
My_Button : Gtk_Button;
Button_Bar : Gtk_Table; -- horizontal box containing buttons
Button_Data : GObject; -- assign another widget, or a custom GObject
-- with information relevant for the desired behavior
-- ...
begin
-- ...
-- create the button bar and the button and attach the button
Button_Bar := Gtk_Table_New(1, 5, True);
My_Button := Gtk_Button_New_With_Mnemonic("_Execute!");
Button_Bar.Attach(Add_Button, 1, 2, 0, 1, Shrink, Shrink, 0, 0);
My_Button.On_Button_Release_Event
(Call => Handle_Button_Click'Access,
Slot => Button_Data,
After => False);
My_Button.On_Mnemonic_Activate
(Call => Handle_Mnemonic'Access,
Slot => Button_Data,
After => False);
-- ...
-- attach Button_Bar to the Window,
-- or to some other UI element attached to the Window
-- start the Gtk main loop
Gtk.Main.Main;
end;
待办事项
截至 2012 年,支持交互式 GTK 构建器 Glade3。我们描述了使用 Glade3 构建“hello world”所需的步骤的示例。
您必须安装 GTKAda 并设置环境变量 GPR_PROJECT_PATH
以找到项目文件 gtkada.gpr
。对于 Windows,GPR_PROJECT_PATH
应该如下所示
C:\GNAT\2012\lib\gnat;C:\GtkAda\lib\gnat (if you used the default locations)
Glade3 交互式地生成描述 UI 的 XML 文件。我们的基本 UI 将包含一个带有标签的 Main_Window。我们必须通过在 Main_Window 中声明一个“destroy”信号来处理终止,以便在关闭窗口时退出应用程序,否则程序将不会结束。
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="2.18"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkWindow" id="main_window">
<property name="width_request">400</property>
<property name="height_request">100</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Glade 3 simple demo with Ada</property>
<property name="resizable">False</property>
<property name="window_position">center</property>
<signal name="destroy" handler="Main_Quit" swapped="no"/>
<child>
<object class="GtkLabel" id="some_label">
<property name="width_request">200</property>
<property name="height_request">50</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Hello from Glade 3 ;-)</property>
</object>
</child>
</object>
</interface>
首先,我们声明将附加到“destroy”信号的处理程序/回调。回调必须在包中,否则你会遇到可访问性错误。
Gobject 事件“destroy”由 XML 中的“Main_Quit”处理,而“Main_Quit”由 Ada 过程 Simple_Callbacks.Quit 实现。
逻辑连接是:
signal : GTKObject.destroy => Handler name : "Main_Quit" => Ada callback : Simple_Callbacks.Quit
with Gtkada.Builder; use Gtkada.Builder;
package Simple_Callbacks is
procedure Quit (Object : access Gtkada_Builder_Record'Class);
end Simple_Callbacks;
with Gtk.Main;
package body Simple_Callbacks is
procedure Quit (Object : access Gtkada_Builder_Record'Class) is
pragma Unreferenced (Object);
begin
Gtk.Main.Main_Quit;
end Quit;
end Simple_Callbacks;
读取 XML 描述,注册终止处理程序,连接,启动 Gtk.Main.Main 循环,就是这样。
with Gtk; use Gtk;
with Gtk.Main; use Gtk.Main;
with Glib.Error; use Glib.Error;
with Gtk.Widget; use Gtk.Widget;
with Ada.Text_IO;
with Gtkada.Builder; use Gtkada.Builder;
-- the following package is user defined.
with Simple_Callbacks; use Simple_Callbacks;
procedure Simple_Glade3 is
Builder : Gtkada_Builder;
Error : Glib.Error.GError;
begin
Gtk.Main.Init;
-- Step 1: create a Builder and add the XML data,
Gtk_New (Builder);
Error := Add_From_File (Builder, "simple.glade");
if Error /= null then
Ada.Text_IO.Put_Line ("Error : " & Get_Message (Error));
Error_Free (Error);
return;
end if;
-- Step 2: add calls to "Register_Handler" to associate your
-- handlers with your callbacks.
Register_Handler
(Builder => Builder,
Handler_Name => "Main_Quit", -- from XML file <signal handler=..>
Handler => Simple_Callbacks.Quit'Access);
-- Step 3: call Do_Connect. Once to connect all registered handlers
Do_Connect (Builder);
-- Find our main window, then display it and all of its children.
Gtk.Widget.Show_All (Get_Widget (Builder, "main_window"));
Gtk.Main.Main;
-- Step 4: when the application terminates or all Windows described through
-- your builder should be closed, call Unref to free memory
-- associated with the Builder.
Ada.Text_IO.Put_Line ("The demo is over");
Unref (Builder);
end Simple_Glade3;
with "gtkada";
project Simple is
type Gtkada_Kind_Type is
("static", "relocatable");
Library_Type : Gtkada_Kind_Type := external ("LIBRARY_TYPE", "static");
for Source_Dirs use ("src");
for Object_Dir use "obj";
for Exec_Dir use ".";
for Main use ("simple_glade3.adb");
package Builder is
for Default_Switches ("ada") use ("-s");
end Builder;
package Compiler is
for Default_Switches ("ada") use ("-O2", "-gnat05");
end Compiler;
package Linker is
-- for Windows production only ;; remove for Linux / Mac / Win debug
for Default_Switches ("ada") use ("-mwindows");
end Linker;
end Simple;
对于 RAD,gate3 是一个 Ada 代码草图器:你使用 glade3.8 构建用户界面,gate3 生成一个有效的 Ada 代码原型。
完整演示程序可从 Sourceforge 获取
- Lorenz 混沌吸引子:使用 GTK 定时器循环的绘图演示。
- Julia 集:将 GTK 界面与 Ada 任务混合使用。