镜缘浮影 小人本住在 苏州的城外 家里有屋又有田 生活乐无边

QT(二).计算器

2017-02-26
wilmosfang
qt
 
原文地址 http://soft.dog/2017/02/26/qt-basic-02/

前言

Qt 是一个著名的 C++ 应用程序框架

本质上来讲,Qt 是一套 C++ 的代码库(类库)与工具集,与开发人员的关系就像军火库对于军人的关系一样,也好比预制构件库对于建筑工程师的关系一样,可以提供各种现成的组件来高效便捷地实现 C++ 应用

Tip: 虽然 Qt 常被用来开发图形界面应用,但它并不仅仅局限于 GUI 应用

Qt 是一个跨平台的框架

Qt is a cross-platform application development framework for desktop, embedded and mobile. Supported Platforms include Linux, OS X, Windows, VxWorks, QNX, Android, iOS, BlackBerry, Sailfish OS and others.

一般有三种策略实现跨平台GUI :

  • API 映射:界面库使用同一套 API,将其映射到不同的底层平台上面
  • API 模拟:API 映射会“缺失”不同平台的特定功能,而 API 模拟可以解决这一问题,不同平台上有差异的 API,使用工具库自己的代码模拟出来
  • GUI 模拟:任何平台都提供了图形绘制函数,例如画点、画线、画面等,工具库利用这些基本函数,再绘制出自己的组件,这就是 GUI 模拟

所以可想而知,同一套 Qt 代码在不同平台上生成的应用,界面风格将会迥异(随平台而定)

QtC++ 编程思想的集大成者,从中可以习得很多优秀的编程最佳实践

前面一篇使用 Qt 实现了一个简单的 helloworld 窗口,下使用 Qt 制作一个简单的计算器

Tip: 当前的最新版本为 Qt 5.8 ,实验使用 Qt 4.8.6,此文中的基础概念参看了 《QT 的信号与槽机制介绍》《Qt 学习之路 2》


概要


平台与环境

应用的开发无法脱离具体的平台与环境,即便声称为跨平台的框架,在现实情况中,同样一套代码,在不同的平台与环境中也不一定会获得相同的效果

[emacs@h102 ~]$ cat /etc/issue
CentOS release 6.6 (Final)
Kernel \r on an \m

[emacs@h102 ~]$ uname -a 
Linux h102.temp 2.6.32-504.el6.x86_64 #1 SMP Wed Oct 15 04:27:16 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
[emacs@h102 ~]$ gcc -v 
Using built-in specs.
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-1.5.0.0/jre --enable-libgcj-multifile --enable-java-maintainer-mode --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --disable-libjava-multilib --with-ppl --with-cloog --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux
Thread model: posix
gcc version 4.4.7 20120313 (Red Hat 4.4.7-11) (GCC) 
[emacs@h102 ~]$ qmake -v 
QMake version 2.01a
Using Qt version 4.8.6 in /usr/local/Trolltech/Qt-4.8.6/lib
[emacs@h102 ~]$

Tip: 虽然 Qt 的最新版本为 Qt 5.8 ,但是这里实验依旧使用的 Qt version 4.8.6


计算器

要求

  • 使用 Qt 写一个 calc 的GUI程序

创建项目

实际上就是创建一个专用的文件夹

[emacs@h102 demo]$ pwd
/home/emacs/demo
[emacs@h102 demo]$ mkdir calc
[emacs@h102 demo]$ ls
calc  hello
[emacs@h102 demo]$ 

设计界面

使用 designer 进行 UI 界面设计

[emacs@h102 calc]$ designer
...
...

Tip: 这条命令在我的具体平台和环境下绝对路径为 /usr/local/Trolltech/Qt-4.8.6/bin/designer ,决定于 Qt 的安装路径,为了方便使用,要将bin目录添加到PATH环境变量中来,以高效调用

最开始会有如下界面:

对话窗口提示创建对象

calc1.png

设计界面

calc2.png

具体的设计方法就是各种托拽,大小调整和布局调整,下面就是一个丑陋的最终产物(实践证明本宝宝在UI的美化上还有很长的路要走)

calc3.png

保存退出后,最终的产物是一个 *.ui 文件

在这个例子中是 calc.ui

