做為一個以c++為目標語言且要適配各種平臺的界面庫,FLTK注定是小眾的,所以寫的內容要限定一下受眾。如果你對c/c++比較熟悉,至少對某一種操作系統的API比較熟悉,希望找到某種一次編寫到處編譯的界面庫,同時對靈活性和尺寸比較在意,那么這個文檔就比較適合你。如果你只是希望學會怎么使用fltk,并不想深入了解它背后的原理,那么這個文檔就不太適合,fltk的在線文檔在這里:http://www.fltk.org/documentation.php
fltk最初的思路來自于1987年的NeXT系統,初始版本針對的是X,所以代碼里有一些用X開頭的函數名,但隨著代碼的不斷演進,接口逐漸變得和系統無關。
基本上,fltk認為所有的操作系統都會提供以下幾種功能:
1.窗口創建和銷毀
2.繪圖(點,直線,曲線,圓...)
3.字體顯示
4.輸入設備交互(鍵盤、鼠標)
只要有這幾種功能,不需要系統提供全套的控件,也可以自行構建出界面。另外系統還會提供一些附加功能,對于豐富界面也很有幫助,但并不是充分必要條件,比如
1.圖片讀寫
2.文件操作
3.打印機
4.輸入法
基于這樣的認知,做為一個GUI庫,fltk需要提供一個模型,把這些元素組合在一起,既要有足夠的彈性又要足夠簡單,FLTK采用的是main-loop,相信很多人開始學習c語言的時候都會寫下面的代碼:
#include <stdio.h>
int main(int argc, char** argv)
{
printf("hello world\n");
return 0;
}
fltk所使用的模型就和這個類似,用偽代碼表示就是:
#include <fltk.h>
int main(int argc, char **argv)
{
create_window(); // 創建窗口
create_widget(); // 創建控件
while (1) {
if ( wait() ) break; // 事件循環
}
return 0;
}
是不是和gtk很類似?
這個模型的好處是容易理解,如果把所有的流程都用class包裹起來,雖然貌似充滿了oo的味道,但是對于理解代碼反而是有害的。任何代碼都有一個入口,為了面向對象,甚至把入口也藏起來,只會增加學習者的困擾。比如mfc,qt,juce,wxwidgets,如果想分析代碼,光是找到起點就很不容易,尤其為了oo,很多GUI庫用宏將main都包裹了起來,更增加了理解的難度。代碼不應該讓編譯器舒服,也不應該屈從于某種思想,而是應該以人為本,讓程序員看的輕松用的輕松。人的注意力是有限的,短期記憶大概只有十幾分鐘的時間,同時注意到的目標也不多,而且似乎人的思維模式是線性的,也就是說只能在一條線上做深入思考,并行處理好幾個問題,大腦會短路。當然有些發達的大腦有一心多用的本領,但是總要照顧大多數人吧?
首先談談這個main(),為什么叫這個名字?這和編譯器和操作系統有關,具體原因可以自行百度,重要的只有一條,這是程序的入口。事實上并不是所有的操作系統都用這個名稱,osx/ios/linux是用main,windows/wince用的是winmain,android/windows phone干脆沒有main,所以要為所有的平臺編寫統一的main。先看看windows平臺的實現,打開fltk的源代碼,找到src/fl_call_main.c
extern int main(int, char *[]);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
int rc, i;
char **ar;
# ifdef _DEBUG
// 這里用來創建一個cmd窗口,或者叫dos窗口,用以輸出調試結果,只在debug版里提供
/*
* If we are using compiling in debug mode, open a console window so
* we can see any printf's, etc...
*
* While we can detect if the program was run from the command-line -
* look at the CMDLINE environment variable, it will be "WIN" for
* programs started from the GUI - the shell seems to run all WIN32
* applications in the background anyways...
*/
AllocConsole();
freopen("conin$", "r", stdin);
freopen("conout$", "w", stdout);
freopen("conout$", "w", stderr);
# endif /* _DEBUG */
ar = (char**) malloc(sizeof(char*) * (__argc + 1));
i = 0;
while (i < __argc) {
int l;
unsigned dstlen;
if (__wargv ) {
for (l = 0; __wargv[i] && __wargv[i][l]; l++) {}; /* is this just wstrlen??? */
dstlen = (l * 5) + 1;
ar[i] = (char*) malloc(dstlen);
/* ar[i][fl_unicode2utf(__wargv[i], l, ar[i])] = 0; */
dstlen = fl_utf8fromwc(ar[i], dstlen, __wargv[i], l);
ar[i][dstlen] = 0;
} else {
for (l = 0; __argv[i] && __argv[i][l]; l++) {};
dstlen = (l * 5) + 1;
ar[i] = (char*) malloc(dstlen);
/* ar[i][mbcs2utf(__argv[i], l, ar[i], dstlen)] = 0; */
ar[i][mbcs2utf(__argv[i], l, ar[i])] = 0;
}
i++;
}
ar[__argc] = 0;
/* Run the standard main entry point function... */
rc = main(__argc, ar);
# ifdef _DEBUG
fclose(stdin);
fclose(stdout);
fclose(stderr);
# endif /* _DEBUG */
return rc;
}
看起來很簡單,就是將winmain包裝了一下,做了一些初始化的工作,再引出main。
osx/linux直接使用了main,所以沒什么可解釋的
接下來是loop。在windows下面比較好理解,打開src/Fl_win32.cxx,找到如下的代碼:
int fl_wait(double time_to_wait) {
...
if (Fl::idle && !in_idle) { // 若處于空閑時間且存在idle函數,執行之
in_idle = 1;
Fl::idle();
in_idle = 0;
}
...
while ((have_message = PeekMessageW(&fl_msg, NULL, 0, 0, PM_REMOVE)) > 0) {
if (fl_send_system_handlers(&fl_msg))
continue;
// Let applications treat WM_QUIT identical to SIGTERM on *nix
if (fl_msg.message == WM_QUIT)
raise(SIGTERM);
if (fl_msg.message == fl_wake_msg) {
// Used for awaking wait() from another thread
thread_message_ = (void*)fl_msg.wParam;
process_awake_handler_requests();
}
TranslateMessage(&fl_msg);
DispatchMessageW(&fl_msg);
}
...
return 1;
}
基本上就是<<Windows程序設計>>上的那一套,就不做說明了
再打開src/Fl_x.cxx,找到fl_wait函數,這里是linux下的loop主體,具體代碼就不分析了,有興趣的可以去找X編程的資料
最后是osx的loop,在osx下面runlooper是不能由程序直接控制的,只能通過外圍發送和接收消息的方式曲線救國,所以FLTK用了一個線程,然后在線程里和runlooper交互。打開src/Fl_cocoa.mm,找到fl_wait函數,再找到DataReady類,這兩個部分組合起來就構成了osx的loop功能,具體實現是用object-c和c/c++混合完成的
以上是各個系統各自的loop功能,最后還要將他們整合起來,打開src/Fl.cxx:
int Fl::run() {
while (Fl_X::first) wait(FOREVER);
return 0;
}
double Fl::wait(double time_to_wait) {
// delete all widgets that were listed during callbacks
do_widget_deletion();
#ifdef WIN32
return fl_wait(time_to_wait);
#elif defined(__APPLE__)
run_checks();
return fl_mac_flush_and_wait(time_to_wait);
#else
if (first_timeout) {
elapse_timeouts();
Timeout *t;
while ((t = first_timeout)) {
if (t->time > 0) break;
// The first timeout in the array has expired.
missed_timeout_by = t->time;
// We must remove timeout from array before doing the callback:
void (*cb)(void*) = t->cb;
void *argp = t->arg;
first_timeout = t->next;
t->next = free_timeout;
free_timeout = t;
// Now it is safe for the callback to do add_timeout:
cb(argp);
}
} else {
reset_clock = 1; // we are not going to check the clock
}
run_checks();
// if (idle && !fl_ready()) {
if (idle) {
if (!in_idle) {
in_idle = 1;
idle();
in_idle = 0;
}
// the idle function may turn off idle, we can then wait:
if (idle) time_to_wait = 0.0;
}
if (first_timeout && first_timeout->time < time_to_wait)
time_to_wait = first_timeout->time;
if (time_to_wait <= 0.0) {
// do flush second so that the results of events are visible:
int ret = fl_wait(0.0);
flush();
return ret;
} else {
// do flush first so that user sees the display:
flush();
if (idle && !in_idle) // 'idle' may have been set within flush()
time_to_wait = 0.0;
return fl_wait(time_to_wait);
}
#endif
}
看起來很明顯,就是將各個平臺的fl_wait包裝起來組合成統一的接口,現在看一個fltk的示例代碼:test/hello.cxx
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
int main(int argc, char **argv) {
Fl_Window *window = new Fl_Window(340,180);
Fl_Box *box = new Fl_Box(20,40,300,100,"Hello, World!");
box->box(FL_UP_BOX);
box->labelfont(FL_BOLD+FL_ITALIC);
box->labelsize(36);
box->labeltype(FL_SHADOW_LABEL);
window->end();
window->show(argc, argv);
return Fl::run();
}
將Fl::run()展開,就是
int main(int argc, char **argv) {
.. // create windows and widgets
while (Fl_X::first) wait(FOREVER);
return 0;
}
這就是FLTK的main-loop模型。簡單,實用,好理解