檢視原始碼 wx,wxWidgets 的 Erlang 綁定

wx 應用程式是 wxWidgets 的 Erlang 綁定。本文檔描述了 Erlang 對 wxWidgets 的映射及其實現。它不是 wxWidgets 的完整使用者指南。如果您需要,則必須閱讀 wxWidgets 文件。wx 嘗試與原始 API 保持一對一的映射,以便盡可能輕鬆地使用原始文件和範例。

可以在 Erlang src 發行版中找到 Wx 範例和測試套件。它們還可以提供有關如何使用 API 的一些幫助。

目前這只是對 wx 的一個非常簡短的介紹。該應用程式仍在開發中,這表示介面可能會發生變化,並且測試套件目前的覆蓋率很低。

目錄

簡介

原始的 wxWidgets 是一個物件導向 (C++) API,這反映在 Erlang 映射中。在大多數情況下,wxWidgets 中的每個類別都表示為 Erlang 中的一個模組。這使得 wx 應用程式具有一個龐大的介面,分佈在多個模組中,並且所有這些都從 wx 模組開始。wx 模組包含用於建立和銷毀 GUI 的函數,即 wx:new/0wx:destroy/0 和一些其他有用的函數。

wx 中的物件或物件參考應被視為 Erlang 進程,而不是 Erlang 項。當您對它們進行操作時,它們可能會改變狀態,例如,它們不是像 Erlang 項那樣的函數式物件。每個物件都有一個類型或更確切地說是一個類別,它通過相應的模組或該物件的子類別來操作。會進行類型檢查,以便模組僅對其物件或繼承的類別進行操作。

使用 new 建立物件,並使用 destroy 銷毀物件。類別中的大多數函數都與其 C++ 對應項同名,只是為了方便起見,在 Erlang 中它們以小寫字母開頭,第一個參數是物件參考。可選參數放在最後,並以任何順序表示為標記的元組。

例如,wxWindow C++ 類別是在 wxWindow Erlang 模組中實現的,因此成員 wxWindow::CenterOnParentwxWindow:centerOnParent。以下 C++ 程式碼

  wxWindow MyWin = new wxWindow();
  MyWin.CenterOnParent(wxVERTICAL);
  ...
  delete MyWin;

在 Erlang 中看起來會像

  MyWin = wxWindow:new(),
  wxWindow:centerOnParent(MyWin, [{dir,?wxVERTICAL}]),
  ...
  wxWindow:destroy(MyWin),

當您閱讀 wxWidgets 文件或範例時,您會注意到 wx 中缺少一些最基本的類別,它們會直接映射到對應的 Erlang 項

  • wxPoint 由 {X坐標,Y坐標} 表示

  • wxSize 由 {寬度,高度} 表示

  • wxRect 由 {X坐標,Y坐標,寬度,高度} 表示

  • wxColour 由 {紅色,綠色,藍色[,Alpha]} 表示

  • wxStringunicode:charlist() 表示

  • wxGBPosition 由 {列,欄} 表示

  • wxGBSpan 由 {列跨度,欄跨度} 表示

  • wxGridCellCoords 由 {列,欄} 表示

在 Erlang API 與原始 API 不同之處,Erlang 文件應該會明確指出使用了哪種表示形式。例如,C++ 陣列和/或清單有時表示為 Erlang 清單,有時表示為元組。

顏色用 {紅色,綠色,藍色[,Alpha]} 表示,當用作函數的引數時,Alpha 值是可選的,但它始終會從 wx 函數傳回。

定義、列舉和全域變數以定義的形式存在於 wx.hrl 中。這些定義大多數是常數,但並非全部。有些是與平台相關的,因此全域變數必須在執行階段進行實例化。這些將通過調用從驅動程式取得,因此並非所有定義都可以在匹配語句中使用。類別局部列舉將以類別名稱和底線作為字首,例如 ClassName_Enum

此外,一些全域函數(即非類別函數)存在於 wx_misc 模組中。

Wx 是作為一個(多執行緒)驅動程式實現的,並且是 C++ API 的相當直接的介面,缺點是如果 Erlang 程式設計師發生錯誤,可能會導致模擬器崩潰。

由於驅動程式是多執行緒的,因此它需要啟用 smp 的模擬器,該模擬器為驅動程式提供執行緒安全介面。

多個進程和記憶體處理