[emacs@h102 calc]$ ls calc.ui 
calc.ui
[emacs@h102 calc]$ file calc.ui 
calc.ui: XML  document text
[emacs@h102 calc]$ cat calc.ui 
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Calc</class>
 <widget class="QDialog" name="Calc">
  <property name="enabled">
   <bool>true</bool>
  </property>
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>254</width>
    <height>233</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Dialog</string>
  </property>
  <widget class="QPushButton" name="pbC">
   <property name="geometry">
    <rect>
     <x>11</x>
     <y>51</y>
     <width>53</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>C</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pbD">
   <property name="geometry">
    <rect>
     <x>70</x>
     <y>51</y>
     <width>53</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>D</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pbDiv">
   <property name="geometry">
    <rect>
     <x>129</x>
     <y>51</y>
     <width>53</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>/</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pbMul">
   <property name="geometry">
    <rect>
     <x>188</x>
     <y>51</y>
     <width>52</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>x</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pb7">
   <property name="geometry">
    <rect>
     <x>11</x>
     <y>87</y>
     <width>53</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>7</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pb8">
   <property name="geometry">
    <rect>
     <x>70</x>
     <y>87</y>
     <width>53</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>8</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pb9">
   <property name="geometry">
    <rect>
     <x>129</x>
     <y>87</y>
     <width>53</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>9</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pbMin">
   <property name="geometry">
    <rect>
     <x>188</x>
     <y>87</y>
     <width>52</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>-</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pb4">
   <property name="geometry">
    <rect>
     <x>11</x>
     <y>123</y>
     <width>53</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>4</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pb5">
   <property name="geometry">
    <rect>
     <x>70</x>
     <y>123</y>
     <width>53</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>5</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pb6">
   <property name="geometry">
    <rect>
     <x>129</x>
     <y>123</y>
     <width>53</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>6</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pbAdd">
   <property name="geometry">
    <rect>
     <x>188</x>
     <y>123</y>
     <width>52</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>+</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pb1">
   <property name="geometry">
    <rect>
     <x>11</x>
     <y>159</y>
     <width>53</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>1</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pb2">
   <property name="geometry">
    <rect>
     <x>70</x>
     <y>159</y>
     <width>53</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>2</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pb3">
   <property name="geometry">
    <rect>
     <x>129</x>
     <y>159</y>
     <width>53</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>3</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pbEq">
   <property name="geometry">
    <rect>
     <x>188</x>
     <y>159</y>
     <width>52</width>
     <height>61</height>
    </rect>
   </property>
   <property name="text">
    <string>=</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pbMod">
   <property name="geometry">
    <rect>
     <x>11</x>
     <y>195</y>
     <width>53</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>%</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pb0">
   <property name="geometry">
    <rect>
     <x>70</x>
     <y>195</y>
     <width>53</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>0</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pbPoint">
   <property name="geometry">
    <rect>
     <x>129</x>
     <y>195</y>
     <width>53</width>
     <height>30</height>
    </rect>
   </property>
   <property name="text">
    <string>.</string>
   </property>
  </widget>
  <widget class="QLineEdit" name="le">
   <property name="geometry">
    <rect>
     <x>40</x>
     <y>10</y>
     <width>201</width>
     <height>31</height>
    </rect>
   </property>
  </widget>
  <widget class="QLabel" name="lbOp">
   <property name="geometry">
    <rect>
     <x>20</x>
     <y>20</y>
     <width>16</width>
     <height>18</height>
    </rect>
   </property>
   <property name="text">
    <string>=</string>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>
[emacs@h102 calc]$

从中可知,这是一个冗长的 XML 文件

内容是在描述窗体与各个控件的参数

Qt 就是通过这些参数来绘制图形的

代码示例

main.cpp

#include <QtGui/QApplication> //QApplication 类管理图形用户界面应用程序的控制流和主要设置
#include <QtCore/QTextCodec> //用来进行字符集转化
#include "calc.h" 

int main(int argc, char** argv) 
{
	QApplication app(argc, argv); //构建一个 QApplication 对象 app,这个操作整个运行期间有且只能有一次,并且要在所有其它组件生成之前构建
	QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8")) ; //设定要转化的字符集为UTF-8,但是在Qt内部,其实还是使用的Unicode进行存储
	app.setFont(QFont("simsun", 10)); //设定字体

	Calc c; //定义一个Calc类对象,这个类在calc.h有详细声明
        c.show(); //调用show方法,这个方法继承于QWidget,QWidget::show ,Shows the widget and its child widgets. This function is equivalent to setVisible(true).

	return app.exec(); //进入主事件循环,这是应用逻辑的进入点
}

calc.h

#ifndef CALC_H //这个条件编译是一种很好的编程习惯,加强了代码的健壮性,在大项目中可以有效避免冲突
#define CALC_H  


#include <QWidget>  //QWidget类是所有UI对象的基类
#include <QDialog>  //QDialog对象一般是最顶层的窗口

#include "ui_calc.h" //ui头文件

class Calc: public QDialog, public Ui_Calc //继承自QDialog 和 Ui_Calc , Ui_Calc 是从哪里来的呢,这个后面交代
{
	Q_OBJECT //这个宏不可或缺,否则这个类无法使用QT中的signal和slot机制
public:
	Calc(QWidget *parent = 0); //构造函数
	~Calc(); //析构函数

signals:
	
public slots:
	void append1();
	void append2();
	void append3();
	void append4();
	void append5();
	void append6();
	void append7();
	void append8();
	void append9();
	void append0();
	void appendP();
	void clear();
	void del();
	void add();
	void eq();
	void min();
	void div();
	void mul();
	void mod(); //定义的各种slots函数,以便触发后产生相应操作
	
private:

};

#endif

calc.cpp

#include <stdio.h> //printf 的声明在此
#include "calc.h"  //这个文件中包含了Calc类的定义

QString res("");  //定义一个res的QString来存放结果
float a=1,b=1,r=0; //临时值的存放处
char c='\0'; //操作符的存放处,作为全局是为了方便共享

