介面的讀者們都可以體會,不管是 MS Windows 還是 Unix 上的 X Window System 或者是
少數有些人可能有過 MAC OS 下 GUI 程設經驗 ( 我指的是更古老的 MAC 系統,非 OS X ),
使用系統的 Native API 撰寫 GUI 是很痛苦的工程,但是我的過來經驗是有過 Native Level
的 GUI 編程其實還是很有用,因為你會了解到任何 OS 下 GUI 子系統的基本法則都很相似
主要基本觀念不外乎就是 GUI 子系統 (有時候會稱為 Window Manager) 會不斷監控各種
視窗訊息並且將訊息傳送給應用程式,一個完整的應用程式一般會有很多的回呼函式來
回應各種訊息,假如不想處理訊息的就利用特殊的 API 送回系統,已經處理過的就不用送回
系統,而訊息一般都會被轉譯或分派,但是一般不需理會,只要在已經註冊好的視窗回呼函
式用 Switch Case 敘述作訊息攔截撰寫對應動作的程式,這就是系統底層怎麼運作 GUI 程式
的基本原理,當然沒這麼簡單,因為有些訊息往往還會夾帶通知,這樣可以更精確的獲得
UI 本身更細部的行為改變,而且不同訊息所夾帶的附加資訊又不同,所以一般寫比較複雜
的 GUI 程式都要花許多時間查閱資料,基本上 Native GUI API 就是一大堆系統提供的
C 函式組合的集合,不妨將這個基本觀念用圖來表示 ( 這邊以 Windows 系統為例 ):
圖一. 基本視窗程式運作原理
一個視窗基本運作原理的概念圖已呈現於圖一,這張圖非常對於 GUI 程式設計來說非常重要
就算不想寫 Native API 的 GUI 程式,最好能夠牢牢的記於心中。
以上的圖就點出今天的主題,說明為何 GUI 程設是一個頭痛的問題,原因就是現在的
程式沒有一個所謂 " UI " 就遜掉了,人人都希望自己的程式能搭配一個美美的介面,
但是問題來了,絕大部分的工程師並非是這方面專家,讀者可以思考一個簡單的問題,
一般許多領域的專家們也許為該領域寫了不少強大的程式庫,但是,要怎麼將一個好的
程式庫 ( Library )推銷出去展示給別人看呢 ? 答案就是得要為程式外面包裝一個 UI,在
這個時代,沒有 UI 肯定行不通,人們不喜歡看指令介面,請專業的 GUI 工程師寫,
可是很一筆昂貴的費用,因此,在使用的 UI 沒有很難的情況下( 例如想要的 UI 類型不存
在,要從零打造 ), 用一些標準的UI 元件拼一拼其實就可以寫出不錯的介面,可是就算用標準元件,要呼叫系統的那些 Native API 來寫 UI 還是要如圖一這種訊息迴圈的觀念,因此,
為了造福芸芸眾生,這些精通系統視窗運作框架的設計師,就利用 C/C++ 將 GUI 編程給
"物件化",這樣就算使用的人並不是 GUI 專家,也能夠輕鬆跨入視窗程式設計的領域,
這些 "物件化" 的框架其實就是一般常講的 物件導向式的 GUI 框架。其中可以跨平台
的兩套重量級的程式庫當然就是GTK+ 與 Qt,GTK+ 是用 C 語言建構的框架,而 Qt 是用
C++ 建構而框架,而且還為了支援獨特的 Meta-Object System 還延伸出 C++ 沒有的語法,
因此每個有使用這些延伸語法的 C++ 原始檔還必須經過 Meta-Object Compiler 處理展開程
C++ Code 整個程式才能編譯。
另外 Qt 本身是一個相當廣泛的 C++ 應用程式開發框架,用它很輕鬆的寫出具有跨平台
能力的 GUI 應用程式,這也是 Qt 標榜的 "寫一次,就可以到處編譯",這當然是有限制,
跟特定作業系統有關的功能當然就不行了,目前官方有提供各種版本的 Qt 程式庫
Windows、Linux、MacOS X 當然還有其他的平台。既然 Qt 標榜簡單易用,當然並沒有特
別要求使用者必須非常了解 C++,因此今天我們來寫一個簡單的 Qt 程式,看看一支 Qt
程式長甚麼模樣。
首先,我不會講解任何 Meta-Object System 的東西,第二,我也不解釋 Qt Event Processing
深入的機制,作為只是要快速擁有介面的使用者而言,在開發 Qt 程式只需要知道 Qt 對於
Event Processing 要有兩個正確的觀念
1. Signals are useful when using a widget.
2. Events are useful when implementing a widget.
這是甚麼意思,這種就是 C++ 封裝觀念的應用,這告訴我們說,當我們使用 Qt 的 Widget
時,應該要使用的是 Signals,把 Signals 跟某些函式關聯,這樣你觸發的了 UI 的某個 Signals
就會有所反應,而 Events (事件) 是用於一個 Widget 內部,給 Widget 內部自己用,Events
可以用來發射 Signals,這種就物件導向的玩法,聽起來很怪,其實一點都不神奇,這就是
為什麼前面要講一點點視窗基本運作原理,當然讀者有寫過 WinMain 程式的會更好懂,
怎麼講呢,就拿按鈕的例子來說,當你點下滑鼠,QT 會觸發 clicked signal,但是發射這個
訊號的有可能來自你按下空白鍵的事件,或是放開滑鼠左鍵的事件,我想這個例子就很清楚
說明什麼是 Signals 什麼是 Events,有寫過 WinMain 程式的讀者可能就知道固中的原理了,因為對於一個按鈕而言,當我們按下滑鼠左鍵或是放開滑鼠左鍵時,會觸發兩種事件,一種就是 WM_LBUTTONDOWN 與 WM_LBUTTONUP,所以說,只要這兩種事件,通通在訊息迴圈呼叫 clicked() 函式,不就是類似 Qt 這種機制了嗎 ? 所以讀者也許就會好奇,那假如想要讓 按下滑鼠左鍵 也會觸發 clicked signal 呢 ? 恩,我們現在不是用 C 寫 WinMain 可以隨便
攔截訊息,這時候你就要得要乖乖遵守 Qt 框架,你得要繼承按鈕類別在你自己的按鈕版本
內呼叫 放開滑鼠左鍵的事件內發射 clicked signal,這種就是物件導向的思維模式,這種技巧
其實就是有名的 Subclassing,這就是一個 C++ 繼承特性的經典應用,Subclassing 跟物件導向一點關係也沒有,它只是說,把一個現有的子視窗訊息可以單獨在導向至另外一個
獨立的回呼函式做處理,然後又回到主回呼函式,只是說 Subclassing 的樣貌在 C++ 下
可以用繼承的技巧來漂亮實現而已,所以 Subclassing 其實就是一種能夠讓我們對現成
元件可以進行增強與修改的技術。也就是說 signals 還可以自行透過 events 創造。
Subclassing 是要踏入進階 GUI 程設領域基本功,在 ActiveX 進階技術的書籍中,有一本
重要的書籍,名為 ActiveX Programming with Visual C++ 5.0,這是吾人以前研究 ActiveX 很
重要的參考資料,這邊當然不會解釋這個玩意,因為前段的解釋算是一個很基本的簡述
讀者可能還是不太了解,因此,引用此書 Chapter 7 在 206 頁對 Subclassing 的定義
Chapter 7 Advanced ActiveX Control Development with MFC
Subclassing Existing Windows Controls
Since the early days of Windows programming, programmers have enjoyed the option of using
the default behavior of existing Windows controls and extending them slightly to create
new and more powerful controls. This technique of creating Windows controls is referred to as
subclassing ( and there is also superclassing, depending on the technique you use ).
The same is still true for ActiveX controls.
我們不妨把最後一段話換成 The same is still true for Qt Widgets,這邊要特別解釋一下,
Controls 這個術語在 MS Windows 裡面用很多,中文一般稱為控制項,在 Qt 裡面講
Widgets,它們的意義相同。
這段話很準確的定義了 Subclassing 這個術語,可以看到,跟使用甚麼語言一點關係
也沒有,也沒有說要怎麼實現這件事情,只說能夠享受到原本已經存在的控制項預設
的行為並且稍微的延伸它們的功能,讓原來的控制項變得更有威力,讀者要特別注意
這段話,Subclassing 可沒有說會把原來整個控制項通通取代掉,另外一點就是要怎麼
實現這種行為,跟你使用的技術有關 ( 使用的程式語言與架構都會有影響 )
前面這些基本知識講了這麼多,也該來玩玩程式了,我們就找最簡單的 Qt Widget
也就是 QPushButton 與 QCheckBox,從 Qt Assistant 可以知道所有的按鈕類型都是繼承於 QAbstractButton 所以按鈕最基本的一些 signal 一定來自於它的父類別,所以進入父類別
看一看它有甚麼 signal 可以用:
其中 有些 signal 會跟衍生出來的按鈕類型有關,像 toggled signal 就適合用在 QCheckBox
判斷有無勾選,這邊讀者要特別注要,在 Qt 內所有的進階的按鈕類別都是從
QAbstractButton 衍生,但是並不表示說,這些 signal 對繼承的子類就一定得要有意義,像
toggled signal 對 QPushButton 就沒有甚麼意義,因為這種 UI 本來就不是 checkable 型式的
按鈕,你當然可以繼承 QPushButton 讓它變得有意義,例如使用者按下去,按鈕就維持著
"按下去" 的風格,在按一次,按鈕才回復 "彈起來" 的風格,可是我並不想示範這個技巧,
這邊著重的點是帶領讀者怎麼自己看 Qt Assistant 學習 Qt,繼承於 QWidget 作為一
個 Qt 主視窗 ( 就是前面講的 Subclassing ),並且知道怎麼宣告插槽 ( slots ),最後跟
現有的 signal 連結,並且手動呼叫 MOC,完成一個基本 Qt 視窗內的行為互動。
首先,我先給出寫好的原始程式碼:
/* Qt Tutorial */ #include <QtGui\QApplication> #include <QtGui\QFont> #include <QtGui\QGridLayout> #include <QtGui\QPushButton> #include <QtGui\QCheckBox> class MyWidget : public QWidget { Q_OBJECT public: MyWidget(QWidget *parent = 0); QPushButton *button1; QPushButton *button2; QCheckBox *button3; private slots: void reactToToggle(bool checked); }; void MyWidget::reactToToggle(bool checked) { if(checked) button3->setText(tr("On")); else button3->setText(tr("Off")); } MyWidget::MyWidget(QWidget *parent) : QWidget(parent) { button1 = new QPushButton(tr("&Button1")); button1->setFont(QFont("Times", 18, QFont::Bold)); QObject::connect(button1, SIGNAL(pressed()), qApp, SLOT(quit())); button2 = new QPushButton(tr("&Button2")); button2->setFont(QFont("Times", 18, QFont::Bold)); QObject::connect(button2, SIGNAL(released()), qApp, SLOT(quit())); button3 = new QCheckBox(tr("&Button3")); button3->setFont(QFont("Times", 18, QFont::Bold)); QObject::connect(button3, SIGNAL(toggled(bool)), this, SLOT(reactToToggle(bool))); QGridLayout *gridLayout = new QGridLayout; gridLayout->addWidget(button1, 0, 0); gridLayout->addWidget(button2, 1, 0); gridLayout->addWidget(button3, 2, 0); setLayout(gridLayout); } int main(int argc, char *argv[]) { QApplication app(argc, argv); MyWidget widget; widget.setGeometry(100, 100, 400, 200); widget.show(); return app.exec(); }
所有 C++ 建構的 GUI 程式框架都大同小異,所謂的主視窗就是繼承於某個視窗類別
以後整個視窗都是基於這個類別去加入子視窗,用 Qt 的術語就是繼承 QWidget,在
上面的程式 MyWidget 繼承了 QWidget,並且在 MyWidget 內加入三個成員,兩個
QPushButton 與一個 QCheckBox,所以 main 程式其實就是去呼叫這個 Subclassing 過的
Widget 作為主視窗,setGeometry 這個 QWidget 的方法可以設定視窗初始位置與初始大小
那最甚麼要在呼叫 show 這個方法,原因是 Qt 有一個特性,所有的 Widget 預設通通都是
隱藏狀態,所以要呼叫 show 才看的見,最後在呼叫 QApplication 的方法 exec 讓 Qt 程式
整個活動起來,其實骨子裡就是無窮的訊息迴圈,只是這些通通隱藏在 Qt 的執行時期
程式庫裡面,因此看不到。而 qApp 是一個巨集,其實就是 QCoreApplication::instance()
所回傳的指標,這個指標的本質就是 QApplication,掌管了視窗許多的基本特性,
這是 Qt 程式獨一無二的應用程式物件,其中 quit 是從 QCoreApplication 繼承來的
public slots,quit slots 可以讓應用程式結束。這個 qApp 其實就相當於 WinMain 程式
裡面的 hInstance,只是傳統的 C 程式用 Handle 的型式表達,在搭配 API 使用,Qt
或是 MFC 都是類別化的框架,MFC 裡面也有一個 theApp,觀念都是非常類似。
另外在原始碼裡面有用有到 layout,這個就是視窗的佈局管理,寫傳統 WinMain 程式
的開發者應該都知道,佈局管理相當的不好寫,這裡面會牽涉到如何列舉出所有主視窗
下全部的子視窗,並且要使用一些資料結構來管理所有視窗的幾何資訊,當主視窗大小改變
時,是樣讓主視窗內所有的子視窗全部等比例一起放大,還是只有 Widget 寬度變寬高度維持
不變,當然還有很多其他類型的佈局管理,讀者有興趣可以參考 Qt Examples 裡面的 Layouts
像 Dynamic Layouts 就是一個很有趣的例子,當視窗大小改變時,只有 QDial 的大小會改變
其餘 Widget 的大小並不會改變,我想 QGridLayout 這個名稱已經明顯昭告自己的用途了
你可以將 Widget 放入一格一格的佈局中,最後主視窗 MyWidget 呼叫 setLayout 指定使用的
布局管理,這個佈局管理就是前面宣告的 gridLayout。
其實 Qt 框架最經典的就是兩件事:" 訊號與插槽 " 和 " 佈局管理 ",現在來談談 訊號與插槽
我特別示範了將 QCheckBox 的 toggled signal 連結到 MyWidget 自己的私有插槽 ( private slots )
這樣的示範才有意義,讓讀者了解到,怎麼在 Qt 中連結到自己類別裡面的方法作為回應
其它 Widget 發出的 signal,首先看到類別的前頭宣告了 Q_OBJECT,然後類別裡面宣告了
一個要作為插槽的方法,所以前面我們需要使用特殊的 Qt 關鍵字,slots,一般使用者作為
主視窗的 QWidget 類別裡面的插槽都是私用,所以一般都是 private,你想要 public 也沒有說
不行,當然也是可以,程式一樣是可以 work,這樣類別下面的方法經過後面要介紹的 MOC
才會產生所需要的 Meta-Object,說到這裡,以上的程式其實嚴格來說不能執行,可是假如
你貼到像一些 Qt Creator 這類完整的 Qt 環境,或是使用 Qt 外掛在 Visual Studio 內的專案
,當然可以直接編譯通過,原因就是這類環境 " 已經認識 Qt " 了,這些環境會偷偷的呼叫
MOC 產生那些原始程式碼裡面缺少的 Meta-Object,可是這樣 Qt 重要的精隨就沒學到了,這
個步驟最好懂得怎麼用手動的方法產生貼到自己的程式裡面,即使不了解這堆 Meta-Object
是甚麼,但是至少知道有 Meta-Object 的存在,至少知道要把產生的 Meta-Object 貼近來才能
編譯,這些知識在學 Qt 很重要,Qt 為了達成 " 訊號與插槽 " 這種特殊框架,還延伸了 C++
沒有的關鍵字,這些關鍵字,只有 MOC 才看的懂,所以現在我們把上面這個程式放進
MOC 裡面掃描,讓 MOC 把產生程式所需的 Meta-Object:
moc qmain.cpp > qmain.txt
下上面這樣的指令就可以產生所需的 Meta-Object,打開來看看到底產生了甚麼神秘的 Code
/**************************************************************************** ** Meta object code from reading C++ file 'qmain.cpp' ** ** Created: Mon May 13 05:06:17 2013 ** by: The Qt Meta Object Compiler version 62 (Qt 4.6.3) ** ** WARNING! All changes made in this file will be lost! *****************************************************************************/ #if !defined(Q_MOC_OUTPUT_REVISION) #error "The header file 'qmain.cpp' doesn't include <QObject>." #elif Q_MOC_OUTPUT_REVISION != 62 #error "This file was generated using the moc from 4.6.3. It" #error "cannot be used with the include files from this version of Qt." #error "(The moc has changed too much.)" #endif QT_BEGIN_MOC_NAMESPACE static const uint qt_meta_data_MyWidget[] = { // content: 4, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags 0, // signalCount // slots: signature, parameters, type, tag, flags 18, 10, 9, 9, 0x08, 0 // eod }; static const char qt_meta_stringdata_MyWidget[] = { "MyWidget\0\0checked\0reactToToggle(bool)\0" }; const QMetaObject MyWidget::staticMetaObject = { { &QWidget::staticMetaObject, qt_meta_stringdata_MyWidget, qt_meta_data_MyWidget, 0 } }; #ifdef Q_NO_DATA_RELOCATION const QMetaObject &MyWidget::getStaticMetaObject() { return staticMetaObject; } #endif //Q_NO_DATA_RELOCATION const QMetaObject *MyWidget::metaObject() const { return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; } void *MyWidget::qt_metacast(const char *_clname) { if (!_clname) return 0; if (!strcmp(_clname, qt_meta_stringdata_MyWidget)) return static_cast<void*>(const_cast< MyWidget*>(this)); return QWidget::qt_metacast(_clname); } int MyWidget::qt_metacall(QMetaObject::Call _c, int _id, void **_a) { _id = QWidget::qt_metacall(_c, _id, _a); if (_id < 0) return _id; if (_c == QMetaObject::InvokeMetaMethod) { switch (_id) { case 0: reactToToggle((*reinterpret_cast< bool(*)>(_a[1]))); break; default: ; } _id -= 1; } return _id; } QT_END_MOC_NAMESPACE
事實上這些東西原本也是要用手寫的部分,早期的 Qt 可沒有這些工具,而隨著 Qt 框架
越來越成熟,這些只是為了搭配 Qt 獨特的 " 訊號與插槽 " 框架所需的樣版程式碼最後
就變成改由工具來產生程式碼,我們不研究 Meta-Object System 這點前面提過了,上面這
堆東西讀者目前只需要知道,這其實就是 MOC 所輸出的 Q_OBJECT 實作,這部分就是
我們前面程式所缺的部分,看看 Q_OBJECT 在 Qt 的基礎標頭檔中 qobjectdefs.h 內的定義:
#define Q_OBJECT \ public: \ Q_OBJECT_CHECK \ static const QMetaObject staticMetaObject; \ Q_OBJECT_GETSTATICMETAOBJECT \ virtual const QMetaObject *metaObject() const; \ virtual void *qt_metacast(const char *); \ QT_TR_FUNCTIONS \ virtual int qt_metacall(QMetaObject::Call, int, void **); \ private:
metaObject、qt_metacast、qt_metacall 正是我們程式所缺少的三條虛方法,因為這個用手寫
很麻煩,所以請 MOC 幫我們產生這部分的程式碼,MOC 很聰明它會知道目前這宣告
Q_OBJECT 的類別名稱,可以看到實作的程式碼都被自動加上 MyWidget::,所以
MOC 可以分辨目前掃瞄到的 Q_OBJECT 所屬的類別名稱,產生正確的 Meta-Object 實作
因此,我們把 MOC 產生的 Meta-Object 實作整個貼到程式裡面,最後的原始碼變成
/* Qt Tutorial */ #include <QtGui\QApplication> #include <QtGui\QFont> #include <QtGui\QGridLayout> #include <QtGui\QPushButton> #include <QtGui\QCheckBox> class MyWidget : public QWidget { Q_OBJECT public: MyWidget(QWidget *parent = 0); QPushButton *button1; QPushButton *button2; QCheckBox *button3; private slots: void reactToToggle(bool checked); }; /******************************************************** ** Meta object code from reading C++ file 'qmain.cpp' *********************************************************/ #if !defined(Q_MOC_OUTPUT_REVISION) #error "The header file 'qmain.cpp' doesn't include <QObject>." #elif Q_MOC_OUTPUT_REVISION != 62 #error "This file was generated using the moc from 4.6.3. It" #error "cannot be used with the include files from this version of Qt." #error "(The moc has changed too much.)" #endif QT_BEGIN_MOC_NAMESPACE static const uint qt_meta_data_MyWidget[] = { // content: 4, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags 0, // signalCount // slots: signature, parameters, type, tag, flags 18, 10, 9, 9, 0x08, 0 // eod }; static const char qt_meta_stringdata_MyWidget[] = { "MyWidget\0\0checked\0reactToToggle(bool)\0" }; const QMetaObject MyWidget::staticMetaObject = { { &QWidget::staticMetaObject, qt_meta_stringdata_MyWidget, qt_meta_data_MyWidget, 0 } }; #ifdef Q_NO_DATA_RELOCATION const QMetaObject &MyWidget::getStaticMetaObject() { return staticMetaObject; } #endif //Q_NO_DATA_RELOCATION const QMetaObject *MyWidget::metaObject() const { return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; } void *MyWidget::qt_metacast(const char *_clname) { if (!_clname) return 0; if (!strcmp(_clname, qt_meta_stringdata_MyWidget)) return static_cast<void*>(const_cast< MyWidget*>(this)); return QWidget::qt_metacast(_clname); } int MyWidget::qt_metacall(QMetaObject::Call _c, int _id, void **_a) { _id = QWidget::qt_metacall(_c, _id, _a); if (_id < 0) return _id; if (_c == QMetaObject::InvokeMetaMethod) { switch (_id) { case 0: reactToToggle((*reinterpret_cast< bool(*)>(_a[1]))); break; default: ; } _id -= 1; } return _id; } QT_END_MOC_NAMESPACE void MyWidget::reactToToggle(bool checked) { if(checked) button3->setText(tr("On")); else button3->setText(tr("Off")); } MyWidget::MyWidget(QWidget *parent) : QWidget(parent) { button1 = new QPushButton(tr("&Button1")); button1->setFont(QFont("Times", 18, QFont::Bold)); QObject::connect(button1, SIGNAL(pressed()), qApp, SLOT(quit())); button2 = new QPushButton(tr("&Button2")); button2->setFont(QFont("Times", 18, QFont::Bold)); QObject::connect(button2, SIGNAL(released()), qApp, SLOT(quit())); button3 = new QCheckBox(tr("&Button3")); button3->setFont(QFont("Times", 18, QFont::Bold)); QObject::connect(button3, SIGNAL(toggled(bool)), this, SLOT(reactToToggle(bool))); QGridLayout *gridLayout = new QGridLayout; gridLayout->addWidget(button1, 0, 0); gridLayout->addWidget(button2, 1, 0); gridLayout->addWidget(button3, 2, 0); setLayout(gridLayout); } int main(int argc, char *argv[]) { QApplication app(argc, argv); MyWidget widget; widget.setGeometry(100, 100, 400, 200); widget.show(); return app.exec(); }
如此一來,有了 Meta-Object 後整個程式就完整了,設定好相關的 Qt 程式庫以及標頭檔後
這支程式就可以用 C++ 編譯器直接編譯為可執行檔,下面給出執行結果:
執行結果可以看到 toggled signal 跟我們令的私有 slots 連結起來了,當打勾時,Button3
的文字會變成 On,取消核選時,則文字會變成 Off,這種效果用原始的 WinMain 程式設計
不僅需要處理攔截到的視窗訊息,還要處理訊息裡面的通知,才能得知一個 UI 物件
本身的變化,在 Qt 中直接以 signal 的方式呈現,我們僅需要連結到我們自己的插槽,處理
不同的狀態要進行的動作,這也是 Qt 的本意,Qt 原本就是要讓使用者盡可能的專注在
功能程式碼的撰寫,減少 UI 程式設計的負擔。誠如 Qt 標語所講
Code less. Create more.
當然 Qt 程式庫是相當強大,對於專門創造新 UI 元件的軟體工程式而言,當然也能夠
基於 Qt 大量現成的 UI 類別進行改造,在 Qt Examples 裡面就有許多例子,讀者不妨可以
自己看看,另外一點就是 Qt 並不使用系統提供的元件,所有的元件都是 Qt 子系統自己
用繪圖的方式呈現,基本上所有的東西都來自 QWidget。
This is pretty cool, learned a lot.
回覆刪除請問有推薦的書嗎?
Qt 框架創造者們寫的兩本書
回覆刪除C++ GUI Programming with Qt 4
Advanced Qt Programming
其中 C++ GUI Programming with Qt 4 有中譯本
英文版看不習慣的話,可以找本書的中譯本
英文版的話稍微 google 一下應該可以找到電子版
除非 Qt 想要玩到很深入 個人認為
Advanced Qt Programming
其實可讀可不讀