Qt是1991年奇趣科技开发的一个跨平台的c++图形用户界面应用程序框架,它除了能提供给应用程序开发者建立图形用户界面所需要的功能外,还继承了很多第三方资源,如数据库、网络/多线程编程等,因此Qt既可以开发GUI程序,也可用于开发非GUI程序。
跟Qt放在一起讨论的是Qt Creator,它是用于Qt开发的轻量级跨平台继承开发环境,该IDE集成了c++代码编辑器,可视化调试器,源代码管理、项目构建个管理等。若想学习Qt编程,我推荐跟着狄泰唐老师的课程学习。
Qt作为一个应用程序框架,其设计思想有很多值得深入学习的地方。几易其主的Qt至今仍占有一席之地,也足矣证明这点。很多人说这是一个看脸的世界,我不否认,Android就是一个很好的例子,随之,“学习Qt不如学习Android”的言论大行其道。但是渐渐地我发现,此言差矣。学习Qt不如学习Android的看法大都出自于之前开发诺基亚塞班的软件工程师,事实上,Qt的价值依旧存在。当然啦,它不是体现在手机平板这样的消费类电子,而是在工业,或者医疗,甚至是汽车电子中。试想,作为一个工业控制系统,有必要去移植如此庞大的安卓?所以还是有很多公司更愿意选择选择ARM + Linux + Qt这样的产品设计的。这些是站在毕业一年,在深圳工作一年的角度的看法。
Qt是用c++语言开发的,那必须得面向对象。回顾前面写的程序中,显然是面向过程的设计流程。操作系统提供了创建用户界面所需要各个函数,通过调用这些功能不一的函数即可创建出界面元素。面向过程的弊端是:当GUI程序稍微大型,程序的开发效率低且不易维护。面向对象的思想更适合于GUI程序的设计。
用面向对象的思想看待GUI界面元素,即GUI用户界面是由各种不相同的对象组成的,如主窗口对象、菜单对象、按钮对象等等。将界面元素定义为对应的类,通过抽象和封装可以隐藏界面元素的细节。下来对前面的程序进行面向对象化。
1. 面向对象的GUI程序设计
1.1 构建widget基类
widget类是用于描述各种窗口元素(主窗口、窗口内元素)的基类:
//Widget.h
#pragma once //防止重复包含
#include <windows.h>
class Widget{
protected:
HWND m_hwnd; //widget的句柄
Widget* m_parent; //该元素所属的父类元素
//例如主窗口内的按钮,按钮的m_parent为主窗口,
//主窗口的m_parent为NULL
public:
Widget(); //构造函数,构造主窗口时用
Widget(Widget* parent); //带参构造函数,构造主窗口内元素时用
HWND hwnd(); //返回widget的句柄
Widget* parent(); //返回widget的parent
};
//Widget.cpp
#include "Widget.h"
Widget::Widget()
{
m_parent = NULL;
}
Widget::Widget(Widget* parent)
{
m_parent = parent;
}
HWND Widget::hwnd()
{
return m_hwnd;
}
Widget* Widget::parent()
{
return m_parent;
}
1.2 构建主窗口类
//MainWindows.h
#pragma once
#include "Widget.h"
class MainWindows : public Widget //继承于Widget
{
protected:
static const wchar_t STYLE_NAME[];
//定义主窗口
BOOL DefineMainWindows(HINSTANCE hInstance);
//创建主窗口
void CreateMainWindows(HINSTANCE hInstance);
//主窗口消息处理函数
static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
public:
MainWindows(HINSTANCE hInstance, const wchar_t* title);
void show();
};
#include "MainWindow.h"
const wchar_t MainWindow::STYLE_NAME[] = L"Win";
//主窗口构造函数,实现定义/创建主窗口
MainWindow::MainWindow(HINSTANCE hInstance, const wchar_t* title) : Widget(NULL)
{
DefineMainWindows(hInstance);
CreateMainWindows(hInstance);
}
//显示主窗口
void MainWindow::show()
{
ShowWindow(m_hwnd, SW_SHOWNORMAL); // 显示窗口
UpdateWindow(m_hwnd); // 刷新窗口
}
//定义主窗口
BOOL MainWindow::DefineMainWindow(HINSTANCE hInstance)
{
static WNDCLASS WndClass = {0}; // 系统结构体类型
// 用于描述窗口样式
WndClass.style = 0;
WndClass.cbClsExtra = 0;
WndClass.cbClsExtra = 0;
WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW); // 定义窗口背景色
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW); // 定义鼠标样式
WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); // 定义窗口左上角图标
WndClass.hInstance = hInstance; // 定义窗口式样属于当前应用程序
WndClass.lpfnWndProc = WndProc; // 窗口消息处理函数
WndClass.lpszClassName = STYLE_NAME; // 窗口样式名
WndClass.lpszMenuName = NULL;
// 将定义好的窗口式样注册到系统
return RegisterClass(&WndClass);
}
//创建主窗口
void MainWindow::CreateMainWindow(HINSTANCE hInstance, const wchar_t* title)
{
//返回主窗口的句柄,赋给基类的m_hwnd成员
m_hwnd = CreateWindow(STYLE_NAME, // 通过定义好的窗口式样创建主窗口
title, // 主窗口标题
WS_OVERLAPPEDWINDOW, // 创建后主窗口的显示风格
CW_USEDEFAULT, // 主窗口左上角 x 坐标
CW_USEDEFAULT, // 主窗口左上角 y 坐标
CW_USEDEFAULT, // 主窗口宽度
CW_USEDEFAULT, // 主窗口高度
NULL, // 父窗口
NULL, // 窗口菜单栏
hInstance, // 主窗口属于当前应用程序
NULL); // 窗口参数
}
//主窗口的消息处理函数
LRESULT CALLBACK MainWindow::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_COMMAND:
if (HIWORD(WM_COMMAND) == BN_CLICKED)
{
MessageBox(hwnd, L"Hello Button!", L"Message", 0);
}break;
}
// 调用系统提供的默认消息处理函数
return DefWindowProc(hwnd, message, wParam, lParam);
}
1.3 构建按钮类
//PushButton.h
#pragma once
#include "Widget.h"
class PushButton : public Widget
{
public:
PushButton(Widget* win, const wchar_t *text);
};
#include "PushButton.h"
// 构造函数创建按钮
PushButton::PushButton(Widget* win, const wchar_t* text)
{
HINSTANCE hInstance = (HINSTANCE)GetWindowLong(win->hwnd(), GWL_HINSTANCE);
//返回按钮的句柄
m_hwnd = CreateWindow(L"button", // 通过系统预定义式样创建窗口元素
text, // 窗口元素标题
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, // 窗口元素的显示风格
50, // 窗口元素在窗口中的左上角 x 坐标
50, // 窗口元素在窗口中的左上角 y 坐标
200, // 窗口元素的宽度
60, // 窗口元素的高度
win->hwnd(), // 窗口元素所在的父窗口
(HMENU)this, // 窗口元素 ID 值
hInstance, // 窗口元素属于当前应用程序
NULL); // 窗口元素参数
}
1.4 构建消息处理类
#pragma once
#include <windows.h>
class Application
{
public:
Application(HINSTANCE hInstance, LPSTR lpCmdLine);
bool exec();
};
//Application.cpp
#include "Application.h"
Application::Application(HINSTANCE hInstance, LPSTR lpCmdLine)
{
}
bool Application::exec()
{
MSG Msg = {0};
// 进入消息循环
while( GetMessage(&Msg, NULL, NULL, NULL) )
{
// 翻译并转换系统消息
TranslateMessage(&Msg);
// 分发消息到对应的消息处理函数
DispatchMessage(&Msg);
}
return TRUE;
}
1.5 main函数
在main.cpp中,调用上面封装的类:
#include <windows.h>
#include "Application.h"
#include "MainWindow.h"
#include "PushButton.h"
BOOL WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
//定义接收消息的类
Application a(hInstance, lpCmdLine);
//定义/创建主窗口
MainWindow w(hInstance, L"Main Window");
//创建窗口内按钮元素
PushButton b(&w, L"My Button");
//显示
w.show();
//进入接收消息循环
return a.exec();
}
只要定义好所有界面元素的类,在main函数中只要将这些类组合起来即可构建GUI程序了,多么简单。但是关键难点在于这些类的构造。Qt就是为解决这个难点而应运而生的一套GUI元素的库,它将不同操作系统的GUI细节封装于类的内部,同样遵循经典的GUI程序设计原理:界面元素 + 消息处理。
2. 第一个Qt程序
#include <QApplication>
#include <QMainWindow>
#include <QPushButton>
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
QMainWindow w;
QPushButton b(&w);
b.setText("hello Qt");
w.show();
return a.exec();
}
在Qt开发环境安装完毕的Linux操作系统上,进入源文件目录:
$ qmake -project //根据目录中的源码生成工程文件“目录名.pro”
$ qmake //生成Makefile文件
$ make //编译
运行:
由此证明,Qt上GUI程序设计的封装实现是跟前面封装Window的API是类似的。在源码目录下执行”qmake -project”生成的.pro文件可以直接用Qt的集成开发环境Qt Creator打开。