Calc::Calc(QWidget *parent):QDialog(parent) //构造函数的实现,parent = 0 为默认值,代表这是一个新的窗口
{
  setupUi(this);  //这个函数是Ui_Calc类的一个内联函数,用来设置自身的界面布局和组件分布,还有组件和窗口的形状大小和位置,参数信息来源于之前使用designer生成的XML文件calc.ui
  
  le->setText(res); //将le的内容设置为res,此时为“”
  le->setAlignment(Qt::AlignRight); //将le的显示设定为右对齐
  
  connect(pb1,SIGNAL(clicked()),this,SLOT(append1()));
  connect(pb2,SIGNAL(clicked()),this,SLOT(append2()));
  connect(pb3,SIGNAL(clicked()),this,SLOT(append3()));
  connect(pb4,SIGNAL(clicked()),this,SLOT(append4()));
  connect(pb5,SIGNAL(clicked()),this,SLOT(append5()));
  connect(pb6,SIGNAL(clicked()),this,SLOT(append6()));
  connect(pb7,SIGNAL(clicked()),this,SLOT(append7()));
  connect(pb8,SIGNAL(clicked()),this,SLOT(append8()));
  connect(pb9,SIGNAL(clicked()),this,SLOT(append9()));
  connect(pb0,SIGNAL(clicked()),this,SLOT(append0()));
  connect(pbPoint,SIGNAL(clicked()),this,SLOT(appendP()));
  connect(pbC,SIGNAL(clicked()),this,SLOT(clear()));
  connect(pbD,SIGNAL(clicked()),this,SLOT(del()));
  connect(pbAdd,SIGNAL(clicked()),this,SLOT(add()));
  connect(pbEq,SIGNAL(clicked()),this,SLOT(eq()));
  connect(pbMin,SIGNAL(clicked()),this,SLOT(min()));
  connect(pbMul,SIGNAL(clicked()),this,SLOT(mul()));
  connect(pbDiv,SIGNAL(clicked()),this,SLOT(div()));
  connect(pbMod,SIGNAL(clicked()),this,SLOT(mod())); //使用信号槽的机制将各种对象的点击事件(signal)与处理过程(slot函数)绑定起来
}

Calc::~Calc()
{

}

void Calc::eq() //等号操作函数实现
{
  b=res.toFloat(); //将临时结果进行保存
  if ('+' == c) r=a+b;
  else if  ('-' == c) r=a-b;
  else if  ('x' == c) r=a*b;
  else if  ('/' == c) r=a/b;
  else if  ('%' == c) r=(int)a % (int)b;
  else r=b; //根据操作符的类别进行相应计算
  printf("%f %c %f = %f\n",a,c,b,r); //在终端上进行日志打印,也可以选择不要此步骤
  lbOp->setText(QString("=")); //将操作符标签改为等号
  res=QString::number(r);  //将r的值转化为QString对象赋值给res
  le->setText(res); //将结果显示出来 
  res=QString("");  //清空暂存的内容
}

void Calc::add()  //加法操作函数实现
{
  a=res.toFloat(); 
  c='+';
  lbOp->setText(QString("+"));
  res=QString("");  
  le->setText(res);
}

void Calc::min() //减法操作函数实现
{
  a=res.toFloat();
  c='-';
  lbOp->setText(QString("-"));
  res=QString("");
  le->setText(res);
}

void Calc::mul() //乘法操作函数实现
{
  a=res.toFloat();
  c='x';
  lbOp->setText(QString("x"));
  res=QString("");
  le->setText(res);
}

void Calc::div() //除法操作函数实现
{
  a=res.toFloat();
  c='/';
  lbOp->setText(QString("/"));
  res=QString("");  
  le->setText(res);
}

void Calc::mod() //取模操作函数实现
{
  a=res.toFloat();
  c='%';
  lbOp->setText(QString("%"));
  res=QString("");  
  le->setText(res);
}

void Calc::del()  //删除一个字符
{
  res.chop(1); 
  le->setText(res);
}

void Calc::clear() //清空内容
{
  res=QString("");
  le->setText(res);
}

void Calc::appendP() //输入小数点
{
  res.append(".");
  le->setText(res);
}

void Calc::append1() //输入1
{
  res.append("1");
  le->setText(res);
}

void Calc::append2() //输入2
{
  res.append("2");
  le->setText(res);
}

void Calc::append3()  //输入3
{
  res.append("3");
  le->setText(res);
}

void Calc::append4() //输入4
{
  res.append("4");
  le->setText(res);
}

void Calc::append5() //输入5
{
  res.append("5");
  le->setText(res);
}

void Calc::append6() //输入6
{
  res.append("6");
  le->setText(res);
}

void Calc::append7() //输入7
{
  res.append("7");
  le->setText(res);
}

void Calc::append8() //输入8
{
  res.append("8");
  le->setText(res);
}

void Calc::append9() //输入9
{
  res.append("9");
  le->setText(res);
}

void Calc::append0() //输入0
{
  res.append("0");
  le->setText(res);
}

编译执行

