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.Quitwith 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 任務混合使用。