目的是每個 Erlang 應用程式都調用一次 wx:new() 來設定其 GUI,這會建立一個環境和一個記憶體映射。為了能夠在應用程式中的多個進程中使用 wx,您必須共享環境。您可以使用 wx:get_env/0 取得活動環境,並使用 wx:set_env/1 在新進程中設定它。兩個都調用過 wx:new() 的進程或應用程式將無法使用彼此的物件。

  wx:new(),
  MyWin = wxFrame:new(wx:null(), 42, "Example", []),
  Env = wx:get_env(),
  spawn(fun() ->
           wx:set_env(Env),
           %% Here you can do wx calls from your helper process.
           ...
        end),
  ...

當調用 wx:destroy/0 時,或當應用程式中的所有進程都已結束時,記憶體將被刪除,並且該應用程式建立的所有視窗都將被關閉。

只要使用者應用程式處於活動狀態,wx 應用程式永遠不會清除或垃圾回收記憶體。當視窗關閉時,或至少當所有具有非空父引數的物件關閉時,大部分物件都會被刪除。在可能的情況下使用 wxCLASS:destroy/1 可以避免記憶體使用量增加。當 wxWidgets 假設或建議您(或更確切地說 C++ 程式設計師)已在堆疊上分配物件時,這一點尤其重要,因為這永遠不會在 Erlang 綁定中完成。例如 wxDC 類別或其子類別或 wxSizerFlags

目前,顯示模式對話方塊會凍結 wxWidgets,直到對話方塊關閉。這是預期的,但在 Erlang 中,您可以同時執行多個 GUI 應用程式,這會導致問題。希望這會在未來的 wxWidgets 版本中得到修正。

事件處理

wx 中的事件處理與原始 API 的差異最大。您必須在 wxWidgets 中指定您要處理的每個事件,這在 Erlang 綁定中是相同的,但您可以選擇將事件接收為訊息或使用回呼 funs 來處理它們。

否則,事件訂閱將作為 wxWidgets 動態事件處理程式連線處理。您可以訂閱來自具有 ID 或在 ID 範圍內的物件的特定類型的事件。回呼 fun 是可選的,如果未提供,則事件將發送到調用 connect/2 的進程。因此,處理程式是一個回呼 fun 或一個將接收事件訊息的進程。

事件會按照由下往上、在小工具層次結構中的順序,由最後訂閱的處理程式優先處理。根據是否調用了 wxEvent:skip(),其他處理程式之後將處理該事件。大多數事件都已安裝了預設事件處理程式。

訊息事件看起來像 #wx{id=integer(), obj=wx:wxObject(), userData=term(), event=Rec }。id 是接收事件的物件的識別碼。obj 欄位包含您使用 connect 的物件。userData 欄位包含使用者提供的項,這是 connect 的一個選項。event 欄位包含具有事件類型相關資訊的記錄。事件記錄中的第一個元素始終是您訂閱的類型。例如,如果您訂閱了 key_up 事件,您將收到 #wx{event=Event},其中 Event 將是一個 wxKey 事件記錄,其中 Event#wxKey.type = key_up

wxWidgets 中,開發人員必須調用 wxEvent:skip(),如果他希望其他處理程式處理該事件。如果您使用回呼,您可以在 wx 中執行相同的操作。如果您想要將事件作為訊息,則只需不提供回呼,並且您可以在 connect 調用中將 skip 選項設定為 true 或 false,預設值為 false。True 表示您收到訊息,但也讓後續處理程式處理該事件。如果您想要動態變更此行為,您必須使用回呼並調用 wxEvent:skip()

回呼事件處理是通過在附加處理程式時使用可選回呼 fun/2 來完成的。fun(#wx{},wxObject() 必須採用兩個引數,其中第一個引數與上述訊息事件中的相同,第二個引數是實際事件物件的物件參考。使用事件物件,您可以調用 wxEvent:skip() 並存取所有資料。使用回呼時,如果您希望將任何事件轉發到後續處理程式,則必須自行調用 wxEvent:skip()。實際的事件物件會在 fun 傳回後刪除。

回呼始終由另一個進程調用,並且在調用時具有 GUI 的獨佔使用權。這表示回呼 fun 無法使用進程字典,並且不應調用其他進程。如果其他進程正在等待其對 GUI 的調用完成,則在回呼 fun 內調用另一個進程可能會導致死鎖。

致謝

Mats-Ola Persson 編寫了最初的 wxWidgets 綁定,作為他碩士論文的一部分。目前版本是完全重新編寫的,但許多想法已被重複使用。重新編寫的原因主要是由於我們給予他的限制要求。

還要感謝 wxWidgets 團隊開發和支援它,以便我們可以使用它。