[emacs@h102 calc]$ ls
calc.cpp  calc.h  calc.ui  main.cpp
[emacs@h102 calc]$ alias gtt
alias gtt='qmake -project ; qmake ; make'
[emacs@h102 calc]$ qmake -project
[emacs@h102 calc]$ qmake 
[emacs@h102 calc]$ make
/usr/local/Trolltech/Qt-4.8.6/bin/uic calc.ui -o ui_calc.h
g++ -c -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/local/Trolltech/Qt-4.8.6/mkspecs/linux-g++ -I. -I/usr/local/Trolltech/Qt-4.8.6/include/QtCore -I/usr/local/Trolltech/Qt-4.8.6/include/QtGui -I/usr/local/Trolltech/Qt-4.8.6/include -I. -I. -I. -o calc.o calc.cpp
g++ -c -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/local/Trolltech/Qt-4.8.6/mkspecs/linux-g++ -I. -I/usr/local/Trolltech/Qt-4.8.6/include/QtCore -I/usr/local/Trolltech/Qt-4.8.6/include/QtGui -I/usr/local/Trolltech/Qt-4.8.6/include -I. -I. -I. -o main.o main.cpp
/usr/local/Trolltech/Qt-4.8.6/bin/moc -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/local/Trolltech/Qt-4.8.6/mkspecs/linux-g++ -I. -I/usr/local/Trolltech/Qt-4.8.6/include/QtCore -I/usr/local/Trolltech/Qt-4.8.6/include/QtGui -I/usr/local/Trolltech/Qt-4.8.6/include -I. -I. -I. calc.h -o moc_calc.cpp
g++ -c -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/local/Trolltech/Qt-4.8.6/mkspecs/linux-g++ -I. -I/usr/local/Trolltech/Qt-4.8.6/include/QtCore -I/usr/local/Trolltech/Qt-4.8.6/include/QtGui -I/usr/local/Trolltech/Qt-4.8.6/include -I. -I. -I. -o moc_calc.o moc_calc.cpp
g++ -Wl,-O1 -Wl,-rpath,/usr/local/Trolltech/Qt-4.8.6/lib -o calc calc.o main.o moc_calc.o    -L/usr/local/Trolltech/Qt-4.8.6/lib -lQtGui -L/usr/local/Trolltech/Qt-4.8.6/lib -L/usr/X11R6/lib -lQtCore -lpthread 
[emacs@h102 calc]$ echo $?
0
[emacs@h102 calc]$ ls
calc      calc.h  calc.pro  main.cpp  Makefile      moc_calc.o
calc.cpp  calc.o  calc.ui   main.o    moc_calc.cpp  ui_calc.h
[emacs@h102 calc]$ ./calc 
...
...

会弹出一个小窗口

calc4.png

编译执行过程中没有报错,从结果来看,符合预期

进行操作检查,功能也相对完备


编译过程

从make的编译过程可以看出如下信息

[emacs@h102 calc]$ make
/usr/local/Trolltech/Qt-4.8.6/bin/uic calc.ui -o ui_calc.h
g++ -c -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/local/Trolltech/Qt-4.8.6/mkspecs/linux-g++ -I. -I/usr/local/Trolltech/Qt-4.8.6/include/QtCore -I/usr/local/Trolltech/Qt-4.8.6/include/QtGui -I/usr/local/Trolltech/Qt-4.8.6/include -I. -I. -I. -o calc.o calc.cpp
g++ -c -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/local/Trolltech/Qt-4.8.6/mkspecs/linux-g++ -I. -I/usr/local/Trolltech/Qt-4.8.6/include/QtCore -I/usr/local/Trolltech/Qt-4.8.6/include/QtGui -I/usr/local/Trolltech/Qt-4.8.6/include -I. -I. -I. -o main.o main.cpp
/usr/local/Trolltech/Qt-4.8.6/bin/moc -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/local/Trolltech/Qt-4.8.6/mkspecs/linux-g++ -I. -I/usr/local/Trolltech/Qt-4.8.6/include/QtCore -I/usr/local/Trolltech/Qt-4.8.6/include/QtGui -I/usr/local/Trolltech/Qt-4.8.6/include -I. -I. -I. calc.h -o moc_calc.cpp
g++ -c -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/local/Trolltech/Qt-4.8.6/mkspecs/linux-g++ -I. -I/usr/local/Trolltech/Qt-4.8.6/include/QtCore -I/usr/local/Trolltech/Qt-4.8.6/include/QtGui -I/usr/local/Trolltech/Qt-4.8.6/include -I. -I. -I. -o moc_calc.o moc_calc.cpp
g++ -Wl,-O1 -Wl,-rpath,/usr/local/Trolltech/Qt-4.8.6/lib -o calc calc.o main.o moc_calc.o    -L/usr/local/Trolltech/Qt-4.8.6/lib -lQtGui -L/usr/local/Trolltech/Qt-4.8.6/lib -L/usr/X11R6/lib -lQtCore -lpthread 
[emacs@h102 calc]$
  • 1.uic : calc.ui->ui_calc.h
  • 2.g++ : calc.cpp->calc.o
  • 3.g++ : main.cpp->main.o
  • 4.moc : calc.h->moc_calc.cpp
  • 5.g++ : moc_calc.cpp->moc_calc.o
  • 6.g++ : calc.o main.o moc_calc.o ->calc

生成关系

相互之间的生成关系为

calc.ui->ui_calc.h
calc.cpp->calc.o------------------|
main.cpp->main.o------------------|->calc
calc.h->moc_calc.cpp->moc_calc.o--|

依赖关系

从源码中的依赖关系可知

[emacs@h102 calc]$ grep include main.cpp 
#include <QtGui/QApplication>
#include <QtCore/QTextCodec>
#include "calc.h"
[emacs@h102 calc]$ grep include calc.cpp 
#include <stdio.h>
#include "calc.h"
[emacs@h102 calc]$ grep include calc.h 
#include <QDialog>
#include <QWidget>
#include "ui_calc.h"
[emacs@h102 calc]$

相互之间的依存关系为

calc.ui-[uic]->ui_calc.h   |->main.cpp-[g++]->main.o------------------|
                     |->calc.h-[moc]->moc_calc.cpp-[g++]->moc_calc.o--|->calc
                           |->calc.cpp-[g++]->calc.o------------------| 

*.pro 文件与 Makefile 文件的关系如下

source-[qmake -project]->*.pro-[qmake]->Makefile--|-[make]->target
    |---------------------------------------------|

信号槽

信号和槽机制是 QT 的核心机制,要精通 QT 编程就必须对信号和槽有所了解。信号和槽是一种高级接口,应用于对象之间的通信,它是 QT 的核心特性,也是 QT 区别于其它工具包的重要地方。信号和槽是 QT 自行定义的一种通信机制,它独立于标准的 C/C++ 语言,因此要正确的处理信号和槽,必须借助一个称为 moc(Meta Object Compiler)的 QT 工具,该工具是一个 C++ 预处理程序,它为高层次的事件处理自动生成所需要的附加代码

在我们所熟知的很多 GUI 工具包中,窗口小部件 (widget) 都有一个回调函数用于响应它们能触发的每个动作,这个回调函数通常是一个指向某个函数的指针。但是,在 QT 中信号和槽取代了这些凌乱的函数指针,使得我们编写这些通信程序更为简洁明了。 信号和槽能携带任意数量和任意类型的参数,他们是类型完全安全的,不会像回调函数那样产生 core dumps

所有从 QObject 或其子类 ( 例如 Qwidget) 派生的类都能够包含信号和槽。当对象改变其状态时,信号就由该对象发射 (emit) 出去,这就是对象所要做的全部事情,它不知道另一端是谁在接收这个信号。这就是真正的信息封装,它确保对象被当作一个真正的软件组件来使用。槽用于接收信号,但它们是普通的对象成员函数。一个槽并不知道是否有任何信号与自己相连接。而且,对象并不了解具体的通信机制

你可以将很多信号与单个的槽进行连接,也可以将单个的信号与很多的槽进行连接,甚至于将一个信号与另外一个信号相连接也是可能的,这时无论第一个信号什么时候发射系统都将立刻发射第二个信号。总之,信号与槽构造了一个强大的部件编程机制

信号

当某个信号对其客户或所有者发生的内部状态发生改变,信号被一个对象发射。只有 定义过这个信号的类及其派生类能够发射这个信号。当一个信号被发射时,与其相关联的槽将被立刻执行,就象一个正常的函数调用一样。信号 - 槽机制完全独立于任何 GUI 事件循环。只有当所有的槽返回以后发射函数(emit)才返回。 如果存在多个槽与某个信号相关联,那么,当这个信号被发射时,这些槽将会一个接一个地 执行,但是它们执行的顺序将会是随机的、不确定的,我们不能人为地指定哪个先执行、哪 个后执行

signals 可以有如下形式

signals: 
		 void mySignal(); 
		 void mySignal(int x); 
		 void mySignalParam(int x,int y);

槽是普通的 C++ 成员函数,可以被正常调用,它们唯一的特殊性就是很多信号可以与其相关联。当与其关联的信号被发射时,这个槽就会被调用。槽可以有参数,但槽的参数不能有缺省值

既然槽是普通的成员函数,因此与其它的函数一样,它们也有存取权限。槽的存取权限决定了谁能够与其相关联。同普通的 C++ 成员函数一样,槽函数也分为三种类型,即 public slots、private slots 和 protected slots

  • public slots:在这个区内声明的槽意味着任何对象都可将信号与之相连接。这对于组件编程非常有用,你可以创建彼此互不了解的对象,将它们的信号与槽进行连接以便信息能够正确的传递
  • protected slots:在这个区内声明的槽意味着当前类及其子类可以将信号与之相连接。这适用于那些槽,它们是类实现的一部分,但是其界面接口却面向外部
  • private slots:在这个区内声明的槽意味着只有类自己可以将信号与之相连接。这适用于联系非常紧密的类

slots 可以有如下形式

public slots: 
		 void mySlot(); 
		 void mySlot(int x); 
		 void mySignalParam(int x,int y);

信号与槽的关联

通过调用 QObject 对象的 connect 函数来将某个对象的信号与另外一个对象的槽函数相关联,这样当发射者发射信号时,接收者的槽函数将被调用

bool QObject::connect ( const QObject * sender, const char * signal, 
		 const QObject * receiver, const char * member ) [static]

这个函数的作用就是将发射者 sender 对象中的信号 signal 与接收者 receiver 中的 member 槽函数联系起来。当指定信号 signal 时必须使用 QT 的宏 SIGNAL(),当指定槽函数时必须使用宏 SLOT()。如果发射者与接收者属于同一个对象的话,那么在 connect 调用中接收者参数可以省略

一个信号也能够与另一个信号相关联

当信号与槽没有必要继续保持关联时,我们可以使用 disconnect 函数来断开连接

bool QObject::disconnect ( const QObject * sender, const char * signal, 
		 const Object * receiver, const char * member ) [static]

Tip: 详细内容可以参看 《QT 的信号与槽机制介绍》


QApplication

QApplication类管理图形用户界面应用程序的控制流和主要设置

QApplication::QApplication

QApplication 的构造函数

QApplication::QApplication ( int & argc, char ** argv )

使用main的argc和argv 来进行初始化

Initializes the window system and constructs an application object with argc command line arguments in argv

整个应用运行期间有且只能有一个 QApplication 对象,并且这个对象要在所有其它的组件生成之前构建(第一个构建)

Note: The data pointed to by argc and argv must stay valid for the entire lifetime of the QApplication object. The global qApp pointer refers to this application object. Only one application object should be created. This application object must be constructed before any paint devices (including widgets, pixmaps, bitmaps etc.)

QApplication::setFont

void QApplication::setFont ( const QFont & font, const char * className = 0 ) [static]

设定默认的应用字体

Changes the default application font to font. If className is passed, the change applies only to classes that inherit className (as reported by QObject::inherits()).On application start-up, the default font depends on the window system. It can vary depending on both the window system version and the locale. This function lets you override the default font; but overriding may be a bad idea because, for example, some locales need extra large fonts to support their special characters


QApplication::exec

执行应用,进入主事件循环,直到 exit() 被调用或者主窗体被注销

Enters the main event loop and waits until exit() is called or the main widget is destroyed, and returns the value that was set to exit() (which is 0 if exit() is called via quit()).

这个就是应用程序的进入点

It is necessary to call this function to start event handling. The main event loop receives events from the window system and dispatches these to the application widgets


QFont

用来指定字体

The QFont class specifies a font used for drawing text


QTextCodec

QTextCodec 类用来进行字符集转化

The QTextCodec class provides conversions between text encodings.

Qt uses Unicode to store, draw and manipulate strings. In many situations you may wish to deal with data that uses a different encoding

Qt 统一使用 Unicode 字符集来存储信息,QTextCodec 的意义就在于将Unicode 字符集与其它字符集进行相互转化

可以将这个类简单看作是用来设定字符集的(尽管事实上,存储在Qt里的依然是Unicode)

Qt provides a set of QTextCodec classes to help with converting non-Unicode formats to and from Unicode. You can also create your own codec classes

Qt 可以支持如下字符集

  • Apple Roman
  • Big5
  • Big5-HKSCS
  • EUC-JP
  • EUC-KR
  • GB18030-0
  • IBM 850
  • IBM 866
  • IBM 874
  • ISO 2022-JP
  • ISO 8859-1 to 10
  • ISO 8859-13 to 16
  • Iscii-Bng, Dev, Gjr, Knd, Mlm, Ori, Pnj, Tlg, and Tml
  • JIS X 0201
  • JIS X 0208
  • KOI8-R
  • KOI8-U
  • MuleLao-1
  • ROMAN8
  • Shift-JIS
  • TIS-620
  • TSCII
  • UTF-8
  • UTF-16
  • UTF-16BE
  • UTF-16LE
  • Windows-1250 to 1258
  • WINSAMI2

QTextCodec::setCodecForTr

用来设定转化的字符集

void QTextCodec::setCodecForTr ( QTextCodec * c ) [static]

Sets the codec used by QObject::tr() on its argument to c. If c is 0 (the default), tr() assumes Latin-1, If the literal quoted text in the program is not in the Latin-1 encoding, this function can be used to set the appropriate encoding

Note: 这是设定要转化的字符集

QTextCodec::codecForName

根据参数搜索已经安装的那么多字符集中最可能的那个字符集的

QTextCodec * QTextCodec::codecForName ( const char * name ) [static]

Searches all installed QTextCodec objects and returns the one which best matches name; the match is case-insensitive. Returns 0 if no codec matching the name name could be found


QWidget

QWidget 是所有UI对象的基类

The QWidget class is the base class of all user interface objects.

它接受鼠标,键盘事件,并且负责定位和显示自身形状

The widget is the atom of the user interface: it receives mouse, keyboard and other events from the window system, and paints a representation of itself on the screen. Every widget is rectangular, and they are sorted in a Z-order. A widget is clipped by its parent and by the widgets in front of it

QWidget::QWidget

QWidget 的构造函数接受一个或两个参数

QWidget::QWidget ( QWidget * parent = 0, Qt::WindowFlags f = 0 )

要创建一个新的窗体时,要使 parent=0 ,否则它就是一个子窗口,如果父窗口注销了,子窗口会随之注销

Constructs a widget which is a child of parent, with widget flags set to f.

If parent is 0, the new widget becomes a window. If parent is another widget, this widget becomes a child window inside parent. The new widget is deleted when its parent is deleted.

The widget flags argument, f, is normally 0, but it can be set to customize the frame of a window (i.e. parent must be 0). To customize the frame, use a value composed from the bitwise OR of any of the window flags.

If you add a child widget to an already visible widget you must explicitly show the child to make it visible.


QDialog

QDialog 是对话窗口的基类,继承自QWidget

The QDialog class is the base class of dialog windows

A dialog window is a top-level window mostly used for short-term tasks and brief communications with the user. QDialogs may be modal or modeless. QDialogs can provide a return value, and they can have default buttons. QDialogs can also have a QSizeGrip in their lower-right corner, using setSizeGripEnabled().

Note that QDialog (an any other widget that has type Qt::Dialog) uses the parent widget slightly differently from other classes in Qt. A dialog is always a top-level widget, but if it has a parent, its default location is centered on top of the parent’s top-level widget (if it is not top-level itself). It will also share the parent’s taskbar entry.

QDialog::QDialog

QDialog 类的构造函数

QDialog::QDialog ( QWidget * parent = 0, Qt::WindowFlags f = 0 )

一般而言,一个对话框是一个最顶层的窗口,如果它不是最顶层的,它将在父窗体的中间进行显示

A dialog is always a top-level widget, but if it has a parent, its default location is centered on top of the parent. It will also share the parent’s taskbar entry.

The widget flags f are passed on to the QWidget constructor. If, for example, you don’t want a What’s This button in the title bar of the dialog, pass Qt::WindowTitleHint Qt::WindowSystemMenuHint in f

Q_OBJECT 宏

Qt/qobjectdefs.h 中有关于 Q_OBJECT 宏的定义

/* tmake ignore Q_OBJECT */
#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: \
    Q_DECL_HIDDEN static const QMetaObjectExtraData staticMetaObjectExtraData; \
    Q_DECL_HIDDEN static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);

只有加入了Q_OBJECT,才能使用QT中的signal和slot机制

The Q_OBJECT macro at the beginning of the class definition is necessary for all classes that define signals or slots

如果不加上这个宏,有什么效果呢

可以顺利编译通过,但是如果运行,虽然可以生成界面,但会产生大量的如下报错,各种事件也不会响应

[emacs@h102 calc]$ ./calc 
Object::connect: No such slot QDialog::append1()
Object::connect:  (sender name:   'pb1')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::append2()
Object::connect:  (sender name:   'pb2')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::append3()
Object::connect:  (sender name:   'pb3')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::append4()
Object::connect:  (sender name:   'pb4')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::append5()
Object::connect:  (sender name:   'pb5')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::append6()
Object::connect:  (sender name:   'pb6')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::append7()
Object::connect:  (sender name:   'pb7')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::append8()
Object::connect:  (sender name:   'pb8')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::append9()
Object::connect:  (sender name:   'pb9')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::append0()
Object::connect:  (sender name:   'pb0')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::appendP()
Object::connect:  (sender name:   'pbPoint')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::clear()
Object::connect:  (sender name:   'pbC')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::del()
Object::connect:  (sender name:   'pbD')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::add()
Object::connect:  (sender name:   'pbAdd')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::eq()
Object::connect:  (sender name:   'pbEq')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::min()
Object::connect:  (sender name:   'pbMin')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::mul()
Object::connect:  (sender name:   'pbMul')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::div()
Object::connect:  (sender name:   'pbDiv')
Object::connect:  (receiver name: 'Calc')
Object::connect: No such slot QDialog::mod()
Object::connect:  (sender name:   'pbMod')
Object::connect:  (receiver name: 'Calc')
...
...

QString

QString是Qt中使用很频繁的类,它提供了一个Unicode构成的字符串

The QString class provides a Unicode character string

QString 由 QChars 组成 ,每个字符是16位,即 2 Byte

QString stores a string of 16-bit QChars, where each QChar corresponds one Unicode 4.0 character. (Unicode characters with code values above 65535 are stored using surrogate pairs, i.e., two consecutive QChars.)

之所以选择Unicode是为了兼容

Unicode is an international standard that supports most of the writing systems in use today. It is a superset of ASCII and Latin-1 (ISO 8859-1), and all the ASCII/Latin-1 characters are available at the same code positions.

QString::QString

QString::QString ( const char * str )

使用字符串来构造一个QString对象

Constructs a string initialized with the ASCII string str. The given const char pointer is converted to Unicode using the fromAscii() function

QString::toFloat

将QString的对象转换为float值

Returns the string converted to a float value

If a conversion error occurs, *ok is set to false; otherwise *ok is set to true. Returns 0.0 if the conversion fails

     QString str1 = "1234.56";
     str1.toFloat();             // returns 1234.56

     bool ok;
     QString str2 = "R2D2";
     str2.toFloat(&ok);          // returns 0.0, sets ok to false

QString::number

将数值转化为Qstring对象(或者通过数值来初始化对象)

Returns a string equivalent of the number n according to the specified base

它有如下几种重载

QString QString::number ( long n, int base = 10 )   [static]
QString QString::number ( ulong n, int base = 10 )   [static]
QString QString::number ( int n, int base = 10 )   [static]
QString QString::number ( uint n, int base = 10 )   [static]
QString QString::number ( qlonglong n, int base = 10 )   [static]
QString QString::number ( qulonglong n, int base = 10 )   [static]
QString QString::number ( double n, char format = 'g', int precision = 6 )   [static]

QString::chop

void QString::chop ( int n )

删除掉字符串末尾的几个字符

Removes n characters from the end of the string, If n is greater than size(), the result is an empty string

QString::append

在字符串末尾追加字符或字符串

Appends the string str onto the end of this string.

这个追加操作非常快,原因是QString有预留空间,不必每次都重新分配整个新的字符串空间

The append() function is typically very fast (constant time), because QString preallocates extra space at the end of the string data so it can grow without reallocating the entire string each time

有如下几种重载形式

QString & QString::append ( const QString & str )
QString & QString::append ( const QLatin1String & str )
QString & QString::append ( const QByteArray & ba )
QString & QString::append ( const char * str )
QString & QString::append ( QChar ch )

QLabel

QLabel 对象可以用来显示文本和图片

The QLabel widget provides a text or image display, No user interaction functionality is provided. The visual appearance of the label can be configured in various ways, and it can be used for specifying a focus mnemonic key for another widget

QLabel 对象可以包含如下类型的对象

Content Setting
Plain text Pass a QString to setText().
Rich text Pass a QString that contains rich text to setText().
A pixmap Pass a QPixmap to setPixmap().
A movie Pass a QMovie to setMovie().
A number Pass an int or a double to setNum(), which converts the number to plain text.
Nothing The same as an empty plain text. This is the default. Set by clear().

setAlignment

配置对齐方式

void setAlignment ( Qt::Alignment )

This property holds the alignment of the label’s contents

详细的对齐方式可以参阅官方的API文档,大体上分为水平和垂直两类

水平对齐有:

  • Qt::AlignLeft
  • Qt::AlignRight
  • Qt::AlignHCenter
  • Qt::AlignJustify

垂直对齐有:

  • Qt::AlignTop
  • Qt::AlignBottom
  • Qt::AlignVCenter

二维居中:

  • Qt::AlignCenter

connect

qobject.h 中有关于 connect 的声明

inline bool QObject::connect(const QObject *asender, const char *asignal,
                             const char *amember, Qt::ConnectionType atype) const
{ return connect(asender, asignal, this, amember, atype); }
...
...
    static bool connect(const QObject *sender, const char *signal,
                        const QObject *receiver, const char *member, Qt::ConnectionType =
#ifdef qdoc
                        Qt::AutoConnection
#else
#ifdef QT3_SUPPORT
                        Qt::AutoCompatConnection
#else
                        Qt::AutoConnection
#endif
#endif
        );

    static bool connect(const QObject *sender, const QMetaMethod &signal,
                        const QObject *receiver, const QMetaMethod &method,
                        Qt::ConnectionType type =
#ifdef qdoc
                        Qt::AutoConnection
#else
#ifdef QT3_SUPPORT
                        Qt::AutoCompatConnection
#else
                        Qt::AutoConnection
#endif
#endif
        );

    inline bool connect(const QObject *sender, const char *signal,
                        const char *member, Qt::ConnectionType type =
#ifdef qdoc
                        Qt::AutoConnection
#else
#ifdef QT3_SUPPORT
                        Qt::AutoCompatConnection
#else
                        Qt::AutoConnection
#endif
#endif
        ) const;

这是关于 connect 函数的几种形式

关于参数的格式可以如下表示

connect(sender,SIGNAL(signal()),receiver,SLOT(slot()));

SIGNAL SLOT

qobjectdefs.h 中有关于 SIGNALSLOT 的宏定义

#ifndef QT_NO_DEBUG
# define QLOCATION "\0" __FILE__ ":" QTOSTRING(__LINE__)
# ifndef QT_NO_KEYWORDS
#  define METHOD(a)   qFlagLocation("0"#a QLOCATION)
# endif
# define SLOT(a)     qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a)   qFlagLocation("2"#a QLOCATION)
#else
# ifndef QT_NO_KEYWORDS
#  define METHOD(a)   "0"#a
# endif
# define SLOT(a)     "1"#a
# define SIGNAL(a)   "2"#a
#endif

源码的引用层级比较深,所以详细调用过程可以参看 QT QObject::connect函数的学习 ,讲得比较透彻


总结

掌握如下概念对Qt的学习非常有用

  • 信号

如下类是Qt中常用的类,掌握它们的属性和方法很有必要

  • QApplication
  • QFont
  • QTextCodec
  • QWidget
  • QDialog
  • QString
  • QLabel

以下函数和宏非常重要

  • connect
  • SIGNAL
  • SLOT
  • Q_OBJECT
原文地址 http://soft.dog/2017/02/26/qt-basic-02/

评论