Add New Notes

This commit is contained in:
geekard
2012-08-08 14:26:04 +08:00
commit 5ef7c20052
2374 changed files with 276187 additions and 0 deletions

View File

@@ -0,0 +1,161 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T21:10:59+08:00
====== QDialog 模态对话框与事件循环 ======
Created Monday 31 October 2011
===== 起源 =====
qtcn中文论坛中有网友问到
假设程序正常运行时只有一个简单的窗体A此时只有一个GUI主线程在这个主线程中有一个事件循环处理窗体上的事件。当此程序运行到某阶段时弹出一个模态窗体B书上说模态窗体是有其自己的事件循环的此时模态窗体B是否会有一个对应的子线程处理其事件循环
这儿其实有两个问题:
* 模态对话框 和 事件循环 没有必然联系
* 事件循环 和 子线程 没有必然联系
===== 题外: =====
如果进一步呢?其实我们还可以说:
* 模态对话框 和 QDialog 没必要联系
===== QDialog 对话框 =====
两种常规用法:
**非模态**
QDialog * dlg = new QDialog()
dlg->show();
当然,这儿用指针(即分配到heap中)不是必须的。 (有疑问或者有时发现窗口一闪而过那么你需要了解C、C++中变量的作用域和生存周期)
**模态**
QDialog dlg;
dlg.exec();
这种情况下,我们一般都是将对象分配上** stack** 上而不是heap上。
当然,你喜欢用 heap也没问题
Dialog * dlg = new QDialog();
dlg->exec();
delete dlg;
===== 模态对话框 =====
前面的 show() 与 exec() 并不是模态与非模态的区别。
想让一个Widget成为模态我们只需要对其设置
setAttribute(Qt::WA_ShowModal, true);
注意这是QWidget的成员函数 也就是说QWidget可以显示为模态或非模态
**setWindowModality**
除了直接调用setAttribute外QWidget 提供了一个易用的函数,来设置窗体的模态。其源码如下:
void QWidget::setWindowModality(Qt::WindowModality windowModality)
{
data->window_modality = windowModality;
// setModal_sys() will be called by setAttribute()
setAttribute(Qt::WA_ShowModal, (data->window_modality != Qt::NonModal));
setAttribute(Qt::WA_SetWindowModality, true);
}
注意:该函数的参数取值:**NonModal、WindowModal、ApplicationModal **分别对应默认情况下的
* QDialog::show()
* QDialog::open()
* QDialog::exec()
如果你没有使用QDialog::open()的需求,你可能也不需要该函数。
**setModal**
除了QWidget提供的成员QDialog 提供了 setModal 的成员函数,我们看看其代码:
void QDialog::setModal(bool modal)
{
setAttribute(Qt::WA_ShowModal, modal);
}
不用解释了吧?我们要显示模态对话框,只需要类似下面的代码:
QDialog * dlg = new QDialog();
dlg->setAttribute(Qt::WA_ShowModal, true);
dlg->show();
**exec()**
有问题是不为啥exec() 直接可以显示模态对话框呢看QDialog源代码吧
int QDialog::exec()
{
Q_D(QDialog);
...
setAttribute(Qt::WA_ShowModal, true);
...
show();
...
QEventLoop eventLoop;
(void) eventLoop.exec(QEventLoop::DialogExec);
...
}
看到答案没exec() 先设置modal属性而后调用 show() 显示对话框,最后启用事件循环
===== 事件循环 =====
Qt 程序时事件驱动的,每个程序,我们需要调用 QApplication::exec() 来启用事件循环。
int QCoreApplication::exec()
{
...
QEventLoop eventLoop;
int returnCode = eventLoop.exec();
...
return returnCode;
}
用前面的 QDialog::exec() 一样,都是调用的 QEventLoop::exec()
int QEventLoop::exec(ProcessEventsFlags flags)
{
Q_D(QEventLoop);
...
while (!d->exit)
processEvents(flags | WaitForMoreEvents | EventLoopExec);
...
return d->returnCode;
}
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
Q_D(QEventLoop);
if (!d->threadData->eventDispatcher)
return false;
if (flags & DeferredDeletion)
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
return d->threadData->eventDispatcher->processEvents(flags);
}
进一步这将调用平台相关的函数比如在windows下
bool QGuiEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
if (!QEventDispatcherWin32::processEvents(flags))
return false;
if (configRequests) // any pending configs?
qWinProcessConfigRequests();
return true;
}
事件循环和线程没有必然的联系事件循环可以用在QThread中而且从Qt4.4开始QThread的run函数默认就调用了自己的事件循环。
对与QDialog来说当它自己的QEventLoop启用时主程序的 QEventLoop 当然是处于暂停状态了。说到底,就是两个死循环,一个在内,一个在外,只有里面的退出后,外边的循环才会执行。不过由于两个循环执行的命令是基本一样的,都是调用并处理程序收到的各种事件,所以,可能变得不容易理解

View File

@@ -0,0 +1,132 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-11-15T17:22:43+08:00
====== QT 坐标转换 实现图片旋转 ======
Created Tuesday 15 November 2011
http://blog.sina.com.cn/s/blog_66e717d70100ilir.html
===== 一、利用QPixmap显示图片。 =====
1.将以前的工程文件夹进行复制备份我们这里将工程文件夹改名为painter05。以前已经说过经常备份工程目录是个很好的习惯
2.在工程文件夹的debug文件夹中新建文件夹我这里命名为images用来存放要用的图片。我这里放了一张linux.jpg的图片。如下图所示。
{{~/sync/notes/zim/Programme/Qt/漫谈QWidget及其派生类(三)/1.jpg}}
3.在Qt Creator中打开工程。即打开工程文件夹中的.pro文件如图。
{{~/sync/notes/zim/Programme/Qt/漫谈QWidget及其派生类(三)/2.jpg}}
{{~/sync/notes/zim/Programme/Qt/漫谈QWidget及其派生类(三)/3.jpg}}
4.将dialog.cpp文件中的paintEvent函数更改如下。
void Dialog::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QPixmap pix;
pix.load(“images/linux.jpg”);
painter.drawPixmap(0,0,100,100,pix);
}
这里新建QPixmap类对象并为其添加图片然后在以00点开始的宽和高都为100的矩形中显示该图片。你可以改变矩形的大小看一下效果啊。最终程序运行效果如下。
{{~/sync/notes/zim/Programme/Qt/漫谈QWidget及其派生类(三)/4.jpg}}
(说明:下面的操作都会和坐标有关,这里请先进行操作,我们在下一节将会讲解坐标系统。)
===== 二、利用更改坐标原点实现平移。 =====
Qpainter类中的__translate()函数实现坐标原点__的改变改变原点后此点将会成为__新的原点00__
例如:
void Dialog::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QPixmap pix;
pix.load(“images/linux.jpg”);
painter.drawPixmap(0,0,100,100,pix);
painter.translate(100,100); //将100100设为坐标原点
painter.drawPixmap(0,0,100,100,pix);
}
这里将100100设置为了新的坐标原点所以下面在00点贴图就相当于在以前的100100点贴图。效果如下。
{{~/sync/notes/zim/Programme/Qt/漫谈QWidget及其派生类(三)/5.jpg}}
==== 三、实现图片的缩放。 ====
我们可以使用QPixmap类中的scaled()函数来实现图片的放大和缩小。
例如:
void Dialog::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QPixmap pix;
pix.load(“images/linux.jpg”);
painter.drawPixmap(0,0,100,100,pix);
qreal width = pix.width(); //获得以前图片的宽和高
qreal height = pix.height();
pix = pix.scaled(width*2,height*2,Qt::KeepAspectRatio);
//将图片的宽和高都扩大两倍,并且在给定的矩形内保持宽高的比值
painter.drawPixmap(100,100,pix); //没有指定矩形,只是指定了绘制的起点。
}
其中参数Qt::KeepAspectRatio是图片缩放的方式。我们可以查看其帮助。将鼠标指针放到该代码上当出现F1提示时按下F1键这时就可以查看其帮助了。当然我们也可以直接在帮助里查找该代码。
{{~/sync/notes/zim/Programme/Qt/漫谈QWidget及其派生类(三)/6.jpg}}
这是个枚举变量,这里有三个值,只看其图片就可大致明 白Qt::IgnoreAspectRatio是不保持图片的长宽比Qt::KeepAspectRatio是在给定的矩形中保持**长宽比**,最后一个也 是保持长宽比,但可能**超出**给定的矩形。这里给定的矩形是由我们显示图片时给定的参数决定的,例如 painter.drawPixmap(0,0,100,100,pix);就是在以00点为起始点的宽和高都是100的矩形中。
程序运行效果如下。
{{~/sync/notes/zim/Programme/Qt/漫谈QWidget及其派生类(三)/7.jpg}}
===== 四、实现图片的旋转。 =====
旋转使用的是QPainter类的rotate()函数它默认是以__原点为中心__进行旋转的。我们要改变旋转的中心可以使用前面讲到的translate()函数完成。
例如:
void Dialog::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QPixmap pix;
pix.load(“images/linux.jpg”);
painter.translate(50,50); //让图片的中心作为旋转的中心
painter.rotate(90); //顺时针旋转90度
painter.translate(-50,-50); //使原点复原
painter.drawPixmap(0,0,100,100,pix);
}
这里必须__先改变旋转中心然后再旋转然后再将原点复原__才能达到想要的效果。
运行程序,效果如下。
{{~/sync/notes/zim/Programme/Qt/漫谈QWidget及其派生类(三)/8.jpg}}
===== 五、实现图片的扭曲。 =====
实现图片的扭曲是使用的QPainter类的shear(qreal shqreal sv)函数完成的。它有两个参数前面的参数实现横行变形后面的参数实现纵向变形。当它们的值为0时表示不扭曲。
例如:
void Dialog::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QPixmap pix;
pix.load(“images/linux.jpg”);
painter.drawPixmap(0,0,100,100,pix);
painter.shear(0.5,0); //横向扭曲
painter.drawPixmap(100,0,100,100,pix);
}
效果如下:
{{~/sync/notes/zim/Programme/Qt/漫谈QWidget及其派生类(三)/9.jpg}}
其他扭曲效果:
painter.shear(0,0.5); //纵向扭曲
{{~/sync/notes/zim/Programme/Qt/漫谈QWidget及其派生类(三)/10.jpg}}
painter.shear(0.5,0.5); //横纵扭曲
{{~/sync/notes/zim/Programme/Qt/漫谈QWidget及其派生类(三)/11.jpg}}
图片形状的变化,其实就是利用坐标系的变化来实现的。我们在下一节中将会讲解坐标系统。这一节中的几个函数,我们可以在其帮助文件中查看其详细解释。

View File

@@ -0,0 +1,126 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-11-15T10:54:31+08:00
====== QT的Graphics View框架与坐标系 ======
Created Tuesday 15 November 2011
http://www.linuxsong.org/2010/09/qt-graphics-view/
Graphics View提供了一个界面它既可以管理大数量的定制2D graphical items又可与它们交互有一个view widget可以把这些项绘制出来并支持旋转与缩放。这个框架也包含一个事件传播结构对于在scene中的这些items,它具有双精度的交互能力。 Items能处理键盘事件鼠标的按移动、释放、双击事件也可以跟踪鼠标移动。Graphics View使用BSP树来提供对item的快速查找使用这种技术它可以实时地绘制大规模场景甚至以百万items计。Graphics View在Qt 4.2中被引用它替代了它的前辈QCanvas。
===== Graphics View的体系结构 =====
Graphics View提供的是一种类似于Qt model-view的编程。多个views可以监视同一个场景而场景包含多个具有多种几何外形的items。
===== 场景 =====
QGraphicsScene 表示Graphics View中的场景它有以下职责
* 为管理大量的items提供一个快速的接口。
* 传播事件到每个item。
* 管理item的状态例如选择焦点处理。
* 提供未经变换的渲染功能,主要用于打印。
场景作为QGraphicsItem对象的容器。通过调用QgraphicsScene::addItem()把这些Items加入到场景中。可以使用众多的查找函数来获取特定的items。QGraphicsScene:items()与它的许多重载函数可获取那些与点、矩形多边形向量路径等相交或是有包含有关系的items。QGraphicsScene::itemAt()返回特定上最顶端的item。所有的item查找函数都以**出栈序列**返回(也就是说,第一个返回的是最顶端的,最后一个返回的是最底端的)。
QGraphicsScene scene;
QGraphicsRectItem *rect=scene.addRect(QRectF(0,0,100,100));
QGraphicsItem *item=scene.itemAt(50,50);
//item==rect;
QGraphicsScene的事件传播结构会把场景事件投递到items也管理多个items之间的传递。假如场景收到了鼠标在某个位置press事件场景会把这个事件投递给处在那个位置的item。QGraphicsScene也管理某种item状态像选择与焦点。你可以通过调用QGraphicsScene::setSelectionArea()来选择items它需要提供一个**任意的形状**为参数。这个函数也作为在QGraphicsView实现**橡皮筋选择**功能的一个基础。为得到这些已经被选择的items,调用QGraphicsScene::selectedItem()。另一个状态处理是是否一个item拥有键盘输入焦点。你可以调用QGraphicsScene::setFocusItem()或QGraphics::setFocus()来设定焦点也可用QGraphicsScene::focusItem()来得到当前拥有焦点的那个item。最后QGraphicsScene允许你通过调用QGraphicsScene::render()函数把部分场景送到绘图设备进行渲染。
===== 视图 =====
{{~/sync/notes/zim/Programme/QT的Graphics_View框架与坐标系/graphicsview-view.png}}
QGraphicsView提供了视图部件它可视化场景中的内容。你可以联结多个视图到同一个场景对这个相同的数据集提供几个视口。**视口部件**是一个滚动区域它提供了滚动条以对大场景进行浏览。为了使用OpenGL,你应该调用QGraphicsView::setViewport()来把一个QGLWidget设为视口。视图从键盘鼠标接收输入事件在发送这些事件到场景之前会对这些事件进行适当的翻译把事件坐标转换成对应的场景坐标
利用转换矩阵QGraphicsView::matrix(),视图可变换场景的坐标系统。这允许高级的导航特性如缩放旋转。为了方便QGraphicsView也提供了在视图与场景之间进行坐标转换的函数QGraphicsView::mapToScene(),QGraphicsView::mapForScene()。
=== Item ===
QGraphicsItem 是场景中图形items的基类。Graphics View 提供了一些标准的、用于典型形状的items。像矩形(QGraphicsRectItem),椭圆QGraphicsEllipseItem),文本 (QGraphicsTextItem),当你写定制的item时那些最有用的一些QGraphicsItem特性也是有效的。除此这 外QGraphicsItem支持以下特性
*鼠标按、移动、释放、双击事件,鼠标悬停事件,滚轮事件,弹出菜单事件。
*键盘输入焦点,键盘事件。
*拖拽
*组包括父子关系使用QGraphicsItemGroup
*碰撞检测
Items如同QGraphicsView一样位于本地坐标系它也为item与场景之间item与item之间的坐标转换提供许多工具函数。而且也像QGraphicsView一样它使用矩阵来变换它的坐标系统QGraphicsItem::matrix()。它对旋转与缩放单个的Item比较有用。
Items可以包含别的items(孩子。父items的转换被它的子孙所**继承**。然而它的所有函数也就是QGraphicsItem::contains(),QGraphicsItem::boundingRect(),QGraphicsItem::collidesWith()),**不会积累**这些转换,依然在本地坐标下工作。
QGraphicsItem通过QGraphicsItem::shape()QGraphicsItem::collideWith())来支持**碰撞检测**。这两个都是虚函数。从shape()返回你的item的形状以本地坐标QPainterPath表示QGraphicsItem会为你处理所有的碰撞检测。假如你想提供自己的碰撞检测你应该重新实现QGraphicsItem::collideWith()。
===== Graphics View 坐标系统 =====
Graphics View基于**笛卡尔坐标系**。item在场景中的位置与几何形状通过x,y坐标表示。当使用未经变形的视图来观察场景时场景中的一个单位等于屏幕上的一个 像素。在Graphics View中有三个有效的坐标系统**Item坐标系场景坐标系视图坐标系**。为了简化你的实现Graphics View提供了方便的函数允许三个坐标系之间相互映射。当渲染时Graphics View的场景坐标对应于QPainter的**逻辑坐标**,视图坐标与**设备坐标**相同。
=== Item坐标 ===
{{~/sync/notes/zim/Programme/QT的Graphics_View框架与坐标系/graphicsview-parentchild.png}}
Items位于它们**自己的坐标系中**。它的坐标都以__点(0,0)为中心点__这也是所有变换的中心点。在item坐标系中的几何**图元**经常被称为item点item线item矩形。当创建一个定制的item,item坐标是所需要考虑的。QGraphicsScene与QGraphicsView可以为你执行所有转换这使得实现定制的item变得容易。举例来说假如你收到鼠标按或是拖进入事件事件的位置以**item坐标**的形式给出。QGraphicsItem::contain()虚函数当某个点的位置在你的item范围内时返回true,否则返回false。这个点参数使用item坐标相似地item的**包围矩形与形状**也使用item坐标。
===== Item位置 =====
Item位置指的是item的**中心点**在它**父亲**(可能是item也可能是场景)的坐标系(**中心点为坐标原点**中的坐标。以这种思想来看场景指的就是那些祖先最少的item的“父亲”。最上级的Item位置就是在场景中的位置。
子坐标与父坐标之间是相关的假如孩子未经变换子坐标与父坐标之间的差值等于在父坐标系下父item与子item之间的距离。例如假如一个未经变换的 子item位置与其父item的中心重合那么这两个item的坐标系统完全相同。如果孩子的位置是100那么孩子坐标系中的(0,10)点,对 应于父坐标系中的1010点。
因为**子item的位置与变换是相对于父item的子item的坐标不会被父亲的变换影响**尽管父item的变换隐含地对子item做了变换。在上面的例子中即使父item旋转缩放子item的(0,10)点依然对应于父item的(10,10)点。然而, 相对于场景来讲子item会遵循父item的变换。假如父item被缩放(2X,2X),子item的位置在场景中的坐标是200它的 100点则与场景中的400对应 。除了QGraphicsItem::pos()QGraphicsItem的函数以Item坐标工作如一个item's包围矩形总是以item坐标 的形式给出。
=== 场景坐标 ===
场景坐标系统描述了每个**最顶级**item的位置也是从视图向场景投递场景事件的基础。场景中的每个item有**场景位置与包围矩形**QGraphicsItem::scenePos(),QGraphicsItem::sceneBoundingRect()), 另外,它有自己**本地item位置与包围矩形**。场景位置描述了item在场景坐标下的位置它的场景包围矩形则用于QGraphicsScene决定场景中哪块区域发生了变化。场景中的变化通过**QGraphicsScene::changed()**信号来通知,它的参数是场景矩形列表。
=== 视图坐标 ===
视图坐标是widget的坐 标,视图坐标中每个单位对应一个像素。这种坐标的特殊之处在于它是相对于**widget或是视口**的不会被所观察的场景所影响。QGraphicsView 的视口的左上角总是00右下角总是(视口宽,视口高)。所有的鼠标事件与拖拽事件,最初以视图坐标表示,就应该把这些坐标映射到场景坐标以便与 item交互。
=== 坐标映射 ===
经常处理场景中item时在场景与item之间item与item之间视图与场景之间进行坐标映射形状映射是非常有用的。举例来讲当你在QGraphicsView的视口中点击鼠标时你应该通过调用QGraphicsView::mapToScence()与QGraphicsScene::itemAt()来获知光标下是场景中的哪个item。假如你想获知一个item位于视口中的什么位置你应该先在item上调用QGraphicsItem::mapToScene(),然后调用QGraphicsView::mapFromScene()。最后假如你想在一个视图椭圆中有哪些items,你应该把**QPainterPath**传递到mapToScene(),然后再把映射后的路径传递到QGraphicsScene::items()。
你可以调用QGraphicsItem::mapToScene()与QGraphicsItem::mapFromScene()在item与场景之间进行坐标与形状的映射。也可以在item与其父item之间通过QGraphicsItem::**mapToParent()**与QGraphicsItem::mapFromItem()进行映射。所有映射函数可以包括点矩形多边形路径。视图与场景之间的映射也与此类似。对于从视图与item之间的映射你应该首先映射到场景然后再从场景向item进行映射。
===== 关键特性 =====
=== 缩放与旋转 ===
QGraphicsView通过QGraphicsView::setMatrix()支持同QPainter一样的仿射变换通过对一个视图应用变换你可以很容易地支持普通的导航特性如缩放与旋转。下面是一个例子
class View::public QGraphicsView
{
Q_OBJECT
//.....
public slots:
void zoomIn() {scale(1.2,1.2);}
void zoomOut() {scale(1/1.2,1/1.2);}
void rotateLeft() {rotate(-10);}
void rotateRight() {rotate(10);}
};
这些槽应与QToolButtons联接并使autoRepeat有效。当对视图变换时QGraphicsView会对视图中心进行校正。
=== 拖拽 ===
因为**QGraphicsView继承自 QWidget**,它也提供了像QWidget那样的拖拽功能另处为了方便Graphics View柜架也为场景每个item提供拖拽支持。当视图接收到拖拽事件它可翻译为QGraphicsSceneDragDropEvent,再发送到场景。场景接管这个事件把它发送到光标下接受拖拽的第一个item。
从一个item开始拖拽时创建一个**QDrag对象**,传递开始拖拽的那个 widget的指针。Items可以同时被多个视图观察但只有一个视图可以开始拖拽。拖拽在多数情况下是从按下鼠标或是移动鼠标开始的因此在 mousePressEvent()或mouseMoveEvent()中你可以从事件中得到那个原始的widget指针例如
void CustomItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QMimeData *data=new QMimeData;
data->setColor(Qt::green);
QDrag *drag=new QDrag(event->widget());
drag->setMimeData(data);
drag->start();
}
为 了在场景中载取拖拽事件你应重新实现QGraphicsScene::dragEnterEvent()和在QGraphicsItem的子类里任何与 你特定场景需要的事件处理器。items也可以通过调用QGraphicsItem::setAcceptDrops()获得**拖拽支持**,为了处理将要进行 的拖拽,你需要重新实现 QGraphicsItem::dragEnterEvent(),QGraphicsItem::dragMoveEvent(),QGraphicsItem::dragLeaveEvent() 和QGraphicsItem::dropEvent()。
=== 光标与工具提示 ===
像QWidget一样QGraphicsItem也 支持光标QgraphicsItem::setCursor)与工具提示(QGraphicsItem::setToolTip())。当光标进入到 item的区域光标与工具提示被QGraphicsView激活通过调用QGraphicsItem::contains()检测)。你也可以直接在 视图上设置一个缺省光标(QGraphicsView::setCursor)。
=== 动画 ===
Graphics View支持几种级别的动画。你可以很容易地通过把QGraphicsItemAnimatoin与你的item联结来装配出**动画路径**,这允许以时间线来控制动画在所有平台上以稳定的速率运作。QGraphicsItemAnimation允许你为item的位置旋转缩放剪切变换等产生一条路径动画可以用QSlider来控制或更为普遍使用的QTimeLine。
另一种是从QObject和QGraphicsItem继承item可以设置自己的定时器以在QObject::timeEvent()中增加步进的方式来控制动画。
第三种是通过调用QGraphicsScene::advance()来推进场景它又依次调用QGraphicsItem::advance().
=== OpenGL渲染 ===
为了使用OpenGL渲染你要设置一个新的QGLWidget作为QGraphicsView的视口QGraphicsView::setViewPort()。假如你让OpenGL提供反锯齿功能你需要OpenGL采样缓冲支持。
QGraphicsView view(&scene);
view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
=== Item组 ===
通过把一个item做为另一个item的孩子你可以得到item组的大多数本质特性这些items会一起移动所有变换
会从父到子传递。QGraphicsItem也可以为它的孩子处理所有的事件这样就允许以父亲代表它所有的孩子可以有效地把所有的items看作一个整体。
另外QGraphicsItemGroup是一个特殊的item,它既对孩子事件进行处理又有一个接口把items从一个组中增加和删除。把一个item加到
QGraphicsItemGroup仍会保留item的原始位置与变换而给一个item重新指定父item则会让item根据其新的父亲重新定位。可以用QGraphicsScene::createItemGroup()建组。

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -0,0 +1,572 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-03-07T19:16:04+08:00
====== QT编程----事件(一) ======
Created Wednesday 07 March 2012
http://blog.csdn.net/jianchi88/article/details/7017022
===== review =====
.ui 生成 .h, .cpp文件
uic form1.ui -o **form1.h**
uic form1.ui **-i form1.h -o form1.cpp**
===== QT程序设计进阶-事件 =====
Qt程序是__事件驱动__的, 程序的每个动作都是由幕后某个事件所触发.
Qt事件的类型很多, 常见的qt的事件如下:
* 键盘事件: 按键按下和松开.
* 焦点事件: 键盘焦点移动.
* 鼠标事件: 鼠标移动,鼠标按键的按下和松开.
* 进入和离开事件: 鼠标移入widget之内,或是移出.
* 拖放事件: 用鼠标进行拖放.
* 滚轮事件: 鼠标滚轮滚动.
* __绘屏事件__: 重绘屏幕的某些部分.
* 定时事件: 定时器到时.
* __移动事件__: widget的位置改变.
* __大小改变事件__: widget的大小改变.
* 显示和隐藏事件: widget显示和隐藏.
* __窗口事件__: 窗口是否为当前窗口.
还有一些非常见的qt事件,比如socket事件,剪贴板事件,字体改变,布局改变等等.
Qt 的事件和Qt中的signal不一样.__ 后者通常用来使用widget, 而前者用来实现 widget__.
比如一个按钮, 我们使用这个按钮的时候, 我们只关心他clicked()的signal, 至于这个按钮**如何接收处理鼠标事件,再发射这个信号**,我们是不用关心的. 但是如果我们要__重载一个按钮的时候,我们就要面对event了__. 比如我们可以改变它的行为,在鼠标按键按下的时候(mouse press event) 就触发clicked()的signal而不是通常在释放的( mouse release event)时候.
===== 事件起源: =====
基于**事件如何被产生与分发**,可以把事件分为三类:
1)Spontaneous 事件
2) Posted 事件
3)Sent 事件
1)Spontaneous 事件,__由窗口系统产生__它们被放到**系统队列**中,通过事件循环逐个处理。
本类事件通常是window system把从系统得到的消息,比如鼠标按键,键盘按键等, 放入**系统的消息队列中**. __Qt事件循环__的时候读取这些事件,__转化为QEvent__,再依次处理.
2)Posted 事件由Qt或是__应用程序产生__它们被**Qt组成队列**,再通过事件循环处理。
调用QApplication::postEvent()来产生一个posted类型事件.
例如QWidget::**update()**函数, 当需要**重新绘制屏幕时**,程序调用update()函数。其实现的原理是new出一个paintEvent,调用 QApplication::postEvent(),将其放入Qt的消息队列中,等待依次被处理.
3)Sent 事件由Qt或是__应用程序产生__但它们被**直接发送到目标对象**。
调用QApplication::sendEvent()函数来产生一个sent类型事件. sent 类型事件不会放入队列, 而是直接被派发和处理, QWidget::**repaint()**函数用的就是这种方式.
当我们在main()函数的末尾调用QApplication::exec()时,程序进入了__Qt的事件循环__
事件循环如下面所示:
while (!exit_was_called)
{
while(!posted_event_queue_is_empty)
{
process_next_posted_event();
}
while(!spontaneous_event_queue_is_empty)
{
process_next_spontaneous_event();
}
while(!posted_event_queue_is_empty)
{
process_next_posted_event();
}
}
===== 事件循环的处理流程: =====
1)先处理Qt事件队列中的posted事件直至为空
2)再处理系统消息队列中的spontaneous消息直至为空
3)在**处理系统消息的时候会产生新的Qt posted事件**,需要对其再次进行处理
===== Notify =====
sendEvent的事件派发不通过事件循环。调用QApplication::sendEvent的时候, __消息会立即被处理,是同步的__. 实际上QApplication::sendEvent()是通过调用QApplication::notify(), 直接进入了**事件的派发和处理环节**.所有的事件都最终通过 notify 派发到相应的对象中。
bool QApplication::notify ( QObject * receiver, QEvent * event )
它是通过调用__receiver->event__(event) 来实现的。目标接受对象的event方法会**自动接受**notify传来的event事件
event() 会返回一个布尔值来__告诉调用者事件是否被accept或ignore,__ (true表示accept)从event()返回的布尔值却是用来与QApplication:notify()通讯的。
event()函数的处理如下所示:
bool QWidget::event(QEvent *event)
{
switch (e->type()) {
case QEvent::KeyPress:
keyPressEvent((QKeyEvent *)event);
if (!((QKeyEvent *)event)->isAccepted())
return false;
break;
case QEvent::KeyRelease:
keyReleaseEvent((QKeyEvent *)event);
if (!((QKeyEvent *)event)->isAccepted())
return false;
break;
...
}
return true;
}
Close事件有点不同调用QCloseEvent:ignore()取消了关闭操作而accept()告诉Qt继续执行正常的关闭操作。为了避免混乱最好是在closeEvent()的新实现中明确地进行accept()与ignore()的调用:、
void MainWindow::closeEvent(QCloseEvent *event)
{
if (userReallyWantsToQuit()) {
event->accept();
} else {
event->ignore();
}
}
===== 例子keyPressEvent =====
==== 1. 在空白窗体页面重载当前窗体类的keyPressEvent方法实现按键事件的响应。 ====
步骤一:
添加头文件<qevent.h>
在form.cpp中填加void Form1::keyPressEventQKeyEvent *k
并实现**根据不同的键值,执行不同的动作**。
步骤二:
添加头文件<qevent.h>
在form.h 中为窗体类form1添加** void ** keyPressEventQKeyEvent *k )声明;
步骤三:
重新编译工程并运行测试。
void Form1::keyPressEvent( QKeyEvent *k )
{
if(k->key() == Key_Left)
{
qDebug("Left\n");
....
}
else if(k->key() == Key_Right)
{
qDebug("Right\n");
...
}
else QWidget::keyPressEvent(k);
}
==== 2. 在具备子控件的复杂窗体中重载当前窗体类的keyPressEvent方法实现按键事件的响应。 ====
步骤一:
添加头文件<qevent.h>
在form.cpp中填加void Form1::keyPressEventQKeyEvent *k
并实现根据不同的键值,执行不同的动作。
步骤二:
添加头文件<qevent.h>
在form.h 中为窗体类form1添加 void keyPressEventQKeyEvent *k )声明;
步骤三:
在form.cpp中**消除子控件的焦点策略使能方向及Tab按键功能**。
步骤四:
重新编译工程并运行测试。
例如:
pushButton1 = new QPushButton( this, "pushButton1" );
pushButton1->setGeometry( __QRect(__ 200, 150, 111, 41 ) );
pushButton1->**setFocusPolicy**(QWidget::NoFocus);
void QWidget::setFocusPolicy ( FocusPolicy )
设置这个窗口部件接收键盘焦点的方式。
“focusPolicy”属性保存的是__窗口部件接收键盘焦点的策略__。
* 如果窗口部件通过tab来接收键盘焦点这个策略就是QWidget::TabFocus
* 如果窗口部件通过点击来接收键盘焦点这个策略就是QWidget::ClickFocus
* 如果窗口部件上述两种方式__都使用__是QWidget::StrongFocus
* 如果它不接收焦点QWidget的**默认值**是QWidget::NoFocus。
==== 3. 重载当前窗体类的event方法实现针对性事件的处理与过滤效果。 ====
步骤一:
在form.cpp中填加bool Form1::__event__QEvent *event
并实现根据不同的键值,执行不同的动作。
步骤二:
在form.h 中为窗体类form1添加 __bool __ eventQEvent *event声明
#include<QKeyEvent>
步骤三:
重新编译工程并运行测试。
bool Form1::event(QEvent * event)
{
if (__event->type() __== __QEvent::KeyPress__)
{
__QKeyEvent__ *keyEvent = (QKeyEvent *) event;
if (keyEvent->key() == Key_A)
{
qDebug("--cut the Key_A--\n");
return true;
}
}
return QWidget::event(event);
}
实验:
1用鼠标事件实现鼠标放在按钮上按钮变大。
2用按键事件实现方向右和方向左键控制2个窗口
3用信号与槽机制实现鼠标点击next和back实现控制2个窗口
main.cpp:
#include <qapplication.h>
#include "form1.h"
int main( int argc, char ** argv )
{
QApplication a( argc, argv );
Form1 w;
w.show();
a.connect( &a, SIGNAL( lastWindowClosed() ), &a, SLOT( quit() ) );
return a.exec();
}
form1.cpp
#include "form1.h"
#include "form2.h"
#include <qvariant.h>
#include <qpushbutton.h>
#include <qlayout.h>
#include <qtooltip.h>
#include <qwhatsthis.h>
#include <qimage.h>
#include <qpixmap.h>
/*
* Constructs a Form1 as a child of 'parent', with the
* name 'name' and widget flags set to 'f'.
*
* The dialog will by default be modeless, unless you set 'modal' to
* TRUE to construct a modal dialog.
*/
Form1::Form1( QWidget* parent, const char* name, bool modal, WFlags fl )
: QDialog( parent, name, modal, fl )
{
if ( !name )
setName( "Form1" );
setMouseTracking (true);
pushButton1_3_2 = new QPushButton( this, "pushButton1_3_2" );
pushButton1_3_2->setGeometry( QRect( 210, 80, 51, 41 ) );
pushButton1_3_2->setFocusPolicy(QWidget::NoFocus);
pushButton1_4_2 = new QPushButton( this, "pushButton1_4_2" );
pushButton1_4_2->setGeometry( QRect( 140, 80, 51, 41 ) );
pushButton1_4_2->setFocusPolicy(QWidget::NoFocus);
pushButton1 = new QPushButton( this, "pushButton1" );
pushButton1->setGeometry( QRect( 70, 20, 51, 41 ) );
pushButton1->setFocusPolicy(QWidget::NoFocus);
pushButton1_3 = new QPushButton( this, "pushButton1_3" );
pushButton1_3->setGeometry( QRect( 210, 20, 51, 41 ) );
pushButton1_3->setFocusPolicy(QWidget::NoFocus);
pushButton1_5 = new QPushButton( this, "pushButton1_5" );
pushButton1_5->setGeometry( QRect( 70, 80, 51, 41 ) );
pushButton1_5->setFocusPolicy(QWidget::NoFocus);
pushButton1_4 = new QPushButton( this, "pushButton1_4" );
pushButton1_4->setGeometry( QRect( 140, 20, 51, 41 ) );
pushButton1_4->setFocusPolicy(QWidget::NoFocus);
pushButton1_2 = new QPushButton( this, "pushButton1_2" );
pushButton1_2->setGeometry( QRect( 280, 20, 51, 41 ) );
pushButton1_2->setFocusPolicy(QWidget::NoFocus);
pushButton1_2_2 = new QPushButton( this, "pushButton1_2_2" );
pushButton1_2_2->setGeometry( QRect( 280, 80, 51, 41 ) );
pushButton1_2_2->setFocusPolicy(QWidget::NoFocus);
pushButton9 = new QPushButton( this, "pushButton9" );
pushButton9->setGeometry( QRect( 340, 160, 70, 30 ) );
pushButton9->setFocusPolicy(QWidget::NoFocus);
__ languageChange(); __
resize( QSize(434, 204).expandedTo(minimumSizeHint()) );
clearWState( WState_Polished );
// signals and slots connections
connect( pushButton9, SIGNAL( clicked() ), this, SLOT( next() ) );
}
/*
* Destroys the object and frees any allocated resources
*/
Form1::~Form1()
{
// no need to delete child widgets, Qt does it all for us
}
/*
* Sets the strings of the subwidgets using the current
* language.
*/
void Form1::languageChange()
{
setCaption( tr( "Form1" ) );
pushButton1_3_2->setText( tr( "7" ) );
pushButton1_4_2->setText( tr( "6" ) );
pushButton1->setText( tr( "1" ) );
pushButton1_3->setText( tr( "3" ) );
pushButton1_5->setText( tr( "5" ) );
pushButton1_4->setText( tr( "2" ) );
pushButton1_2->setText( tr( "4" ) );
pushButton1_2_2->setText( tr( "8" ) );
pushButton9->setText( tr( "next" ) );
}
void Form1::next()
{
Form2 a;
a.show();
close();
a.exec();
}
void Form1::press_next()
{
Form2 a;
a.show();
close();
a.exec();
}
void Form1::keyPressEvent ( QKeyEvent * e )
{
if(e->key()==Key_Right)
press_next();
else
QWidget::keyPressEvent (e);
}
void Form1:: mouseMoveEvent ( __QMouseEvent__ * e )
{
if(e->x() < 260 && e->x() > 210 && e->y() > 80 && e->y() < 120)
pushButton1_3_2-> resize(61,51);
else if(e->x() < 120 && e->x() > 70 && e->y() > 20 && e->y() < 60)
pushButton1->resize(61,51);
else if(e->x() < 260 && e->x() > 210 && e->y() > 20 && e->y() < 60)
pushButton1_3->resize(61,51);
else
{
pushButton1_3_2->resize(51,41);
pushButton1->resize(51,41);
pushButton1_3->resize(51,41);
QWidget::mouseMoveEvent(e);
}
}
form2.cpp
#include "form2.h"
#include "form1.h"
#include <qvariant.h>
#include <qpushbutton.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qtooltip.h>
#include <qwhatsthis.h>
#include <qimage.h>
#include <qpixmap.h>
/*
* Constructs a Form2 as a child of 'parent', with the
* name 'name' and widget flags set to 'f'.
*
* The dialog will by default be modeless, unless you set 'modal' to
* TRUE to construct a modal dialog.
*/
Form2::Form2( QWidget* parent, const char* name, bool modal, WFlags fl )
: QDialog( parent, name, modal, fl )
{
if ( !name )
setName( "Form2" );
pushButton19 = new QPushButton( this, "pushButton19" );
pushButton19->setGeometry( QRect( 20, 160, 71, 31 ) );
textLabel1 = new QLabel( this, "textLabel1" );
textLabel1->setGeometry( QRect( 130, 40, 171, 81 ) );
languageChange();
resize( QSize(435, 204).expandedTo(minimumSizeHint()) );
clearWState( WState_Polished );
// signals and slots connections
connect( pushButton19, SIGNAL( clicked() ), this, SLOT( back() ) );
}
/*
* Destroys the object and frees any allocated resources
*/
Form2::~Form2()
{
// no need to delete child widgets, Qt does it all for us
}
/*
* Sets the strings of the subwidgets using the current
* language.
*/
void Form2::languageChange()
{
setCaption( tr( "Form2" ) );
pushButton19->setText( tr( "back" ) );
pushButton19->setFocusPolicy(QWidget::NoFocus);
textLabel1->setText( tr( "<h1>hello world</h1>" ) );
}
void Form2::back()
{
Form1 a;
a.show();
close();
a.exec();
}
;
void Form2::press_back()
{
Form1 a;
a.show();
close();
a.exec();
}
void Form2::keyPressEvent ( QKeyEvent * e )
{
if(e->key()==Key_Left)
press_back();
else
QWidget::keyPressEvent (e);

View File

@@ -0,0 +1,248 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-03-07T20:32:53+08:00
====== QT编程----事件(二) ======
Created Wednesday 07 March 2012
http://blog.csdn.net/jianchi88/article/details/7025819
===== eventFilter 事件过滤器 =====
Qt事件模型一个真正强大的特色是__一个QObject 的实例能够管理另一个QObject 实例的事件__。
一个CustomerDialog的小部件。CustomerDialog 包含一系列QLineEdit. 现在,我们想**用空格键来代替Tab**使焦点在这些QLineEdit间切换。
一个解决的方法是__子类化QLineEdit__重新实现__keyPressEvent()__并在keyPressEvent()里调用**focusNextChild()**。像下面这样:
void MyLineEdit::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Space) {
focusNextChild();
} else {
**QLineEdit::keyPressEvent**(event);
}
}
上述做法有一个缺点。如果CustomerDialog里有很多不同的控件比如QComboBox,QEdit,QSpinBox我们就必须子类化这么多控件。这是一个烦琐的任务。
一个更好的解决办法是: 让CustomerDialog去__管理他的子部件的按键事件__实现要求的行为。我们可以__使用事件过滤器__。
一个事件过滤器的安装需要下面2个步骤
1 调用installEventFilter__注册需要管理的对象__。
2在eventFilter() 里处理需__要管理的对象的事件__。
一般推荐在CustomerDialog的**构造函数中**注册被管理的对象。像下面这样:
CustomerInfoDialog::CustomerInfoDialog(QWidget *parent) : QDialog(parent)
{ ...
firstNameEdit->installEventFilter(this);
lastNameEdit->installEventFilter(this);
cityEdit->installEventFilter(this);
phoneNumberEdit->installEventFilter(this);
}
一旦事件管理器被注册发送到firstNameEditlastNameEditcityEditphoneNumberEdit的事件将__首先发送到eventFilter()__。
下面是一个 eventFilter()函数的实现:
bool CustomerInfoDialog::__eventFilter__(QObject *target, QEvent *event)
{
if (target == firstNameEdit || target == lastNameEdit
|| target == cityEdit || target == phoneNumberEdit) {
if (__event->type() __== __QEvent::KeyPress__) {
QKeyEvent *keyEvent =** static_cast<QKeyEvent *>**(event);
if (__keyEvent->key() __== Qt::Key_Space) {
focusNextChild();
return__ true; __
}
}
}
** return** QDialog::eventFilter(target, event);
}
在上面的函数中,我们首先检查目标部件是否是 firstNameEditlastNameEditcityEditphoneNumberEdit。接着我们判断事件__是否是按键事件__。如果事件是按键事件我们**把事件转换为QKeyEvent**。接着我们判断是否按下了空格键如果是我们调用focusNextChild()**把焦点传递给下一个控件**。然后返回true__通知Qt我们已经处理了该事件。__
**如果返回false的话**__Qt继续将该事件发送给目标控件__**结果是一个空格被插入到QLineEdit中。 **
如果目标控件不是 QLineEdit,或者按键不是空格键我们将把事件传递给__基类的eventFilter()函数__。
===== Qt提供5个级别的事件处理和过滤 =====
1重新实现__事件函数__。 比如: mousePressEvent(), keyPressEvent(), paintEvent() 。 这是最常规的事件处理方法。
2重新实现QObject::__event()__. 这一般用在Qt没有提供该事件的处理函数时。也就是我们**增加新的事件时**。
3安装事件过滤器
4在 QApplication 上安装事件过滤器。 QApplication 上的事件过滤器将__捕获应用程序的所有事件__而且第一个获得该事件。也就是说事件在发送给其它任何一个event filter之前发送给QApplication的event filter。
5重新实现QApplication 的 __notify()__方法.
**Qt使用 notify()来分发事件**。要想在任何事件处理器捕获事件之前捕获事件唯一的方法就是重新实现QApplication 的 notify()方法。
在创建了过滤器之后,下面要做的是**安装这个过滤器**。安装过滤器需要调用installEventFilter()函数。这个函数的签名如下:
 void QObject::installEventFilter ( QObject * filterObj )
这个函数是QObject的一个函数因此可以安装到任何QObject的子类并不仅仅是UI组件。这个函数接收一个QObject对象**调用了这个函数安装事件过滤器的组件会调用filterObj定义的eventFilter()函数**。
例如textField->installEventFilter(obj)则__如果有事件发送到textField组件时会先调用obj->eventFilter()函数然后才会调用textField->event()。__
也可以把事件过滤器安装到QApplication上面这样就可以__过滤所有的事件__已获得更大的控制权。不过这样做的后果就是会降低事件分发的效率。
如果一个组件安装了多个过滤器则__最后一个安装的会最先调用__类似于堆栈的行为。
pushButton2 = new QPushButton( this, "pushButton2" );
pushButton2->setGeometry( QRect( 200, 160, 111, 31 ) );
pushButton2->installEventFilter( this );
bool Form1::__eventFilter__( QObject *o, QEvent *e )
{
if( pushButton2==o )
{
if ( e->__type()__ == QEvent::KeyPress )
{
QKeyEvent *k = (QKeyEvent *)e;
qDebug( "eat key press %d", **k->key()** );
return __TRUE;__
}
if ( e->type() == QEvent::MouseButtonPress )
{
QMouseEvent *k = (QMouseEvent *)e;
qDebug( "eat Mouse press " );
return TRUE;
}
else {
return FALSE;
}
}
else
return__ QWidget::eventFilter__( o, e );
}
bool Form1::event(QEvent * event)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = (QKeyEvent *) event;
if (keyEvent->key() == Key_A)
{
qDebug("--cut the Key_A--\n");
return true;
}
}
return __QWidget::event(event)__;
}
===== 事件的产生 =====
QT应用程序可以产生自定义的事件或是__预定义类型或是自定义类型__。 这可以通过创建QEvent类或它的**子类的实例**并且调用QApplication:postEvent()或QApplication::sendEvent()来实现。
这两个函数需要一个 QObject* 与一个QEvent * 作为参数假如你调用postEvent(),你必须用 new 操作符来创建__事件对象__Qt会它被处理后帮你删除它。
假如你用sendEvent(), 你应该在栈上来创建事件。
下面举两个例子:
一是posting 事件:
QApplication::postEvent(mainWin, new QKeyEvent(QEvent::KeyPress,Key_X,'X',0,"X"));
二是sending 事件:
QKeyEvent event(QEvent::KeyPress, Key_X, 'X', 0,"X");
QApplication::sendEvent(mainWin, &event);
Qt应用程序**很少直接调用**postEvent()或是sendEvnet()因为大多数事件会在必要时__被Qt或是窗口系统自动产生__。在大多数的情况下**当你想发送一个事件时Qt已经为你准备好了一个更高级的函数来为你服务**。例如update()与repaint())。
为了提高qt程序的自定义特性可以显式得采用程序实现事件的发送。
===== 重绘事件 paintEvent() =====
当窗口被其他窗口覆盖后再次重新显示时系统将产生__ spontaneous 事件__来请求重绘事件循环最终从事件队列中捡选这个事件并把它__分发到那个需要重画的widget__。
当我们调用 QWidget::update() 时产生的是__ Posted 重绘事件__ .
当我们调用 QWidget::repaint() 时,产生的是 Sent 重绘事件 .
posting 相对于sending的一个优势是它给了**Qt一个压缩(compress)事件的机会**。假如你在一个widget上连续地调用update() 十次因update()而产生的这十个事件将会自动地被合并为一个单独的事件但是QPaintEvents事件附带的区域信息也合并了。
可压缩的事件类型包括:**paint,move,resize,layout hint,language change。**
最后要注意可以在任何时候调用QApplication::sendPostedEvent()强制Qt产生一个对象的posted事件。
Qt 系统还提供了一个
===== QCustomEvent 类 =====
,用于用户自定义事件,这些自定义事件可以利用 QThread::postEvent() 或者QApplication::postEvent() 被发给各种控件或其他 QObject 实例。
QWidget 类的子类可以通过 QWidget::customEvent() 事件处理函数方便地接收到这些自定义的事件。
需要注意的是QCustomEvent 对象在创建时都带有一个__类型标识 id __以定义事件类型为了避免与 Qt 系统定义的事件类型冲突,该 id 值应该**大于枚举类型 QEvent::Type **中给出的 "User" 值。
演示如何post一个定制事件的代码片段
const QEvent::Type MyEvent = (QEvent::Type)1234;
...
QApplication::postEvent(mainwin, new **QCustomEvent(MyEvent)**);
事件必须是QCustomEvent类型(或子类)的。
构造函数的参数是__事件的类型__1000以下被Qt保留。其他可被程序使用**。为处理定制事件类型要重新实现customEvent()函数**
void MyWin::customEvent(QCustomEvent *event)
{
if (event->type() == MyEvent) {
myEvent();
} else {
Qwidget::customEvent(event);
}
}
QcustomEvent类有一个void *的成员可用于特定的目的。你也可以子类化QCustomEvent加上别的成员。
===== 一些事件类型可以被传递 =====
。这意味着假如**目标对象不处理一个事件**Qt会试着寻找另外的事件接收者。用新的目标来调用QApplication::notify()。
举例来讲key事件是传递的假如拥有焦点的Widget不处理特定键Qt会分发相同的事件给父widget,然后是父亲的父亲直到最顶层widget。
可被传递的事件**可以“接收”或是“忽略”这个事件**。假如事件被处理,这个事件将不会再被传递。否则Qt会试着查找另外的事件接收者。
大部分qt对象对事件的__处理缺省情况下是“接收”__在QWidget中的__缺省实现是调用“忽略”__,假如你希望接收事件,你需要做的是**重新实现事件handler**避免调用QWidget的实现。假如你想“忽略”事件只需简单地传递它到QWidget的实现。
下面的代码演示了这一点:
void MyWidget::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Key_Escape) {
doEscape();
} else {
QWidget::keyPressEvent(event);
}
}
在上面的例子里,假如用户按了"ESC"键我们会调用doEscape()并且事件被“**接收”**了这是缺省的情况事件不会被传递到父widget,假如用户按了别的键则调用QWidget的缺省实现。
void QWidget::keyPressEvent(QKeyEvent *event)
{
event->ignore();
}
此处调用ignore(),事件会被传递到**父widget**中去。
以上假设基类都是QWidget,然而同样的规则也可以应用到别的层次中只要用其他基类代替QWidget即可。
举例来说:
void MyLineEdit::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Key_SysReq) {
doSystemRequest();
} else {
QLineEdit::keyPressEvent(event);
}
}

View File

@@ -0,0 +1,137 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T21:00:46+08:00
====== Qt 之 show,hide,setVisible,setHidden,close 等小结 ======
Created Monday 31 October 2011
不知道标题该怎么取了,文中就简单整理一下下面几个函数吧。因为不断有网友问到此类问题(包括相关问题),所以,自己整理一下,也算学习小结了。
这些函数分两类,一类是用来删除对象的(从内存中干掉),一类是用来隐藏窗口的(从界面上干掉)
0 QObject::deleteLater() delete obj;析构对象
1 QWidget::setVisible(bool) 使得Widget可见或不可见
2 QWidget::setHidden(bool) 1号的马甲
3 QWidget::show() 1号的马甲
4 QWidget::hide() 1号的马甲
5 QWidget::close(bool) 视情况确定是否调用4号0号
6 QDialog::done(int) 始终会调用4号视情况调用0号
7 QDialog::accept() 6号的马甲
8 QDialog::reject() 6号的马甲
写在前面,我们知道,
* 分配到heap中的对象(通过 new)当对其delete才会被析构。
* 分配在stack中的对象当其离开作用域是被析构
在 Qt 中,一般建议你使用 new 的方式创建对象。或者,你可以看看 从 Qt 的 delete 说开来
注意:本文接下来的讨论,都是假定你用的 new 创建的对象。
===== QObject =====
从Qt类的祖宗开始吧。因为QObject__不涉及界面__啊成员中只涉及析构问题。在从 Qt 的 delete 说开来 一文中,我们知道,
obj->deleteLater();
最后调用的就是:
delete obj;
很简单哈。所以,这个函数的谈论(略)
另外:对象析构时会发射 destroyed 信号。注意,是对象析构时,这是句废话,但要记住。
===== QWidget =====
呵呵show()、hide()、setVisible()、setHidden() 这4个函数让人看得眼花缭乱。怎么办
看看代码吧:
virtual void setVisible(bool visible);
inline void setHidden(bool hidden) { setVisible(!hidden); }
inline void show() { setVisible(true); }
inline void hide() { setVisible(false); }
代码很清楚:这四个东西之中,只有 setVisible 是__独立__的其他三个都是它的马甲
setVisible 的作用是什么呢顾名思义使得一个Widget可见或不可见。
__要点__不可见是Widget不在界面上显示但不代表对象被析构
**close **这个函数Manual中给的其实很详细的很透彻。但一开始想弄明白还真是不容易。
首先我们直接调用close时(或者点击__关闭按钮__调用),它会生成** QCloseEvent **事件我们可以选择接受或阻止它如果我们阻止事件close将直接返回什么都不做
看点源码:
bool QWidgetPrivate::close_helper(CloseMode mode)
{
...
QCloseEvent e;
if (mode == CloseWithSpontaneousEvent)
QApplication::sendSpontaneousEvent(q, &e);
else
QApplication::sendEvent(q, &e);
if (!that.isNull() && !e.isAccepted()) {
data.is_closing = 0;
return false;
}
...
}
其次:如果我们接受了事件(默认),她就直接调用我们前面提到的朋友。(从界面上干掉)
q->hide();
再次:如果我们为它设置过标记位(关闭时删除它):又看到老朋友不是?(从内存中干掉)
if (q->testAttribute(Qt::WA_DeleteOnClose)) {
q->setAttribute(Qt::WA_DeleteOnClose, false);
q->deleteLater();
}
题外close 还是其他代码,但与本主题无关,不再涉及。
===== QDialog =====
QDialog 和 QWidget 相比,多了 done、reject 和 accept 3个相关函数
先看看两个马甲:
void QDialog::accept()
{
done(Accepted);
}
void QDialog::reject()
{
done(Rejected);
}
**done() **done 做的事情和close比较类似因为它调用了close所调用的 close_helper 函数。只不过不同于close函数它始终会先让Widget__不可见__。然后close操作最后根据参数发射信号
void QDialog::done(int r)
{
Q_D(QDialog);
hide();
setResult(r);
d->close_helper(QWidgetPrivate::CloseNoEvent);
emit finished(r);
if (r == Accepted)
emit accepted();
else if (r == Rejected)
emit rejected();
}
注意QDilaog可能包含事件循环事件循环由QDialog::exec() 开始QDialog::setVisible(false)将负责退出事件循环这儿用的是其马甲hide()。

View File

@@ -0,0 +1,7 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T12:46:22+08:00
====== Qt学习之路 ======
Created Monday 31 October 2011

View File

@@ -0,0 +1,487 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-11-04T20:12:18+08:00
====== QT 2D绘图全面解析 ======
Created Friday 04 November 2011
http://hacktao.com/2011/03/14/329
Qt4中的2D绘图部分称为__Arthur绘图系统__.它由3个类支撑整个框架QPainter,QPainterDevice和QPainterEngine.
QPainter用来执行具体的绘图相关操作 如画点,画线,填充,变换,**alpha通道**等。QPainterDevice是QPainter用来绘图的绘图设备,Qt中有几种预定义的绘图设备如QWidget,QPixamp,QPrinter等.他们都从QPaintDevice继承。QPaintEngine类提供了不同类型设备的接口QPaintEngine对程序员透明由QPainter,QPaintDevice类与其进行交互。
从Qt4.2开始,__Graphics View框架__取代了QCanvas, QGraphics View框架使用了MVC模式适合对大量2D图元的管理Grphics View框架中**场景**(scene)存储了图形数据,它通过**视图**(view)以多种表现形式,每个**图元**(item)可以单独进行控制.
===== Arthur绘图基础 =====
在Arthur绘图框架中的基本绘图元素是**画笔****画刷**。QPainter类具有GUI程序需要的绝大多数函数,能够绘制基本图形(点,线,矩形,多边形等)以及复杂的图形(如绘图路径).使用绘图路径(QPaintPath)的优点是复杂形状的图形只用生成**一次**以后再使用的时候是需要调用QPainter::drawPath()就可以了。
QPainterPath对象可以用来填充绘制轮廓。线和轮廓都可以用画笔(QPen)进行绘制,画刷(QBrush)进行**填充**。画笔定义了风格(线形),宽度,笔尖画刷以及端点是如何绘制的(cap-style),端点的连接方式(join-style). 画刷用来填充画笔绘制的图形可以定制不同的填充模式和颜色的画刷。当绘制文字时字体使用QFont类定义Qt使用指定字体的属性如果没有匹配的字体Qt将使用最接近的字体。**字体属性**可以通过QFontInfo来获取。**字体度量**(measurement)使用QFontMetrics类来获取。QFontDatabase类可以获得底层窗口系统所有可用的字体.
通常情况下QPainter以默认的坐标系统进行绘制也可以用QMatrix类对坐标进行变换。
当绘制时可以使用QPainter::RenderHint来告诉绘图引擎是否启用**反锯齿**功能使图变得平滑。
QPainter::RenderHint的可取如表6-1中的值
————————————————————————————————
QPainter::Antialiasing 告诉绘图引擎应该在可能的情况下进行**边缘**的反锯齿绘制
QPainter::TextAntialiasing 尽可能的情况下文字的反锯齿绘制
QPainter::SmoothPixmapTransform 使用平滑的pixmap变换算法(双线性插值算法),而不是近邻插值算法
-----------------------------------------------------------------------------------------
QPainter的绘图函数
drawArc() 弧
drawChord() 弦
drawConvexPolygon() 凸多边形
drawEllipse() 椭圆
drawImage() QImage表示的图像
drawLine() 线
drawLines() 多条线
drawPath() 路径
drawPicture() 按QPainter指令绘制
drawPie() 扇形
drawPixmap() QPixmap表示的图像
drawPoint() 点
drawPoints() 多个点
drawPolygon() 多边形
drawPolyline() 多折线
drawRect() 矩形
drawRects() 多个矩形
drawRoundRect() 圆角矩形
drawText() 文字
drawTiledPixmap() 平铺图像
drawLineSegments() 绘制折线
**drawPicture()**函数负责绘制QPicture中存储的**QPainter**指令QPicture是可以记录QPainter绘图指令的类.它将QPainter的绘图指令串行化为平台无关的存储格式。
下面的代码将记录的绘图指令重绘。
QPicture picture;
picture.load(“mypicture.pic”);
QPainter painter(this);
painter.drawPicture(0,0,picture);//在(0,0)处重放绘图指令也可以使用QPicture::play()完成相同的功能
===== 使用画笔 =====
画笔的属性包括线型线宽颜色等。画笔的属性可以在构造函数中指定也可以使用setStyle()setWidth(), setBrush(),setCapStyle(),setJoinStyle()等函数
逐项设定画笔的各项属性.Qt中使用Qt::PenStyle定义了6种画笔风格分别是Qt::SolidLine,Qt::DashLine,Qt::DotLine,Qt::DashDotLine,Qt::DashDotDotLine,
Qt::CustomDashLine.自定义线风格(Qt::CustomDashLine)需要使用QPen的setDashPattern()函数来设定自定义风格.
下面代码设置了一个自定义QPen
QPen pen;
QVector customDashes;
qreal blank=4;
dashes<<2< pen.setDashPattern(customDashes);
** 端点风格(cap style)**
端点风格决定了线的端点样式它只对线宽大于1的线有效。Qt种定义了三种端点风格用枚举类型Qt::PenCapStyle表示,分别为Qt::SqureCap,QT::FlatCap,Qt::RoundCap,
** 连接风格(Join style)**
连接风格是两条线如何连接连接风格对线宽大于等于1的线有效。Qt定义了四种连接方式用枚举类型Qt::PenStyle表示.分别是Qt::MiterJoin,Qt::BevelJoin,Qt::RoundJoin. Qt::SvgMiterJoin.
===== 2.画刷 =====
在Qt中图形使用QBrush进行填充画刷包括**填充颜色**和**风格(填充模式)**.在Qt中颜色使用QColor类表示QColor支持RGB,HSV,CMYK颜色模型。QColor还支持alpha混合的轮廓和填充。基本模式填充包括有各种点线组合的模式。Qt支持RGB,HSV,和CMYK颜色模型。RGB是面向硬件的模型。颜色由红绿蓝三种基色混合而成。HSV模型比较符合人对颜色的感觉由色调(0-359),饱和度(0-255),亮度(0-255)组成.CMYK由青洋红黑四种基色组成。主要用于打印机等硬件拷贝设备上。每个颜色分量的取值是0-255.另外QColor还可以用 SVG1.0中定义的任何颜色名为参数初始化.
Qt4提供了**渐变填充**的画刷,渐变填充包括两个要素,**颜色的变化和路径的变化**。颜色变化可以指定从一种颜色渐变到另外一种颜色。也可以在变化的路径上指定一些点的颜色进行**分段渐变**。
Qt4中提供了三种渐变填充线性(QLinearGradient),圆形(QRadialGradient)和圆锥渐变(QConicalGradient).所有的类都从QGradient类继承.
** 线性渐变填充**
线性渐变填充指定两个控制点画刷在两个控制点之间进行颜色插值。通过创建QLinearGradient对象来设置画刷.
QLinearGradient linearGradient(0,0,200,100);
linearGradient.setColorAt(0,Qt::red);
linearGradient.setColorAt(0.5,Qt::green);
linearGradient.setColorAt(1,Qt::blue);
painter.setBrush**(linearGradient)**;
painter.drawRect(0,0,200,100);
在QGradient构造函数中指定线行填充的两点分别为(0,0),(100,100).setColorAt()函数在0-1之间设置指定位置的颜
** 圆形渐变填充**
圆形渐变填充需要指定圆心半径和焦点。画刷在焦点和圆上的所有点之间进行颜色插值。创建QRadialGradient对象设置画刷
QRadialGradient radialGradient(50,50,50,30,30);
radialGradient.setColorAt(0.2,Qt::cyan);
radialGradient.setColorAt(0.8,Qt::yellow);
radialGradient.setColorAt(1,Qt::magenta);
painter.setBrush(radialGradient);
painter.drawEllipse(0,0,100,100);
——————————-
** 圆锥渐变填充**
圆锥渐变填充指定圆心和开始角画刷沿圆心逆时针对颜色进行插值创建QConicalGradient对象并设置画刷.
QConicalGradient conicalGradient(60,40,30);
conicalGradient.setColorAt(0,Qt::gray);
conicalGradient.setColorAt(0.4,Qt::darkGreen);
conicalGradient.setColorAt(0.6,Qt::darkMagenta);
conicalGradient.setColorAt(1,Qt::drakBlue);
painter.setBrush(conicalGradient);
painter.drawEllipse(0,0,100,100);
———————————
为了实现**自定义填充**还可以使用QPixmap或者QImage对象进行__纹理填充__。两种图像分别使用**setTexture()**和**setTextureImage()**函数加载纹理.
======================================================================================================================
===== 双缓冲绘图 =====
在Qt4中所有的窗口部件**默认都使用双缓冲**进行绘图。使用双缓冲可以减轻绘制的闪烁感。在有些情况下用户要关闭双缓冲自己管理绘图。下面的语句设置了窗口部件的Qt::WA_PaintOnScreen属性 ,就关闭了窗口部件的双缓冲.
mywidget->**setAttribute**(Qt::WA_PaintOnScreen);
由于Qt4不再提供异或笔**组合模式**QPainter::CompostionMode_Xor()并不是异或笔,Qt4只提供了QRubberBand实现矩形和直线的绘图反馈。因此要实现在绘图中动态反馈必须使用其他方法。程序中使用双环冲来解决这个问题。在绘图过程中一个缓冲区绘制临时内存一个缓冲区保存绘制好的内容最后进行合并。
在交互绘图过程中,程序将图像缓冲区复制到临时缓冲区,并在临时缓冲区上绘制,绘制完毕在将结果复制到图像缓冲区,如果没有交互复制,则直接将图像缓冲区绘制显示到屏幕上。
===== 使用alpha通道 =====
在windows,Mac OSX和有**XRender扩展**的X11系统上Qt4能够支持Alpha通道通过使用Alpha通道可以实现__半透明__效果QColor类中定义了Alpha通道的__透明度__0 表示完全透明255表示完全不透明。注意QWidget类有一个属性windowOpacity,通过setWindowOpacity(qreal level)可以设置**窗口的透明度**。但该属性和Alpha通道的原理并不相同Qt4在Windows和Mac OS X平台上才支持该属性但在X11平台上却需要Composite扩展才能工作。(alpha通道使用的是X11的xRender扩展).
===== 绘图设备 =====
QPaintDevice类是实际的绘制设备的基类.QPainter能够在QPaintDevice子类上进行绘制如**QWidget,QImage,QPixmap**,QGLWidget,QGLPixelBuffer,**QPicture**,QPrinterQSvgGenerator.
要实现自己的绘图设备必须从QPaintDevice类继承并实现其虚函数QPaintDevice::__paintEngine()__以告之QPainter能够在这个特定的设备上绘制图形同时还需要从QPaintEngine类继承自定义的图形绘制引擎。
1 QWidget
QWidget是所有用户界面元素的基类窗口部件时用户界面的原子元素他接受鼠标键盘窗口系统的其他事件并在屏幕上绘制自己。
2 QImage
QImage类提供了与硬件无关的__图像表示__它为直接**操作像素**提供优化QImage支持单色8-bit,32-bit和alpha混合图像使用QImage的优点在于可以获得平台无关的绘制操作另外还有一个好处时图像可以不必在GUI线程中处理。
3 QPixmap
QPixmap时**后台显示**的图像它为在屏幕上显示图像提供优化不同于QImage,pixmap的图像数据用户不可见而且由底层窗口系统管理为了优化QPixmap图像Qt提供了QPixmapCache类来存储临时的pixmap.Qt还提供了QPixmap的继承类QBitmap类QBitmap表示单色的pixmap,主要用来创建自定义的QCursor和QBrush对象构造QRegion对象设置pixmap和窗口部件的掩码。
4 OPenGLWidget
Qt提供了QtOpenGL模块来实现OpenGL操作QGLWidget允许使用OpenGL API进行绘制。同时QGLWidget时QWidget的子类因此QPainter也可以在上面绘制。这样可以使Qt能够利用OpenGl完成绘制操作如变换和绘制pixmap
5 pixel Buffer
QGLPixelBuffer从QPaintDevice继承封装了OpenGL pbuffer.使用pbuffer绘制通常时全硬件加速这比使用QPixmap绘制更为迅速。
6 FrameBuffer
QGLFrameBufferObject从QPaintDevice继承QGLFrameBufferObject封装了OpenGL frameBuffer对象FrameBuffer对戏那个用来实现后台屏幕绘制比pixel buffer更好一些。
7 picture
QPicture类时能够记录和重演QPainter命令的绘图设备picture串行化painter的命令为平台无关的格式QPicture同时也于分辨率无关如QPicuture能够在不同的设备上(svg,pdf,ps 打印机和屏幕)有一只的显示。QPicture::load()和QPicture::save()函数分别完成载入和存储图像。
8 Printer
QPrinter 类时在打印机上绘制的绘图设备在Windows和MAC OS X上QPrinter使用内建的打印机驱动程序在X11上QPrinter山城postscript代码并发送给lpr,lp或者其他打印程 序QPrinter可以在任意其他QPrintEngine对象上打印也可以直接生成PDF文件。
QPrintEngine类定义了QPrinter如何和其他打印机系统交互的接口主要创建自己的打印引擎时可以从QPaintEngine和QPaintEngine上继承。
======================================================================================================
===== 坐标系统与坐标变换 =====
1. Qt坐标系统由QPainter控制同时也由 QPaintDevice和QPaintEngine类控制.QPaintDevice类是绘图设备的基 类QWidget,QPixmap,QImage,和QPrinter都是QPaintDevice类的子类。Qt绘图设备默认坐标原点是**左上角**X轴 向右增长Y轴向下增长默认的单位在基于像素的设备上是像素在打印机设备上是1/72英寸(0.35毫米).QPainter的**逻辑坐标**与 QPainterDevice的**物理坐标**之间的映射由QPainter的**变换矩阵,视口和窗口处理**。逻辑坐标和物理坐标也是一直的。QPainter也支持坐标变换(如旋转和伸缩);
2. 坐标变换。
通常QPainer在设备的坐标系统上绘制图形但QPainter也支持坐标变换。可以通过QPainter::scale()函数进行**比例变换**。使用 QPainter::rotate()函数进行**旋转变换**。**平移变换**则使用QPainter::translate()函 数QPainter::shear()函数对图形进行**扭曲操作**,所有变换操作的**变换矩阵**都可以通过QPainter::wordMatrix()函数取出。不同的变换矩阵可以使用**堆栈**保存。
用QPainter::save()__保存变换矩阵__到堆栈用QPainter::restore()函数将其弹出堆栈。
QMatrix定义了系统的二维变换。QMatrix对象实际上定义了一个3×3矩阵。
————–
m11 m12 0
m21 m22 0
dx dy 1
—————
x\\=m11*x+m21*y+dx;
y\\=m22*y+m12*x+dy;
其中dx,dy表示水平和垂直偏移量m11,m22表示水平和垂直方向上的比例。m12和m21表示水平和垂直方向上的扭曲程度。
矩阵可以通过setMatrix函数进行设置然后可以使用translate(),rotate(),scale(),shear()等函数进行变 换.Qt4.3中引入QTransform类表示变换矩阵。与QMatrix不同的是QTransform()支持透视变换。使用toAffine() 函数可以将QTransform对象转换为QMatrix对象。这将丢失QTransform的透视变换数据。逻辑坐标和物理坐标的变换由 QPainter的worldMatrix()函数。以及QPainter的viewport()和window()函数处理。视口表示物理坐标下的任意 矩形。而在窗口表示在逻辑坐标下的相同矩形。默认情况下逻辑坐标与物理坐标时相同的。与绘图设备上的矩形也是一致的。使用窗口-视口变换可以使逻辑坐标符 合自定义要求,这个机制通常用来完成设备无关的绘图代码。例如,可以设置逻辑坐标(-100,-100)到(100,100)且在原点(0,0),通过调 用QPainter::setWindow()函数可以完成下列操作。
QPainter painter(this);
painter.setWindow(QRect(-100,-100,200,200));
现 在,逻辑坐标的(-100,-100)对应着绘图设备的(0,0),这样可以绘制独立于设备,始终在指定逻辑坐标上工作。设置窗口或视口矩形实际上是执行 线性变换。本质上是窗口四个角映射到对应的视口四个角反之亦然因此保持视口和窗口x轴和y轴之间的比例变换一致保证变换没有变形。窗口视口变换只 是线性变换,不执行裁剪操作,例如当绘制超出窗口后,这些绘制仍然 通过线性变换映射到
视口进行绘制。Qt的绘制过程是进行坐标变换在进行窗口视口变换。
==================================================================================================================
使用不同的字体
Qt提供了Font类来表示字体当创建QFont对象时Qt会使用指定的字体如果没有对应的字体Qt将寻找一种最接近的已安装字体。字体信息可以通过
QFontInfo 取出并可用QFontMetrics取得字体的相关数据。函数exactMatch()判断底层窗口系统中是否有完全对应的字体。使用 QApplication::setFont()可以设置应用程序默认的字体如果选择的字体不包括所有要显示的字符QFont将会尝试寻找最基接近的 字体。当QPainter绘制指定的字体中不存在的字符时
将绘制一个空心的正方行。
绘图路径 QPainterPath
绘图路径(painter path)由基本图元(矩形,椭圆,直线,曲线)组成绘图路径可以是闭合的路径如矩形和圆或者是非闭合的路径如直线和曲线。绘图路径在Qt中使用QPainterPth类表示
它提供了绘图操作的容器可以使图形能够复用。绘图路径可以进行填充显示轮廓和裁剪。要生成可填充的轮廓的绘图路径可以使用QPainterPathStroker类.使用QPainterPath的优点是复杂的
图形只需创建一次就可以多次使用。QPainterPath对象可以时只有起点的空路径或者从其他QPainterPath对象复制创建了QPainterPath对象后可以使用lineTo(),cubicTo(),
quadTo() 函数将直线和曲线添加到路径中来直线和曲线从currentPosition()开始绘制。currentPosition()总是返回最后的子路经绘 制的终点。使用moveTo()函数可以在不增加路径的情况下移动currentPositon(),它关闭了一个子路经,开始一个新的子路经。 closeSubPath()也可以关闭当前路径并从currentPosition()连接一条直线到绘图路径的起点。QPainter可以使用 addEllipse(),addPath(),addRect(),addRegion(),addText()将Qt的一些基本图元加入绘图路径。一 个已有的绘图路径可以通过connectPath()函数加入到另一个绘图路径中。
如下代码绘制了一个箭头:
QPainterPath path;
path.moveTo(10,100);
path.cubicTo(10,100,100,10,200,70);
path.lineTo(200,50);
path.lineTo(220,80);
path.lineTo(200,110);
path.lineTo(200,90);
path.cuticTo(200,100,100,50,50,100);
QPainter painter(this);
QPen pen(QColor(255,0,0),2);
painter.setPen(pen);
painter.drawPath(path);
Qt提供了两种填充方式Qt::OddEventFill和 Qt::WindingFill.Qt::OddEvent时默认的填充规则它指定QPainterPath使用奇偶填充规则该规则判断一个点是否在 论经图形内的方法是从该店画一条水平线到路径外计算水平线和路径的交点数如果交点时奇数个则说明该点在路径图形内。QPainterPath还有一些 函数可以获取路径信息如elementAt()函数可以取出指定的子路经元素,
isEmpty()函数判断当前路径是否为空。 controlPointRect()函数返回路径中所有的点和控制点的矩形该函数运行速度比返回精确包容框boundingRect()函数快得多。 contains()函数判断一个点或一个矩形是否在路径内。intersects()判断指定的矩形与路径是否相交.QPainterPath可以将矩 形图形转换为其他图形如使用toFillPolygon,toFillPolygon(),toSubpathPOlygons()函数将路径转化 为多边形。
QPainterPath还可以使用文字作为路径下面的代码演示了文字路径并使用线性渐变填充。
QLinearGradient linearGrad(QPointF(200,0),QPointF(1000,0));
linearGrad.setColorAt(0,Qt::black);
linearGrad.setColorAt(1,Qt::white);
QFont font(“隶书”,80);
font.setBold(true);
QPainterPath textPath();
textPath.addText(200,300,font,tr(“电子工业出版社”));
painter.setBrush(linearGrad);
painter.drawPath(textPath);
===========================================================================
QImage和QPixmap绘图设备
Qt 提供了4个处理图像的类。QImage,QPixmap,QBitmap,QPicure.他们有着各自的特点。QImage优化了I/O操作可以直接 存取操作像素数据。QPixmap主要用来在屏幕上显示图像。QBitmap从QPixmap继承只能表示两种颜色QPicture是可以记录和重放 QPrinter命令的类。QImage提供了与硬件无关的图像表示方法。通过QImage可以直接存取像素数据QImage也可以用作绘图设备。
QImage 支持的图像颜色可以是单色8位32位和alpha混合的格式。因为QImage从QPainterDevice继承所以QPainter可以直接在 QImage上绘图。除了绘制文字格式外(QFont依赖于底层的GUI).其他的绘制操作可以在任意线程中完成,如果要在其他线程中绘制文字,可以使用 QPainterPath。QImage对象具有隐式共享作为传值参数可以使用数据流及进行比较等特性。
读入图像可以通过QImage构造函 数load(),loadFromData()几种方法完成。还可以通过QImage的静态函数fromData()由指定数据构造一个QImage对 象。既可以从文件系统装入也可以从Qt应用程序的嵌入式资源中读取使用save()可以保存QImage对象。可以通过 QImageReader::supportedImageFormats()和 QImageWriter::supportedImageFormats()获取QImage支持的所有文件格式列表。
——————————————
QImage函数
————————————————————————————————————————————————–
几何函数 size(),widt(),dotsPermeterX(),dotsPerMeterY()函数获取图像大小和比例信息。
rect()函数返回图像的包容矩形valid()测试给定的坐标是否在此矩形内。offset()获取图像和其他图像之间的相对偏移量。setOffset()函数设置偏移量。
颜色函数 某个像素的颜色可以通过pixel函数获取返回值是QRgb类型对于单色和256色图像colorTable()返回调色板,numColors返回调色板中的条目数.用pixelIndex()
函数获取像素的颜色索引然后使用color()函数取出实际的颜色值.hasAlphaChannel()函数返回图像是否使用了alpha通道。allGray(),isGrayscale()测试图像是否为灰度图像。
文字 text()函数返回图像附属的文字textKeys()返回文字的键值表。setText()函数改变图像附属文字.
低级信息 depth()函数获取图像颜色位数.支持1,8,32位.format().bytesPerLine()和numBytes()函数返回图像的数据存储信息.serieralNumber()函数取得唯一标识QImage对象的数字.
————————————————————————————————————————————————–
QImage的8位和单色图像采用颜色索引表的方式存取32为的图像则直接存储ARGB值.因此他们的像素操作函数也不相同对32位的图像setPixel()函数可以改变指定像素的QRgb颜色值对8位和
单 色图像setPixel()改变在预定义颜色表中的索引值如果要改变颜色表可以使用setColor()函数。QImage提供 scanLine()函数返回指定行的数据。bits()函数返回第一个像素的指针。每个像素在QImage中都使用整数形式表示。单色图像使用一位的索 引指向只有两种颜色的调色板有两种类型的单色图像big endia(MSB),little endian(LSB).256色图像使用8位颜色调色板调色板的数据类型是QVector,QRgb实际上时无符号整数型存储ARGB 的格式是0xAARRGGBB.32位的图像直接存储有三种类型的存储格式RGB,ARGB和已预乘的ARGB。在已预乘ARGB中红绿蓝三色已经 和alpha相乘并模除255.allGray()和isGrayscale()函数可以判断一个彩色图像能否安全转化为灰度图像。图像的格式用 format()函数读取出convertToFormat()可以进行图像格式转化QImage支持的存储格式如下:
QImage::Format_Mono 单色图像(MSB)
QImage::Format_MonoLSB 单色图像(LSB)
QImage::Format_Indexed8 使用颜色表的256色图像
QImage::Format_RGB32 不支持Alpha通道的32位图像
QImage::FOrmat_ARGB32 含Alpha通道的32位图像
QImage::Format_ARGB32_Premultiplied 已预乘的含Alpha通道的32位图像.
———————————–
QPixmap
QPixmap 主要完成屏幕后台(off-screen)缓冲区绘图。QPixmap对象可以使用QLabel或QAbstractButton子类 (QPushButton,QToolButton)显示,QLabel通过设置pixmap属性QAbstractButton通过设置icon属性 来完成除了使用构造函数初始化QPixmap对象还可以使用静态函数grabWidget()和grabWindow()函数创建,并绘制指定的窗口 和窗口部件.QPixmap中的像素数据时内部的并且由底层的窗口系统进行管理如果要存取像素只有通过QPrinter函数将QPixmap对戏那 个转换为QImage对象根据底层系统的不同QPixmap可以RGB32或者混合alpha格式存储如果图像有Alpha通道且底层系统允许则 优先使用混合alpha格式因此QPixmap时依赖于底层系统的在X11上和Mac上QPixmap存储在服务器端,QImage存储在客户点 在windows上这两个类表达方式时相同的。QImage和QPixmap可以相互转换通常QImage载入图像并进行直接操作然后转换为 QPixmap在屏幕上显示。如果不需要操作像素就直接使用QPixmap.在windows上QPixmap还可以与HBITMAP之间相互转 换QPixmap和QImage一样使用隐式共享也能够使用数据流。
=======================================================================================================
组合模式绘图
组 合模式(Composition Mode)用来指定如何合并源图像和一个图像最常见的是SourceOver(通常也叫alpha混合),当原像素和目标像素以这种方式混合时,源图像 的alpha通道定义了像素的透明度。组合模式绘图只支持Format_ARGB32_Premultiplied和Format_ARGB32格格式 而且应该优先使用Format_ARGB32_Premultiplied格式设置了组合模式后它对所有的绘图操作都有效如画笔画刷渐变效果和 pixmap/image绘制。QPainter::CompositeMode枚举类型中前12中组合类型是T.Porter和T.Duff于1984 年在沦为(Compositing Digital Image)中阐明的12种混合规则(Porter-Duff规则)混合的计算方法在此给出。以便理解混合的过程。
首先定义混合的因子 :
As: 原像素的alpha分量
Cs: 原像素种计算好(premultiplied)色彩分量
Ad: 目标像素的alpha分量
Cd; 目标像素计算好的色彩分量
Fs: 原像素在输出结果种占有的比例
Fd: 目标像素在输出结果种占有的比例
Ar: 输出结果种的Alpha分量
Cr: 输出结果种计算好的色彩分量
Porter和Duff定义了选择混合因子Fs和Fd产生不同的视觉效果的12种规则最终结果种的Alpha值和色彩值由下面的公式决定
Fs=f(Ad);
Fd=f(As);
Ar=AsxFs+AdxFd
Cr=CsxFs+CdxFd
每种类型的Fs和Fd取值如表所示
——————————————————————————————
常 量 Fs Fd 说明
QPainter::CompositionMode_SourceOver 1 1-As 默认模式源alpha和目标像素混合
QPainter::CompositionMode_DestinationOver 1-Ad 1 和SourceOver相反目标Alpha和源像素混合
QPainter::CompositionMode_Clear N/A N/A 清除目标像素
QPainter::CompositionMode_Source N/A N/A 输出源像素
QPainter::CompositionMode_SourceIn Ad 0 在目标部分的源替代目标
QPainter::CompositionMode_DestinationI 0 As 于SourceIn相反
QPainter::CompositionMode_SourceOut 1-Ad 0 在目标之外的源替代目标
QPainter::CompositionMode_DestinationOut 0 1-As 于SourceOut相反
Qpainter::CompositionMode_SourceAtop Ad 1-As 在目标部分的源和目标组合
QPainter::CompositionMode_DestinationAtop 1-Ad As 与sourceatop相反
QPainter::CompositionMode_Xor 1-Ad 1-As 在目标之外的源和源之外的目标混合
——————————————————————————————————–
注意上面的说明并没有完全概括各种混合的含义要准确理解他们可以看公式并进行实践畜类上面12种Porter_Duff规则外Qt还支持12种扩展混合模式。下面给出计算公式需要注意如果结果中alpha值和色彩值超过0-255的范围数值将会被截断
1 QPainter::CompositionMode_Plus 源和目标相加该操作实现动画中两幅图像的溶解和过度过程。Cr=Cs+Cd Ar=As+Ad
2 QPainter::CompositionMode_Multiply 源和目标进行正片叠底(multiply)操作。结果的颜色至少是源和目标种较暗的颜色。任何颜色和黑色作该操作产生黑色。任何颜色和白色作
该操作将不会改变。Cr=CsxCd+Csx(1-Ad)+Cdx(1-As) Ar=AsxAd+Asx(1-Ad)+Adx(1-As)=As+Ad-AsxAd
3 QPainter::CompositionMode_Screen 源和目标互补然后相乘结果的颜色至少是源和目标种较亮的颜色。任何颜色和黑色进行滤色操作不会改变,任何颜色和白色进行滤色操
作还是白色
4 QPainter::CompositionMode_Overlay 根据目标颜色值不同,进行相乘操作或滤色操作,源色彩保持亮度和阴影覆盖在目标上。目标颜色和源颜色混合以反应目标的亮度。
5 QPainter::CompositionMode_Darken 选择源和目标种较暗的颜色
6 QPainter::CompositionMode_Lighten 选择源和目标种较亮的颜色
7 QPainter::CompositoinMode_ColorDodge 加亮目标颜色以反应源颜色,绘制黑色将没有效果
8 QPainter::CompositionMode_ColorBurn 使目标颜色变暗以反应源颜色,绘制白色没有效果。
9 QPainter::CompositionMode_HardLight 根据源的颜色决定是正片叠底还是滤色操作。如果源颜色高于0.5目标颜色将变亮。即进行滤色操作。如果源颜色亮度值低于0.5,目标将
会变暗相当于进行了正片叠底操作。如果源亮度值等于0.5目标不会改变变亮或者变暗成都取决于源颜色和0.5的差,绘制纯黑色和纯白
色结果还是纯黑或纯白。
10 QPainter::CompositionMode_SoftLight 根据源的颜色,决定进行变暗(darken)操作还是变亮(lighten)操作。如果源颜色比0.5亮,目标将变亮,即进行了滤色操作。如果源颜色
比0.5暗,目标将变暗,相当于进行了颜色加深(burn)操作,如果等于0.5.目标不会发生改变。变亮或者变暗的程度取决于源颜色和0.5的差
值。
11 QPainter::CompositionMode_Difference 源和目标种较暗的颜色减去较亮的颜色,绘制导致白色反转成目标颜色黑色没 化
12 Qpainter::CompositionMode_Exclusion 和上一条规则的效果类似,但对比对较低一些,绘制白色将导致反转成目标颜色,绘制黑色没有

View File

@@ -0,0 +1,24 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-11-04T22:36:52+08:00
====== Qt坐标系统与图形绘制 ======
Created Friday 04 November 2011
http://www.dzsc.com/data/html/2009-6-29/77236.html
为了能够使用Qt开发图形绘制软件首先应该掌握Qt的坐标系统。在Qt中通过QPainter类来控制它的坐标系统。连同QPaintDevice类与QPaintEngine类QPainter类构成了Qt绘图系统的基础。其中QPainter用于执行绘图的操作QPaintDevice是对**一块二维空间的抽象**在这块空间上我们可以使用QPainter进行图形的绘制QPaintEngine提供了在不同的设备上进行绘图的接口。
QPaintDevice类是能够进行绘图的对象的基类QWidgetQPixmapQPictureQImage以及QPrinter类继承了QPaintEngine类的绘图能力。绘图设备的缺省坐标系统是以**左上角**作为原点x坐标向右递增y坐标向下递增。单位对于不同的设备是不同的在基于像素的设备上以一个像素作为缺省单位而在打印机上以1/72英寸作为缺省单位。
在编写图形绘制软件时,我们需要进**行逻辑坐标与物理坐标**之间的转换这部分功能由QPainter的转换矩阵视口以及窗口来实现。缺省情况下逻辑坐标与物理坐标是一致的。QPainter也支持平移与旋转这样的坐标变换。
图元的尺寸宽度与高度总是对应于它的数学模型并且会__忽略它所使用的笔的宽度__。下图是通过两个点来实现矩形和直线的例子用到的代码为
{{./1.jpg}}
  QRect1276 QLine2761
在绘图的时候我们使用QPainter::Antialiasing的渲染暗示来控制像素渲染。计算机绘图会出现走样的情况如下图所示在绘制直线的时候出现了边缘不规则的情况。
{{./2.jpg}}
如果我们设置了QPainter的反走样渲染暗示像素将会对称的出现在点的两侧。因此可以使用以下代码来解决绘制直线出现锯齿的问题.  这样绘制的直线如下图所示:
{{./3.jpg}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,25 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T12:47:04+08:00
====== Qt学习之路(1):前言 ======
Created Monday 31 October 2011
http://devbean.blog.51cto.com/448512/193918
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/193918
Qt是一个著名的C++库——或许并不能说这只是一个GUI库因为Qt十分庞大并不仅仅是GUI。使用Qt在一定程序上你获得的是一个“一站式”的服务不再需要研究STL不再需要C++的<string>因为Qt有它自己的QString等等。或许这样说很偏激但Qt确实是一个“伟大的C++库”。
我们所使用的Qt确切地说也就是它的GUI编程部分。C++的GUI编程同Java不同GUI并不是C++标准的一部分。所以如果使用Java那么你最好的选择就是AWT/Swing或者也可以使SWT/JFace但是C++的GUI编程给了你更多的选择wxWidget, gtk++以及Qt。这几个库我都有接触但是接触都不是很多只能靠一些资料和自己的一点粗浅的认识说一下它们之间的区别(PS: 更详尽的比较在前面的文章中有)。
首先说wxWidget这是一个标准的C++库和Qt一样庞大。它的语法看上去和MFC类似有大量的宏。据说一个MFC程序员可以很容易的转换到wxWidget上面来。wxWidget有一个很大的优点就是它的界面都是原生风格的。这是其他的库所不能做到的。wxWidget的运行效率很高据说在Windows平台上比起微软自家的MFC也不相上下。
gtk++其实是一个C库不过由于C++和C之间的关系这点并没有很大的关系。但是gtk++是一个使用C语言很优雅的实现了面向对象程序设计的范例。不过这也同样带来了一个问题——它的里面带有大量的类型转换的宏来模拟多态并且它的函数名“又臭又长(不过这点我倒是觉得无所谓,因为它的函数名虽然很长,但是同样很清晰)”使用下划线分割单词看上去和Linux如出一辙。由于它是C语言实现因此它的运行效率当然不在话下。gtk++并不是模拟的原生界面,而有它自己的风格,所以有时候就会和操作系统的界面显得格格不入。
再来看Qt和wxWidget一样它也是一个标准的C++库。但是它的语法很类似于Java的Swing十分清晰而且SIGNAL/SLOT机制使得程序看起来很明白——这也是我首先选择Qt的一个很重要的方面因为我是学Java出身的 :) 。不过所谓“成也萧何败也萧何”这种机制虽然很清楚但是它所带来的后果是你需要使用Qt的qmake对程序进行预处理才能够再使用make或者nmake进行编译。并且它的界面也不是原生风格的尽管Qt使用style机制十分巧妙的模拟了本地界面。另外值得一提的是Qt不仅仅运行在桌面环境中Qt已经被Nokia收购它现在已经会成为Symbian系列的主要界面技术——Qt是能够运行于嵌入式平台的。
以往人们对Qt的授权多有诟病。因为Qt的商业版本价格不菲开源版本使用的是GPL协议。但是现在Qt的开源协议已经变成LGPL。这意味着你可以将Qt作为一个库连接到一个闭源软件里面。可以说现在的Qt协议的争议已经不存在了——因为wxWidgets或者gtk+同样使用的是类似的协议发布的。
在本系列文章中我们将使用Qt4进行C++ GUI的开发。我是参照着《C++ GUI Programming with Qt4》一书进行学习的。其实我也只是初学Qt4在这里将这个学习笔记记下来希望能够方便更多的朋友学习Qt4。我是一个Java程序员感觉Qt4的一些命名规范以及约束同Java有异曲同工之妙因而从Java迁移到Qt4似乎困难不大。不过这也主要是因为Qt4良好的设计等等。
闲话少说,还是尽快开始下面的学习吧!

View File

@@ -0,0 +1,26 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T15:46:00+08:00
====== Qt学习之路(10)Meta-Object系统 ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/199472
前面说过Qt使用的是自己的预编译器它提供了对C++的一种扩展。利用Qt的信号槽机制就可以把彼此独立的模块相互连接起来不需要实现知道模块的任何细节。
为了达到这个目的Qt提出了一个Meta-Object系统。它提供了两个关键的作用信号槽和内省。
面向对象程序设计里面会讲到Smalltalk语言有一个元类系统。所谓元类就是这里所说的Meta-Class。如果写过HTML会知道HTML标签里面也有一个<meta>这是用于说明页面的某些属性的。同样Qt的Meta-Object系统也是类似的作用。内省又称为反射允许程序在运行时获得类的相关信息也就是meta-information。什么是meta-information呢举例来说像这个类叫什么名字它有什么属性有什么方法它的信号列表它的槽列表等等这些信息就是这个类的meta-information也就是“元信息”。这个机制还提供了对国际化的支持是QSA(Qt Script for Application)的基础。
标准C++并没有Qt的meta-information所需要的动态meta-information。所以Qt提供了一个独立的工具moc通过定义Q_OBJECT宏实现到标准C++函数的转变。moc使用纯C++实现的,因此可以在任何编译器中使用。
这种机制工作过程是:
首先Q_OBJECT宏声明了一些QObject子类必须实现的内省的函数如metaObject()tr(),qt_metacall()等;
第二Qt的moc工具实现Q_OBJECT宏声明的函数和所有信号
第三QObject成员函数connect()和disconnect()使用这些内省函数实现信号槽的连接。
以上这些过程是qmakemoc和QObject自动处理的你不需要去考虑它们。如果实现好奇的话可以通过查看QMetaObject的文档和moc的源代码来一睹芳容。

View File

@@ -0,0 +1,29 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T15:46:48+08:00
====== Qt学习之路(11)MainWindow ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/203313
尽管Qt提供了很方便的快速开发工具QtDesigner用来拖放界面元素但是现在我并不打算去介绍这个工具原因之一在于我们的学习大体上是依靠手工编写代码过早的接触设计工具并不能让我们对Qt的概念突飞猛进……
前面说过本教程很大程度上依照的是《C++ GUI Programming with Qt4, 2nd Edition》这本书。但是这本书中接下来的部分用了很大的篇幅完成了一个简单的类似Excel的程序。虽然最终效果看起来很不错但我并不打算完全依照这个程序来因为这个程序太大以至于我们在开始之后会有很大的篇幅接触不到能够运行的东西这无疑会严重打击学习的积极性——至少我是如此看不到做的东西很难受——所以我打算重新组织一下这个程序请大家按照我的思路试试看吧
闲话少说,下面开始新的篇章!
就像Swing的顶层窗口一般都是JFrame一样Qt的GUI程序也有一个常用的__顶层窗口__叫做__MainWindow__。好了现在我们新建一个Gui Application项目MyApp注意在后面选择的时候选择Base Class是QMainWindow。
{{./1.png}}
然后确定即可。此时QtCreator已经为我们生成了必要的代码我们只需点击一下Run看看运行出来的结果。
{{./2.png}}
一个很简单的窗口,什么都没有,这就是我们的主窗口了。
MainWindow继承自QMainWindow。QMainWindow窗口分成几个主要的区域
{{./3.png}}
最上面是Window Title用于显示标题和控制按钮比如最大化、最小化和关闭等下面一些是Menu Bar用于显示菜单再下面一点事Toolbar areas用于显示工具条注意Qt的主窗口支持__多个工具条显示__因此这里是ares你可以把几个工具条并排显示在这里就像Word2003一样工具条下面是Dock window areas这是__停靠窗口__的显示区域所谓停靠窗口就是像Photoshop的工具箱一样可以在主窗口的四周显示再向下是Status Bar就是状态栏中间最大的Central widget就是主要的__工作区__了。
好了今天的内容不多我们以后的工作就是要对这个MainWindow进行修改以满足我们的各种需要。

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,109 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T15:50:43+08:00
====== Qt学习之路(12)菜单和工具条 ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/205034
在前面的QMainWindow的基础之上我们开始着手建造我们的应用程序。虽然现在已经有一个框架但是确切地说我们还一行代码没有写呢下面的工作就不那么简单了在这一节里面我们要为我们的框架添加菜单和工具条。
就像Swing里面的Action一样Qt里面也有一个类似的类叫做__QAction__。顾名思义QAction类保存有关于这个动作也就是action的信息比如它的**文本描述、图标、快捷键、回调函数**(也就是信号槽)等等。神奇的是QAction能够根据添加的__位置__来改变自己的样子——如果添加到菜单中就会显示成一个菜单项如果添加到工具条就会显示成一个按钮。这也是为什么要把菜单和按钮放在一节里面。下面开始学习
首先,我想添加一个**打开命令**。那么,就在**头文件**里面添加一个私有的QAction变量
class QAction;
//...
private:
QAction *openAction;
//...
注意不要忘记QAction类的前向声明哦要不就会报错的
然后我们要在cpp文件中添加QAction的定义。为了简单起见我们直接把它定义在构造函数里面
openAction = new QAction(tr("&Open"), this);
openAction->setShortcut(QKeySequence::Open);
openAction->setStatusTip(tr("Open a file."));
第一行代码创建一个QAction对象。QAction有几个__重载的构造函数__我们使用的是
QAction(const QString &text, QObject* parent);
这一个。它有两个参数第一个text是这个动作的文本描述用来显示文本信息比如在菜单中的文本第二个是parent一般而言我们通常传入this指针就可以了。我们不需要去关心这个parent参数具体是什么它的作用是指明这个QAction的父组件当这个父组件被销毁时比如delete或者由__系统自动销毁__与其相关联的这个QAction也会自动被销毁。
如果你还是不明白构造函数的参数是什么意思或者说想要更加详细的了解QAction这个类那么就需要自己翻阅一下它的API文档。前面说过有关API的使用方法这里不再赘述。这也是学习Qt的一种方法因为Qt是一个很大的库我们不可能面面俱到因此只为说道用到的东西至于你自己想要实现的功能就需要自己去查文档了。
第二句我们使用了setShortcut函数。shortcut是这个动作的快捷键。Qt的QKeySequence已经为我们定义了很多__内置的快捷键__比如我们使用的Open。你可以通过查阅API文档获得所有的快捷键列表或者是在QtCreator中输入::后会有系统的自动补全功能显示出来。这个与我们自己定义的有什么区别呢简单来说我们完全可以自己定义一个__tr("Ctrl+O")__来实现快捷键。原因在于这是Qt跨平台性的体现。比如PC键盘和Mac键盘是不一样的一些键在PC键盘上有而Max键盘上可能并不存在或者反之所以推荐使用QKeySequence类来添加快捷键这样它会根据平台的不同来定义不同的快捷键。
第三句是setStatusTip函数。这是添加__状态栏的提示语句__。状态栏就是主窗口最下面的一条。现在我们的程序还没有添加状态栏因此你是看不到有什么作用的。
下面要做的是把这个QAction添加到菜单和工具条
QMenu *file = menuBar()->addMenu(tr("&File"));
file->addAction(openAction);
QToolBar *toolBar = addToolBar(tr("&File"));
toolBar->addAction(openAction);
QMainWindow有一个menuBar()函数,会**返回菜单栏**,也就是最上面的那一条。如果不存在会自动创建,如果已经存在就返回那个菜单栏的指针。直接使用返回值**添加一个菜单**也就是addMenu参数是一个QString也就是显示的菜单名字。然后使用这个QMenu指针添加这个QAction。类似的使用addToolBar函数的返回值添加了一个工具条并且把这个QAction添加到了上面。
好了主要的代码已经写完了。不过如果你只修改这些的话是编译不过的哦因为像menuBar()函数返回一个QMenuBar指针但是你并没有include它的头文件哦虽然没有明着写出QMenuBar这个类但是实际上你已经用到了它的addMenu函数了所以还是要注意的
下面给出来全部的代码:
1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QtGui/QMainWindow>
class QAction;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
QAction *openAction;
};
#endif // MAINWINDOW_H
2. mainwindow.cpp
#include <QtGui/QAction>
#include <QtGui/**QMenu**>
#include <QtGui/**QMenuBar**>
#include <QtGui/QKeySequence>
#include <QtGui/**QToolBar**>
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
openAction = new QAction(tr("&Open"), this);
openAction->setShortcut(QKeySequence::Open);
openAction->setStatusTip(tr("Open a file."));
QMenu *file = menuBar()->addMenu(tr("&File"));
file->addAction(openAction);
QToolBar *toolBar = addToolBar(tr("&File"));
toolBar->addAction(openAction);
}
MainWindow::~MainWindow()
{
}
main.cpp没有修改这里就不给出了。下面是运行结果
{{./1.png}}
很丑是吧不过我们已经添加上了菜单和工具条了哦按一下键盘上的Alt+F因为这是我们给它定义的快捷键。虽然目前挺难看不过以后就会变得漂亮的想想看Linux的KDE桌面可是Qt实现的呢

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,90 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T15:58:01+08:00
====== Qt学习之路(13)菜单和工具条(续) ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/205958
前面一节我们已经把QAction添加到菜单和工具条上面。现在我们要添加一些__图片美化__一下然后把__信号槽__加上这样我们的action就可以相应啦
首先来添加图标。QAction的图标会显示在菜单项的前面以及工具条按钮上面显示。
为了添加图标我们首先要使用Qt的资源文件。在QtCreator的__项目__上右击选择New File...然后选择resource file。
{{./1.png}}
然后点击next选择好位置Finish即可。为了使用方便我就把这个文件建在根目录下建议应该在仔细规划好文件之后建在专门的rsources文件夹下。完成之后生成的是一个__.qrc__文件qrc其实是Qt Recource Collection的缩写。它只是一个普通的XML文件可以用记事本等打开。不过这里我们不去深究它的结构完全利用QtCreator操作这个文件
{{./2.png}}
点击Add按钮首先选择Add prefix然后把生成的/new/prefix改成/。这是__prefix__就是以后使用图标时需要提供的前缀以/开头。添加过prefix之后然后在工程文件中添加一个图标再选择Add file选择那个图标。这样完成之后保存qrc文件即可。
说明一下QToolBar的图标大小默认是32*32菜单默认是16*16。如果提供的图标小于要求的尺寸则不做操作Qt不会为你放大图片反之如果提供的图标文件大于相应的尺寸要求比如是64*64Qt会__自动缩小__尺寸。
{{./3.png}}
图片的路径怎么看呢可以看出Qt的资源文件视图使用树状结构根是/,叶子节点就是图片位置,连接在一起就是路径。比如这张图片的路径就是**/Open.png**。
注意为了简单起见我们没有把图标放在专门的文件夹中。正式的项目中应该__单独有一个resources文__件夹放资源文件的。
然后回到前面的mainwindow.cpp在构造函数中修改代码
openAction = new QAction(tr("&Open"), this);
openAction->setShortcut(QKeySequence::Open);
openAction->setStatusTip(tr("Open a file."));
openAction->**setIcon**(QIcon(":/Open.png")); // Add code.
我们使用setIcon添加图标。添加的类是QIcon构造函数需要一个参数是一个字符串。由于我们要使用qrc中定义的图片所以字符串**以 : 开始**后面跟着prefix因为我们先前定义的prefix是/,所以就需要一个/然后后面是file的路径。这是在前面的qrc中定义的打开qrc看看那张图片的路径即可。
好了,图片添加完成,然后点击运行,看看效果吧!
{{./4.png}}
我们只需要修改QAction菜单和工具条就已经为我们做好了相应的处理还是很方便的
下一步为QAction添加__事件响应__。还记得Qt的事件响应机制是基于信号槽吗点击QAction会发出__triggered()__信号所以我们要做的是声明一个slot然后connect这个信号。
mainwindow.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
**void open(); **
private:
**QAction *openAction;**
};
因为我们的open()目前只要在类的内部使用因此定义成private slots即可。然后修改cpp文件
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
openAction = new QAction(tr("&Open"), this);
openAction->setShortcut(QKeySequence::Open);
openAction->setStatusTip(tr("Open a file."));
openAction->setIcon(QIcon(":/Open.png"));
** connect**(openAction, SIGNAL(triggered()), this, SLOT**(open()**));
QMenu *file = menuBar()->addMenu(tr("&File"));
file->addAction(openAction);
QToolBar *toolBar = addToolBar(tr("&File"));
toolBar->addAction(openAction);
}
void MainWindow::open()
{
__QMessageBox::information__(NULL, tr("Open"), tr("Open a file"));
}
注意我们在open()函数中简单的弹出一个标准对话框,并没有其他的操作。编译后运行,看看效果:
{{./5.png}}
好了关于QAction的动作也已经添加完毕了
至此QAction有关的问题先告一段落。最后说一下如果你还不知道怎么添加子菜单的话看一下QMenu的API里面会有一个addMenu函数。也就是说创建一个QMenu然后添加就可以的啦

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,50 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T16:06:51+08:00
====== Qt学习之路(14)状态栏 ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/210947
有段时间没有写过博客了。假期去上海旅游,所以一直没有能够上网。现在又来到这里,开始新的篇章吧!
今天的内容主要还是继续完善前面的那个程序。我们要为我们的程序加上一个状态栏。
状态栏位于主窗口的最下方,提供一个显示**工具提示**等信息的地方。一般地当窗口不是最大化的时候状态栏的右下角会有一个可以调节大小的__控制点__当窗口最大化的时候这个控制点会自动消失。Qt提供了一个__QStatusBar类__来实现状态栏。
Qt具有一个相当成熟的GUI框架的实现——这一点感觉比Swing要强一些——Qt似乎对GUI的开发做了很多设计比如QMainWindow类里面就有一个statusBar()函数用于实现状态栏的调用。类似menuBar()函数如果不存在状态栏该函数会自动创建一个如果已经创建则会返回这个状态栏的指针。如果你要替换掉已经存在的状态栏需要使用QMainWindow的__setStatusBar()__函数。
在Qt里面状态栏显示的信息有三种类型**临时信息、一般信息和永久信息**。其中临时信息指临时显示的信息比如QAction的提示等也可以设置自己的临时信息比如程序启动之后显示Ready一段时间后自动消失——这个功能可以使用QStatusBar的__showMessage()__函数来实现一般信息可以用来显示页码之类的永久信息是不会消失的信息比如可以在状态栏提示用户Caps Lock键被按下之类。
QStatusBar继承自QWidget因此它可以__添加其他的QWidget__。下面我们在QStatusBar上添加一个QLabel。
首先在class的声明中添加一个私有的QLabel属性
private:
QAction *openAction;
QLabel *msgLabel;
然后在其构造函数中添加:
msgLabel = new QLabel;
msgLabel->setMinimumSize(msgLabel->sizeHint());
msgLabel->setAlignment(Qt::AlignHCenter);
statusBar()->addWidget(msgLabel);
这里第一行创建一个QLabel的对象然后设置最小大小为其本身的建议大小——注意这样设置之后这个最小大小可能是变化的——最后设置显示规则是水平居中(HCenter)。最后一行使用statusBar()函数将这个label添加到状态栏。编译运行将鼠标移动到工具条或者菜单的QAction上状态栏就会有相应的提示
{{./1.png}}
看起来是不是很方便?只是,我们很快发现一个问题:当没有任何提示时,状态栏会有一个短短的竖线:
{{./2.png}}
这是什么呢其实这是__QLabel的边框__。当没有内容显示时QLabel只显示出自己的一个边框。但是很多情况下我们并不希望有这条竖线于是我们对statusBar()进行如下设置:
statusBar()->setStyleSheet(QString("QStatusBar::item{border: 0px}"));
这里先不去深究这句代码是什么意思简单来说就是把QStatusBar的子组件的border设置为0也就是没有边框。现在再编译试试吧那个短线消失了
QStatusBar右下角的大小控制点可以通过setSizeGripEnabled()函数来设置是否存在详情参见API文档。
好了现在我们的状态栏已经初步完成了。由于QStatusBar可以添加多个QWidget因此我们可以构建出很复杂的状态栏。

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,71 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T17:11:05+08:00
====== Qt学习之路(15)Qt标准对话框之QFileDialog ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/213414
《Qt学习之路》已经写到了第15篇然而现在再写下去却有点困难原因是当初并没有想到会连续的写下去因此并没有很好的计划这些内容究竟该怎样去写。虽然前面说过本教程主要线路参考《C++ Gui Programming with Qt 4, 2nd Edition》然而最近的章节由于原文是一个比较完整的项目而有所改变因此现在不知道该从何写起。
我并不打算介绍很多组件的使用因为Qt有很多组件各种组件用法众多根本不可能介绍完只能把API放在手边边用边查。所以对于很多组件我只是简单的介绍一下具体用法还请自行查找(确切地说我知道的也并不多很多时候还是要到API里面去找)。
下面还是按照我们的进度从Qt的__标准对话框__开始说起。所谓标准对话框其实就是Qt内置的一些对话框比如文件选择、颜色选择等等。今天首先介绍一下QFileDialog。
QFileDialog是Qt中用于文件打开和保存的对话框相当于Swing里面的JFileChooser。下面打开我们前面使用的工程。我们已经很有先见之明的写好了一个打开的action还记得前面的代码吗当时我们只是弹出了一个消息对话框(这也是一种标准对话框哦~)用于告知这个信号槽已经联通,现在我们要写真正的打开代码了!
修改MainWindow的open函数
void MainWindow::open()
{
QString path = __QFileDialog::getOpenFileName__(this, tr("Open Image"), ".", tr("Image Files(*.jpg *.png)"));
if(path.length() == 0) {
QMessageBox::information(NULL, tr("Path"), tr("You didn't select any files."));
} else {
__QMessageBox::information__(NULL, tr("Path"), tr("You selected ") + path);
}
}
编译之前别忘记include QFileDialog哦然后运行一下吧点击打开按钮就会弹出打开对话框然后选择文件或者直接点击取消会有相应的消息提示。
QFileDialog提供了很多__静态函数__用于获取用户选择的文件。这里我们使用的是getOpenFileName(), 也就是“获取打开文件名”你也可以查看API找到更多的函数使用。不过这个函数的参数蛮长的而且类型都是QString并不好记。考虑到这种情况Qt提供了另外的写法
QFileDialog *fileDialog = new QFileDialog(this);
fileDialog->setWindowTitle(tr("Open Image"));
fileDialog->setDirectory(".");
fileDialog->__setFilter__(tr("Image Files(*.jpg *.png)"));
if(fileDialog->exec() == __QDialog::Accepted__) {
QString path = fileDialog->selectedFiles()[0];
QMessageBox::information(NULL, tr("Path"), tr("You selected ") + path);
} else {
QMessageBox::information(NULL, tr("Path"), tr("You didn't select any files."));
}
不过这两种写法虽然功能差别不大但是弹出的对话框却并不一样。getOpenFileName()函数在Windows和MacOS X平台上提供的是本地的对话框而QFileDialog提供的始终是Qt自己绘制的对话框(还记得前面说过Qt的组件和Swing类似也是自己绘制的而不都是调用系统资源API)。
为了说明QFileDialog::getOpenFileName()函数的用法,还是先把函数签名放在这里:
QString QFileDialog::getOpenFileName (
QWidget * parent = 0,
const QString & caption = QString(),
const QString & dir = QString(),
const QString & filter = QString(),
QString * selectedFilter = 0,
Options options = 0 )
第一个参数parent用于指定父组件。注意很多Qt组件的构造函数都会有这么一个parent参数并提供一个默认值0
第二个参数caption是对话框的标题
第三个参数dir是对话框显示时默认打开的目录"." 代表程序运行目录,"/" 代表当前盘符的根目录(WindowsLinux下/就是根目录了),也可以是平台相关的,比如"C:\\"等;
第四个参数filter是对话框的__后缀名过滤器__比如我们使用"Image Files(*.jpg *.png)"就让它只能显示后缀名是jpg或者png的文件。如果需要使用多个过滤器使__用";;"分割__比如"JPEG Files(*.jpg);;PNG Files(*.png)"
第五个参数selectedFilter是默认选择的过滤器
第六个参数options是对话框的一些参数设定比如只显示文件夹等等它的取值是__enum QFileDialog::Option__每个选项可以使用 | 运算组合起来。
如果我要想选择多个文件怎么办呢Qt提供了getOpenFileNames()函数其返回值是一个QStringList。你可以把它理解成一个只能存放QString的List也就是STL中的list<string>。
好了我们已经能够选择打开文件了。保存也是类似的QFileDialog类也提供了保存对话框的函数**getSaveFileName**具体使用还是请查阅API。

View File

@@ -0,0 +1,43 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T21:19:06+08:00
====== Qt学习之路(16)Qt标准对话框之QColorDialog ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/214164
继续来说Qt的标准对话框这次说说QColorDialog。这是Qt提供的颜色选择对话框。
使用QColorDialog也很简单Qt提供了getColor()函数类似于QFileDialog的getOpenFileName()可以直接获得选择的颜色。我们还是使用前面的QAction来测试下这个函数
QColor color = QColorDialog::getColor(Qt::white, this);
QString msg = QString("r: %1, g: %2, b: %3").arg(QString::number(color.red()), QString::number(color.green()), QString::number(color.blue()));
QMessageBox::information(NULL, "Selected color", msg);
不要忘记include QColorDialog哦这段代码虽然很少但是内容并不少。
第一行QColorDialog::getColor()调用了QColorDialog的__static函数getColor()__。这个函数有两个参数第一个是QColor类型是对话框打开时**默认选择**的颜色第二个是它的parent。
第二行比较长涉及到QString的用法。如果我没记错的话这些用法还没有提到过本着“有用就说”的原则尽管这些和QColorDialog毫不相干这里还是解释一下。QString("r: %1, g: %2, b: %3")创建了一个QString对象。我们使用了参数化字符串也就是那些%1之类。在Java的properties文件中字符参数是用{0}, {1}之类实现的。其实这都是一些占位符也就是后面会用别的字符串替换掉这些值。占位符的替换需要使用QString的arg()函数。这个函数会返回它的调用者因此可以使用链式调用写法。它会按照顺序替换掉占位符。然后是QString::number()函数这也是QString的一个static函数作用就是把int、double等值换成QString类型。这里是把QColor的R、G、B三个值输出了出来。关于QString类我们会在以后详细说明。
第三行就比较简单了,使用一个消息对话框把刚刚拼接的字符串输出。
现在就可以运行这个测试程序了。看上去很简单,不是吗?
QColorDialog还有一些其他的函数可以使用。
QColorDialog::__setCustomColor()__可以设置用户自定义颜色。这个函数有两个值第一个是自定义颜色的索引第二个是自定义颜色的RGB值类型是QRgb大家可以查阅API文档来看看这个类的使用下面只给出一个简单的用发
QColorDialog::setCustomColor(0, QRgb(0x0000FF));
getColor()还有一个重载的函数,签名如下:
QColorDialog::getColor( const QColor & initial, QWidget * parent, const QString & title, ColorDialogOptions options = 0 )
第一个参数initial和前面一样是对话框打开时的默认选中的颜色
第二个参数parent设置对话框的父组件
第三个参数title设置对话框的title
第四个参数options是QColorDialog::ColorDialogOptions类型的可以设置对话框的一些属性如是否显示Alpha值等具体属性请查阅API文档。特别的这些值是可以使用OR操作的。
QColorDialog相对简单一些API文档也很详细大家遇到问题可以查阅文档的哦

View File

@@ -0,0 +1,78 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T21:24:28+08:00
====== Qt学习之路(17)Qt标准对话框之QMessageBox ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/217694
好久没有更新博客主要是公司里面还在验收一些东西所以没有及时更新。而且也在写一个基于Qt的画图程序基本上类似于PS的东西主要用到的是Qt Graphics View Framework。好了现在还是继续来说说Qt的标准对话框吧
这次来说一下QMessageBox以及类似的几种对话框。其实我们已经用过QMessageBox了就在之前的几个程序中。不过当时是大略的说了一下现在专门来说说这几种对话框。
先来看一下最熟悉的QMessageBox::information。我们在以前的代码中这样使用过
QMessageBox::information(NULL, "Title", "Content", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
下面是一个简单的例子:
{{./1.png}}
现在我们从API中看看它的函数签名
__static __StandardButton QMessageBox::information ( QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton );
首先它是static的所以我们能够使用类名直接访问到(怎么看都像废话…)然后看它那一堆参数第一个参数parent说明它的父组件第二个参数title也就是对话框的标题第三个参数text是对话框显示的内容第四个参数buttons声明对话框放置的按钮默认是只放置一个OK按钮这个参数可以使用或运算例如我们希望有一个Yes和一个No的按钮可以使用QMessageBox::Yes | QMessageBox::No所有的按钮类型可以在QMessageBox声明的__StandarButton枚举__中找到第五个参数defaultButton就是默认选中的按钮默认值是NoButton也就是哪个按钮都不选中。这么多参数豆子也是记不住的啊所以我们在用QtCreator写的时候可以在输入QMessageBox::information之后输入(稍等一下QtCreator就会帮我们把函数签名显示在右上方了还是挺方便的一个功能
Qt提供了五个类似的接口用于显示类似的窗口。具体代码这里就不做介绍只是来看一下样子吧
QMessageBox::critical(NULL, "critical", "Content", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
{{./2.png}}
QMessageBox::warning(NULL, "warning", "Content", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
{{./3.png}}
QMessageBox::question(NULL, "question", "Content", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
{{./4.png}}
QMessageBox::about(NULL, "About", "About this application");
{{./5.png}}
请注意最后一个about()函数是没有后两个关于button设置的按钮的
QMessageBox对话框的文本信息时可以__支持HTML标签__的。例如
QMessageBox::about(NULL, "About", "About this <font color='red'>application</font>");
运行效果如下:
{{./6.png}}
如果我们想自定义图片的话也是很简单的。这时候就不能使用这几个static的函数了而是要我们自己定义一个QMessagebox来使用
QMessageBox message(QMessageBox::NoIcon, "Title", "Content with icon.");
message.setIconPixmap(QPixmap("icon.png"));
message.exec();
这里我们使用的是exec()函数而不是show()因为这是一个__模态对话框__需要有它自己的__事件循环__否则的话我们的对话框会一闪而过哦(感谢laetitia提醒).
需要注意的是,同其他的程序类似,我们在程序中定义的**相对路径**都是要相对于运行时的.exe文件的地址的。比如我们写"icon.png",意思是是在.exe的当前目录下寻找一个"icon.png"的文件。这个程序的运行效果如下:
{{./7.png}}
还有一点要注意我们使用的是png格式的图片。因为Qt内置的处理图片格式是png所以这不会引起很大的麻烦如果你要使用jpeg格式的图片的话Qt是以插件的形式支持的。在开发时没有什么问题不过如果要部署的话需要注意这一点。
最后再来说一下怎么处理对话框的交互。我们使用QMessageBox类的时候有两种方式**一是使用static函数另外是使用构造函数。**
首先来说一下static函数的方式。注意static函数都是要__返回一个StandardButton__我们就可以通过判断这个返回值来对用户的操作做出相应。
QMessageBox::StandardButton rb = QMessageBox::question(NULL, "Show Qt", "Do you want to show Qt dialog?", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if(rb == QMessageBox::Yes)
{
QMessageBox::aboutQt(NULL, "About Qt");
}
如果要使用构造函数的方式,那么我们就要自己运行判断一下啦:
QMessageBox message(QMessageBox::NoIcon, "Show Qt", "Do you want to show Qt dialog?", QMessageBox::Yes | QMessageBox::No, NULL);
if(message.exec() == QMessageBox::Yes)
{
QMessageBox::aboutQt(NULL, "About Qt");
}
其实道理上也是差不多的。

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -0,0 +1,46 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T21:36:13+08:00
====== Qt学习之路(18)Qt标准对话框之QInputDialog ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/219338
这是Qt标准对话框的最后一部分。正如同其名字显示的一样QInputDialog用于接收用户的输入。QInputDialog提供了一些简单的__static函数__用于快速的建立一个对话框正像QColorDialog提供了getColor函数一样。
首先来看看getText函数
bool isOK;
QString text = QInputDialog::getText(NULL, "Input Dialog",
"Please input your comment",
QLineEdit::Normal,
"your comment",
&isOK);
if(isOK) {
QMessageBox::information(NULL, "Information",
"Your comment is: <b>" + text + "</b>",
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes);
}
代码比较简单使用getText函数就可以弹出一个可供用户输入的对话框
{{./1.png}}
下面来看一下这个函数的签名:
static QString QInputDialog::getText ( QWidget * parent,
const QString & title,
const QString & label,
QLineEdit::EchoMode mode = QLineEdit::Normal,
const QString & text = QString(),
bool * ok = 0,
Qt::WindowFlags flags = 0 )
第一个参数parent也就是那个熟悉的父组件的指针第二个参数title就是对话框的标题第三个参数label是在输入框上面的提示语句第四个参数mode用于指明这个QLineEdit的__输入模式__取值范围是QLineEdit::EchoMode默认是Normal也就是正常显示你也可以声明为__password__这样就是密码的输入显示了具体请查阅API第五个参数text是QLineEdit的默认字符串第六个参数ok是可选的如果非NLL则当用户按下对话框的OK按钮时这个bool变量会被置为true可以由这个去判断用户是按下的__OK还是Cancel__从而获知这个text是不是有意义第七个参数flags用于指定对话框的样式。
虽然参数很多但是每个参数的含义都比较明显大家只要参照API就可以知道了。
函数的返回值是QString也就是用户在QLineEdit里面输入的内容。至于这个内容有没有意义那就要看那个ok参数是不是true了。
QInputDialog不仅提供了获取字符串的函数还有getIntegergetDoublegetItem三个类似的函数这里就不一一介绍。

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,71 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T21:41:09+08:00
====== Qt学习之路(19)事件(event) ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/223974
前面说了几个标准对话框下面不打算继续说明一些组件的使用因为这些使用很难讲完很多东西都是与实际应用相关的。实际应用的复杂性决定了我们根本不可能把所有组件的所有使用方法都说明白。这次来说说Qt相对高级一点的特性事件。
事件(event)是由系统或者Qt本身在不同的时刻发出的。当用户按下鼠标敲下键盘或者是窗口需要__重新绘制__的时候都会发出一个相应的事件。一些事件是在对__用户操作__做出响应的时候发出如键盘事件等另一些事件则是由__系统自动发出__如计时器事件。
一般来说使用Qt编程时我们并不会把主要精力放在事件上因为在Qt中需要我们关心的__事件总会发出一个信号__。比如我们关心的是QPushButton的鼠标点击但我们不需要关心这个鼠标点击事件而是关心它的clicked()信号。这与其他的一些框架不同在Swing中你所要关心的是JButton的ActionListener这个点击事件。
Qt的事件很容易和信号槽混淆。这里简单的说明一下__signal由具体对象发出__然后会马上交给由connect函数连接的slot进行处理而对于事件Qt使用一个__事件队列__对所有发出的事件进行维护当新的事件产生时会被追加到事件队列的尾部前一个事件完成后取出后面的事件进行处理。但是必要的时候Qt的事件也是可以不进入事件队列而是__直接处理__的。并且事件还可以使用“__事件过滤器__”进行过滤。
==== 总的来说,如果我们使用组件,我们关心的是信号槽;如果我们自定义组件,我们关心的是事件。 ====
===== 事件处理函数会发出相应的信号。 =====
因为我们可以通过事件来改变组件的默认操作。比如如果我们要自定义一个QPushButton那么我们就需要重写它的鼠标点击事件和键盘处理事件并且在恰当的时候发出clicked()信号。
还记得我们在main函数里面创建了一个QApplication对象然后调用了它的exec()函数吗其实这个函数就是开始Qt的__事件循环__。在执行exec()函数之后程序将进入事件循环来__监听__应用程序的事件。当事件发生时Qt将创建一个__事件对象__。Qt的所有事件都继承于__QEvent__类。在事件对象创建完毕后Qt将这个事件对象传递给QObject的__event()__函数。event()函数并不直接处理事件而是按照事件对象的类型分派给特定的__事件处理函数__(event handler)。关于这一点,我们会在以后的章节中详细说明。
在所有组件的父类QWidget中定义了很多事件处理函数如keyPressEvent()、keyReleaseEvent()、mouseDoubleClickEvent()、mouseMoveEvent ()、mousePressEvent()、mouseReleaseEvent()等。这些函数都是protected virtual的也就是说我们应该在子类中重定义这些函数。下面来看一个例子。
#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QMouseEvent>
class EventLabel : public QLabel
{
protected:
void mouseMoveEvent(QMouseEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
};
void EventLabel::mouseMoveEvent(QMouseEvent *event)
{
this->setText(QString("<center><h1>Move: (%1, %2)</h1></center>")
.arg(QString::number(event->x()), QString::number(event->y())));
}
void EventLabel::mousePressEvent(QMouseEvent *event)
{
this->setText(QString("<center><h1>Press: (%1, %2)</h1></center>")
.arg(QString::number(event->x()), QString::number(event->y())));
}
void EventLabel::mouseReleaseEvent(QMouseEvent *event)
{
QString msg;
msg.sprintf("<center><h1>Release: (%d, %d)</h1></center>",
event->x(), event->y());
this->setText(msg);
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
EventLabel *label = new EventLabel;
label->setWindowTitle("MouseEvent Demo");
** label->resize(300, 200);**
label->show();
return app.exec();
}
这里我们继承了QLabel类重写了mousePressEvent、mouseMoveEvent和MouseReleaseEvent三个函数。我们并没有添加什么功能只是在鼠标按下(press)、鼠标移动(move)和鼠标释放(release)时把坐标显示在这个Label上面。注意我们在mouseReleaseEvent函数里面有关QString的构造。我们没有使用arg参数的方式而是使用C语言风格的sprintf来构造QString对象如果你对C语法很熟悉(估计很多C+++程序员都会比较熟悉的吧)那么就可以在Qt中试试熟悉的C格式化写法啦

View File

@@ -0,0 +1,64 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T12:47:52+08:00
====== Qt学习之路(2)Hello, world! ======
Created Monday 31 October 2011
http://devbean.blog.51cto.com/448512/194031
任何编程技术的学习第一课基本上都会是Hello, world!我也不想故意打破这个惯例——照理说应该首先回顾一下Qt的历史不过即使不说这些也并无大碍。
或许有人总想知道Qt这个单词是什么意思。其实这并不是一个缩写词仅仅是因为它的发明者TrollTech公司的CEOHaarard Nord和Trolltech公司的总裁Eirik Chambe-Eng在联合发明Qt的时候并没有一个很好的名字。在这里字母Q是Qt库中所有类的前缀——这仅仅是因为在Haarard的emacs的字体中这个字母看起来特别的漂亮而字母t则代表“toolkit”这是在Xt( X toolkit )中得到的灵感。
顺便说句Qt原始的公司就是上面提到的Trolltech貌似有一个中文名字是奇趣科技——不过现在已经被Nokia收购了。因此一些比较旧的文章里面会提到Trolltech这个名字。
好了闲话少说先看看Qt的开发吧事先说明一下我是一个比较懒的人不喜欢配置很多的东西而Qt已经提供了一个轻量级的IDE并且它的网站上也有for Eclipse 和 VS 的开发插件,不过在这里我并不想用这些大块头 :)
Qt有两套协议——商业版本和开源的LGPL版本。不同的是前者要收费而后者免费当然后者还要遵循LGPL协议的规定这是题外话。
Qt的网址是https://qt.nokia.com/downloads不过我打开这个站点总是很慢不知道为什么。你可以找到大大的 LGPL/Free 和 Commercial好了我选的是LGPL版本的下载包蛮大但是下载并不会很慢。下载完成后安装就可以了其它不用管了。这样整个Qt的开发环境就装好了——如果你需要的话也可以把qmake所在的目录添加进环境变量不过我就不做了。
安装完成后会有个Qt Creator的东西这就是官方提供的一个轻量级IDE不过它的功能还是蛮强大的。运行这个就会发现其实Qt不仅仅是Linux KDE桌面的底层实现库。而且是这个IDE的实现 :) 这个IDE就是用Qt完成的。
Qt Creator左面从上到下依次是Welcome(欢迎页面,就是一开始出现的那个)Edit(我们的代码编辑窗口)Debug(调试窗口)Projects(工程窗口)Help(帮助这个帮助完全整合的Qt的官方文档相当有用)Output(输出窗口)。
下面我们来试试我们的 Hello, world! 吧!
在Edit窗口空白处点右键有 New project... 这里我们选第三项Qt Gui Application。
{{~/sync/notes/zim/Programme/Qt学习之路/Qt学习之路(2)Hello,_world!/1.png}}
然后点击OK来到下一步输入工程名字和保存的位置。
{{~/sync/notes/zim/Programme/Qt学习之路/Qt学习之路(2)Hello,_world!/2.png}}
点击Next来到选择库的界面。这里我们系统默认为我们选择了Qt core 和 GUI还记得我们建的是Gui Application吗就是这里啦它会自动为我们加上gui这个库。现在应该就能看出Qt是多么庞大的一个库它不仅仅有Gui而且有NetworkOpenGLXML之类。不过现在在这里我们不作修改直接Next。
{{~/sync/notes/zim/Programme/Qt学习之路/Qt学习之路(2)Hello,_world!/3.png}}
下一个界面需要我们定义文件名我们不修改默认的名字只是为了清除起见把generate form的那个勾去掉即可。
{{~/sync/notes/zim/Programme/Qt学习之路/Qt学习之路(2)Hello,_world!/7.png}}
Next之后终于到了Finish了——漫长的一系列啊检查无误后Finish就好啦
{{~/sync/notes/zim/Programme/Qt学习之路/Qt学习之路(2)Hello,_world!/4.png}}
之后可以看到IDE自动生成了四个文件一个.pro文件两个.cpp和一个.h。这里说明一下.pro就是工程文件(project)它是qmake自动生成的用于生产makefile的配置文件。这里我们先不去管它。main.cpp里面就是一个main函数其他两个文件就是先前我们曾经指定的文件名的文件。
{{~/sync/notes/zim/Programme/Qt学习之路/Qt学习之路(2)Hello,_world!/5.png}}
现在我们把main.cpp中的代码修改一下
#include <QtGui/QApplication>
#include <QLabel>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QLabel *label = new QLabel("Hello, world!");
label->show();
return a.exec();
}
修改完成后保存。点击左下角的绿色三角键Run。一个小小的窗口出现了——
{{~/sync/notes/zim/Programme/Qt学习之路/Qt学习之路(2)Hello,_world!/6.png}}
好了我们的第一个Qt程序已经完成了。
PS截了很多图说得详细些以后可就没这么详细的步骤啦嘿嘿…相信很多朋友应该一下子就能看明白这个IDE应该怎么使用了的无需我多费口舌。呵呵。

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,75 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T21:55:49+08:00
====== Qt学习之路(20)事件接收与忽略 ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/225519
本章内容也是关于Qt事件。或许这一章不能有一个完整的例子因为对于事件总是感觉很抽象还是从底层上理解一下比较好的吧
前面说到了事件的作用下面来看看我们如何来__接收事件__。回忆一下前面的代码我们在子类中重写了事件函数以便让这些子类按照我们的需要完成某些功能就像下面的代码
void MyLabel::mousePressEvent(QMouseEvent * event)
{
if(event->button() == Qt::LeftButton) {
// do something
} else {
QLabel::mousePressEvent(event);
}
}
上面的代码和前面类似,在鼠标按下的事件中检测,如果按下的是左键,做我们的处理工作,如果不是左键,则调用父类的函数。这在某种程度上说,是把事件向上**传递给父类**去响应也就是说我们在子类中“__忽略__”了这个事件。
我们可以把Qt的事件传递看成__链状__如果子类没有处理这个事件就会继续向其他类__传递__。其实Qt的事件对象都有一个**accept()函数和ignore()函数**。正如它们的名字前者用来告诉Qt事件处理函数“接收”了这个事件不要再传递后者则告诉Qt事件处理函数“忽略”了这个事件**需要**继续传递寻找另外的接受者。在事件处理函数中可以使用isAccepted()来查询这个事件是不是已经被接收了。
事实上我们很少使用accept()和ignore()函数而是想上面的示例一样如果希望忽略事件只要调用父类的响应函数即可。记得我们曾经说过Qt中的事件大部分是protected的因此重写的函数必定存在着其父类中的响应函数这个方法是可行的。为什么要这么做呢因为我们无法确认父类中的这个处理函数的操作如果我们在子类中直接忽略事件Qt不会再去寻找其他的接受者那么父类的操作也就不能进行这可能会有__潜在__的危险。
另外我们查看一下QWidget的mousePressEvent()函数的实现:
void QWidget::mousePressEvent(QMouseEvent *event)
{
event->ignore();
if ((windowType() == Qt::Popup)) {
event->accept();
QWidget* w;
while ((w = qApp->activePopupWidget()) && w != this){
w->close();
if (qApp->activePopupWidget() == w) // widget does not want to dissappear
w->hide(); // hide at least
}
if (!rect().contains(event->pos())){
close();
}
}
}
请注意第一条语句如果所有子类都没有覆盖mousePressEvent函数这个事件会在这里**被忽略掉**,这暗示着这个组件不关心这个事件,这个事件就可能被传递给其父组件。
不过事情也不是绝对的。在一个情形下我们必须使用accept()和ignore()函数那就是在__窗口关闭__的时候。如果你在窗口关闭时需要有个询问对话框那么就需要这么去写
void MainWindow::closeEvent(QCloseEvent * event)
{
if(continueToClose()) {
event->accept();
} else {
event->ignore();
}
}
bool MainWindow::continueToClose()
{
if(QMessageBox::question(this,
tr("Quit"),
tr("Are you sure to quit this application?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No)
== QMessageBox::Yes) {
return true;
} else {
return false;
}
}
这样,我们经过询问之后才能正常退出程序。

View File

@@ -0,0 +1,51 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T22:05:06+08:00
====== Qt学习之路(21)event() ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/230883
今天要说的是event()函数。记得之前曾经提到过这个函数说在事件对象创建完毕后Qt将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler)。
event()函数主要用于__事件的分发__所以如果你希望在事件分发之前做一些操作那么就需要注意这个event()函数了。为了达到这种目的我们可以重写event()函数。例如如果你希望在窗口中的tab键按下时将焦点移动到下一组件而不是让具有焦点的组件处理那么你就可以继承QWidget并重写它的event()函数,已达到这个目的:
bool MyWidget::event(QEvent *event) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Tab) {
// 处理Tab鍵
return true;
}
}
return QWidget::event(event);
}
event()函数接受一个QEvent对象也就是需要这个函数进行转发的对象。为了进行转发必定需要有一系列的__类型判断__这就可以调用QEvent的type()函数其返回值是__QEvent::Type类型的枚举__。我们处理过自己需要的事件后可以直接return回去对于其他我们不关心的事件需要调用父类的event()函数继续转发,否则这个组件就只能处理我们定义的事件了。
event()函数返回值是bool类型如果传入的事件已被识别并且处理返回true否则返回false。如果返回值是trueQApplication会认为这个事件已经处理完毕会继续处理事件队列中的下一事件如果返回值是falseQApplication会尝试寻找这个事件的下一个处理函数。
event()函数的返回值和事件的accept()和ignore()函数不同。accept()和ignore()函数用于不同的事件处理器之间的沟通例如判断这一事件是否处理event()函数的返回值主要是通知QApplication的notify()函数是否处理下一事件。为了更加明晰这一点我们来看看QWidget的event()函数是如何定义的:
bool QWidget::event(QEvent *event) {
switch (e->type()) {
case QEvent::KeyPress:
keyPressEvent((QKeyEvent *)event);
if (!((QKeyEvent *)event)->isAccepted())
return false;
break;
case QEvent::KeyRelease:
keyReleaseEvent((QKeyEvent *)event);
if (!((QKeyEvent *)event)->isAccepted())
return false;
break;
// more...
}
return true;
}
QWidget的event()函数使用一个巨大的switch来判断QEvent的type并且分发给不同的事件处理函数。在事件处理函数之后使用这个事件的isAccepted()方法获知这个事件是不是被接受如果没有被接受则event()函数立即返回false否则返回true。
另外一个必须重写event()函数的情形是有自定义事件的时候。如果你的程序中有自定义事件则必须重写event()函数以便将自定义事件进行分发,否则你的自定义事件永远也不会被调用。关于自定义事件,我们会在以后的章节中介绍。

View File

@@ -0,0 +1,50 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T22:13:57+08:00
====== Qt学习之路(22)事件过滤器 ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/231861
Qt创建了QEvent事件对象之后会调用QObject的**event()**函数做事件的分发。有时候你可能需要在调用event()函数之前做一些另外的操作比如对话框上某些组件可能并不需要响应回车按下的事件此时你就需要重新定义组件的event()函数。如果组件很多就需要重写很多次event()函数,这显然没有效率。为此,你可以使用一个**事件过滤器**来判断是否需要调用event()函数。
QOjbect有一个__eventFilter()__函数用于建立事件过滤器。这个函数的签名如下
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event )
如果watched对象安装了事件过滤器这个函数会被调用并进行事件过滤然后才轮到组件进行事件处理。在重写这个函数时如果你需要过滤掉某个事件例如停止对这个事件的响应需要返回__true__。
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == textEdit) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
__ qDebug() __<< "Ate key press" << keyEvent->key();
return __true__;
} else {
return false;
}
} else {
// pass the event on to the parent class
return QMainWindow::eventFilter(obj, event);
}
}
上面的例子中为MainWindow建立了一个事件过滤器。为了过滤某个组件上的事件首先需要判断这个对象是哪个组件然后判断这个事件的类型。例如我不想让textEdit组件处理键盘事件于是就首先找到这个组件如果这个事件是键盘事件则直接返回true也就是过滤掉了这个事件其他事件还是要继续处理所以返回false。对于其他组件我们并不保证是不是还有过滤器于是最保险的办法是调用父类的函数。
在创建了过滤器之后下面要做的是__安装这个过滤器__。安装过滤器需要调用installEventFilter()函数。这个函数的签名如下:
void QObject::installEventFilter ( QObject * filterObj )
这个函数是QObject的一个函数因此可以安装到任何QObject的子类并不仅仅是UI组件。这个函数接收一个QObject对象调用了这个函数安装事件过滤器的组件会调用filterObj定义的eventFilter()函数。例如textField.installEventFilter(obj)则如果有事件发送到textField组件时会先调用obj->eventFilter()函数然后才会调用textField.event()。
当然你也可以把事件过滤器安装到QApplication上面这样就可以过滤所有的事件已获得更大的控制权。不过这样做的后果就是会降低事件分发的效率。
如果一个组件安装了多个过滤器则__最后一个安装的会最先调用__类似于堆栈的行为。
注意如果你在事件过滤器中delete了某个接收组件务必将返回值设为true。否则Qt还是会将事件分发给这个接收组件从而导致程序崩溃。
事件过滤器和被安装的组件必须在__同一线程__否则过滤器不起作用。另外如果在install之后这两个组件到了不同的线程那么只有等到二者重新回到同一线程的时候过滤器才会有效。
事件的调用最终都会调用QCoreApplication的__notify()函数__因此最大的控制权实际上是重写QCoreApplication的notify()函数。由此可以看出Qt的事件处理实际上是分层五个层次重定义事件处理函数重定义event()函数为单个组件安装事件过滤器为QApplication安装事件过滤器重定义QCoreApplication的notify()函数。这几个层次的控制权是逐层增大的。

View File

@@ -0,0 +1,52 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-11-04T17:19:36+08:00
====== Qt学习之路(23) 自定义事件 ======
Created Friday 04 November 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/232314
这部分将作为Qt事件部分的结束。我们在前面已经从大概上了解了Qt的事件机制。下面要说的是如何自定义事件。
Qt允许你创建自己的事件类型这在多线程的程序中尤其有用当然也可以用在单线程的程序中作为一种**对象间通讯**的机制。那么为什么我需要使用事件而不是使用信号槽呢主要原因是__事件的分发既可以是同步的又可以是异步的而函数的调用或者说是槽的回调总是同步的。事件的另外一个好处是它可以使用过滤器。__
Qt中的自定义事件很简单同其他类似的库的使用很相似都是要继承一个类进行扩展。在Qt中你需要继承的类是QEvent。注意在Qt3中你需要继承的类是QCustomEvent不过这个类在Qt4中已经被废除(这里的废除是不建议使用,并不是从类库中删除)。
继承QEvent类你需要提供一个QEvent::Type类型的参数作为自定义事件的类型值。这里的QEvent::Type类型是QEvent里面定义的一个enum因此你是可以传递一个int的。重要的是你的事件类型不能和已经存在的type值重复否则会有不可预料的错误发生因为系统会将你的事件当做系统事件进行派发和调用。在Qt中系统将保留0 - 999的值也就是说你的事件type要大于999. 具体来说你的自定义事件的type要在QEvent::User和QEvent::MaxUser的范围之间。其中QEvent::User值是1000QEvent::MaxUser的值是65535。从这里知道你最多可以定义64536个事件相信这个数字已经足够大了但是即便如此也只能保证用户自定义事件不能覆盖系统事件并不能保证自定义事件之间不会被覆盖。为了解决这个问题Qt提供了一个函数registerEventType(),用于自定义事件的注册。该函数签名如下:
static int QEvent::registerEventType ( int hint = -1 );
函数是static的因此可以使用QEvent类直接调用。函数接受一个int值其默认值为-1返回值是创建的这个Type类型的值。如果hint是合法的不会发生任何覆盖则会返回这个值如果hint不合法系统会自动分配一个合法值并返回。因此使用这个函数即可完成type值的指定。这个函数是线程安全的因此不必另外添加同步。
你可以在QEvent子类中添加自己的事件所需要的数据然后进行事件的发送。Qt中提供了两种发送方式
static bool QCoreApplication::sendEvent(QObjecy * receiver, QEvent * event)事件被QCoreApplication的notify()函数直接发送给receiver对象返回值是事件处理函数的返回值。使用这个函数必须要在栈上创建对象例如
QMouseEvent event(QEvent::MouseButtonPress, pos, 0, 0, 0);
QApplication::sendEvent(mainWindow, &event);
static bool QCoreApplication::postEvent(QObject * receiver, QEvent * event)事件被QCoreApplication追加到事件列表的最后并等待处理该函数将事件追加后会立即返回并且注意该函数是线程安全的。另外一点是使用这个函数必须要在堆上创建对象例如
QApplication::postEvent(object, new MyEvent(QEvent::registerEventType(2048)));
这个对象不需要手动deleteQt会自动delete掉因此如果在post事件之后调用delete程序可能会崩溃。另外postEvent()函数还有一个重载的版本增加一个优先级参数具体请参见API。通过调用sendPostedEvent()函数可以让已提交的事件立即得到处理。
如果要处理自定义事件可以重写QObject的customEvent()函数该函数接收一个QEvent对象作为参数。注意在Qt3中这个参数是QCustomEvent类型的。你可以像前面介绍的重写event()函数的方法去重写这个函数:
void CustomWidget::customEvent(QEvent *event) {
CustomEvent *customEvent = static_cast<CustomEvent *>(event);
// ....
}
另外你也可以通过重写event()函数来处理自定义事件:
bool CustomWidget::event(QEvent *event) {
if (event->type() == MyCustomEventType) {
CustomEvent *myEvent = static_cast<CustomEvent *>(event);
// processing...
return true;
}
return QWidget::event(event);
}
这两种办法都是可行的。
好了至此我们已经概略的介绍了Qt的事件机制包括事件的派发、自定义等一系列的问题。下面的章节将继续我们的学习之路

View File

@@ -0,0 +1,83 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-11-04T17:21:04+08:00
====== Qt学习之路(24) QPainter ======
Created Friday 04 November 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/235332
多些大家对我的支持啊有朋友也提出前面的几节有关event的教程缺少例子。因为event比较难做例子也就没有去写只是把大概写了一下。今天带来的是新的部分有关Qt的2D绘图。这部分不像前面的内容还是比较好理解的啦所以例子也会增加出来。
有人问豆子拿Qt做什么其实豆子就是在做一个Qt的画图程序努力朝着Photoshop和GIMP的方向发展。但这终究要经过很长的时间、很困难的路程的所以也放在网上开源有兴趣的朋友可以来试试的呀…
Qt的绘图系统允许使用相同的API在屏幕和打印设备上进行绘制。整个绘图系统基于QPainterQPainterDevice和QPaintEngine三个类。
QPainter用来执行绘制的操作QPaintDevice是一个**二维空间的抽象**这个二维空间可以由QPainter在上面进行绘制QPaintEngine提供了画笔painter在不同的设备上进行绘制的统一的接口。QPaintEngine类用在QPainter和QPaintDevice之间并且通常对开发人员是透明的除非你需要自定义一个设备这时候你就必须重新定义QPaintEngine了。
下图给出了这三个类之间的层次结构(出自Qt API 文档)
{{./1.png}}
===== QPainter =====
这种实现的主要好处是所有的绘制都遵循着同一种绘制流程这样添加可以很方便的添加新的特性也可以为不支持的功能添加一个默认的实现方式。另外需要说明一点Qt提供了一个独立的QtOpenGL模块可以让你在Qt的应用程序中使用OpenGL功能。该模块提供了一个OpenGL的模块可以像其他的Qt组件一样的使用。它的不同之处在于它是使用OpenGL作为显示技术使用OpenGL函数进行绘制。对于这个组件我们以后会再介绍。
通过前面的介绍我们知道Qt的绘图系统实际上是说**使用QPainter在QPainterDevice上面进行绘制它们之间使用QPaintEngine进行通讯**。好了下面我们来看看怎么使用QPainter。
首先我们定义一个组件,同前面的定义类似:
class PaintedWidget : public QWidget
{
public:
PaintedWidget();
protected:
void paintEvent(QPaintEvent *event);
};
这里我们只定义了一个构造函数并且重定义paintEvent()函数。从名字就可以看出这实际上是一个事件的回调函数。请注意一般而言Qt的事件函数都是protected的所以如果你要重写事件就需要继承这个类了。至于事件相关的东西我们在前面的内容已经比较详细的叙述了这里不再赘述。
构造函数里面主要是一些大小之类的定义,这里不再详细说明:
PaintedWidget::PaintedWidget()
{
resize(800, 600);
setWindowTitle(tr("Paint Demo"));
}
我们关心的是paintEvent()函数的实现:
void PaintedWidget::paintEvent(QPaintEvent *event)
{
** QPainter painter(this);**
painter.drawLine(80, 100, 650, 500);
painter.setPen(Qt::red);
painter.drawRect(10, 10, 100, 400);
painter.setPen(QPen(Qt::green, 5));
painter.setBrush(Qt::blue);
painter.drawEllipse(50, 150, 400, 200);
}
为了把我们的程序运行起来下面是main()函数:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
PaintedWidget w;
w.show();
return app.exec();
}
运行结果如下所示:
{{./2.png}}
首先我们声明了一个QPainter对象。注意我们在这个**函数的栈空间**建立了对象因此不需要delete。
QPainter接收一个QPaintDevice*类型的参数。QPaintDevice有很多子类比如**QImage以及QWidget**。注意回忆一下QPaintDevice可以理解成**要在哪里去画**而现在我们希望在这个widget上画因此传入的是this指针。
QPainter有很多以draw开头的函数用于各种图形的绘制比如这里的drawLinedrawRect和和drawEllipse等。具体的参数请参阅API文档。下图给出了QPainter的draw函数的实例本图来自C++ GUI Programming with Qt4, 2nd Edition.
{{./3.png}}
好了今天先到这里我们将在下面一章中继续对这个paintEvent()函数进行说明。

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View File

@@ -0,0 +1,44 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-11-04T17:29:36+08:00
====== Qt学习之路(25)QPainter(续) ======
Created Friday 04 November 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/235851
过去一天没有接上上章的东西,今天继续啊!
首先还是要先把上次的代码拿上来。
void PaintedWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.drawLine(80, 100, 650, 500);
painter.setPen(Qt::red);
painter.drawRect(10, 10, 100, 400);
painter.setPen(QPen(Qt::green, 5));
painter.setBrush(Qt::blue);
painter.drawEllipse(50, 150, 400, 200);
}
上次我们说的是Qt绘图相关的架构以及QPainter的建立和drawXXXX函数。可以看到基本上代码中已经设计到得函数还剩下两个setPen()和setBrush()。现在,我们就要把这两个函数讲解一下。
Qt绘图系统提供了三个主要的参数设置**画笔(pen)、画刷(brush)和字体(font)**。这里我们要说明的是画笔和画刷。
所谓画笔是用于绘制线的比如线段、轮廓线等都需要使用画笔绘制。画笔类即QPen可以设置画笔的样式例如虚线、实现之类画笔的颜色画笔的转折点样式等。画笔的样式可以在创建时指定也可以由__setStyle()__函数指定。画笔支持三种主要的样式**笔帽(cap),结合点(join)和线形 (line)**。这些样式具体显示如下(图片来自C++ GUI Programming with Qt4, 2nd Edition)
{{./1.png}}
上图共分成三行第一行是Cap样式第二行是Join样式第三行是Line样式。QPen允许你使用setCapStyle()、setJoinStyle()和setStyle()分别进行设置。具体请参加API文档。
所谓画刷,主要用来填充封闭的几何图形。画刷主要有两个参数可供设置:**颜色和样式**。当然,你也可以使用**纹理或者渐变色**来填充图形。请看下面的图片(图片出自Qt API 文档)
{{./2.png}}
这里给出了不同style的画刷的表现。同画笔类似这些样式也可用通过一个enum进行设置。
明白了这些之后我们再来看看我们的代码。首先我们直接使用drawLine()函数由于没有设置任何样式所以使用的是默认的1px,黑色solid样式画了一条直线然后使用setPen()函数将画笔设置成Qt::red即红色画了一个矩形最后将画笔设置成绿色5px画刷设置成蓝色画了一个椭圆。这样便显示出了我们最终的样式
{{./3.png}}
另外要说明一点,请注意我们的绘制顺序,首先是直线,然后是矩形,最后是椭圆。这样,因为椭圆是最后画的,因此在最上方。
在我们学习OpenGL的时候肯定听过这么一句话OpenGL是一个__状态机__。所谓状态机就是说OpenGL保存的只是各种状态。怎么理解呢比如你把颜色设置成红色那么直到你重新设置另外的颜色它的颜色会一直是红色。QPainter也是这样它的状态不会自己恢复除非你使用了各种set函数。因此如果在上面的代码中我们在椭圆绘制之后再画一个椭圆它的样式还会是绿色5px的轮廓和蓝色的填充除非你显式地调用了set进行更新。这可能是绘图系统较多的实现方式因为无论是OpenGL、QPainter还是Java2D都是这样实现的(DirectX不大清楚)。

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,48 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-11-04T18:49:26+08:00
====== Qt学习之路(26) 反走样 ======
Created Friday 04 November 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/237447
今天继续前面的内容。既然已经进入2D绘图部分那么就先继续研究一下有关QPainter的东西吧
反走样是对**画笔**即图形的**外边界**而言的,对于图形内部并不适用。
反走样是图形学中的重要概念用以防止“锯齿”现象的出现。很多系统的绘图API里面都会内置了反走样的算法不过**默认一般都是关闭的**Qt也不例外。下面我们来看看代码。这段代码仅仅给出了paintEvent函数相信你可以很轻松地替换掉前面章节中的相关代码。
void PaintedWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setPen(QPen(Qt::black, 5, Qt::DashDotLine, Qt::RoundCap));
painter.setBrush(Qt::yellow);
painter.drawEllipse(50, 150, 200, 150);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(QPen(Qt::black, 5, Qt::DashDotLine, Qt::RoundCap));
painter.setBrush(//Qt::yellow//);
painter.drawEllipse(300, 150, 200, 150);
}
看看运行后的效果:
{{./1.png}}
左边的是没有使用反走样技术的,右边是使用了反走样技术的。二者的差别可以很容易的看出来。
下面来看看相关的代码。为了尝试画笔的样式,这里故意使用了一个新的画笔:
painter.setPen(QPen(Qt::black, 5, Qt::DashDotLine, Qt::RoundCap));
我们对照着API去看第一个参数是画笔颜色这里设置为黑色第二个参数是画笔的粗细这里是5px第三个是画笔样式我们使用了DashDotLine正如同其名字所示是一个短线和一个点相间的类型第四个是RoundCap也就是圆形笔帽。然后我们使用一个黄色的画刷填充画了一个椭圆。
后面的一个和前面的十分相似,唯一的区别是多了一句
painter.setRenderHint(QPainter::Antialiasing, true);
不过这句也很清楚就是设置Antialiasing属性为true。如果你学过图形学就会知道这个长长的单词就是“反走样”。经过这句设置我们就打开了QPainter的反走样功能。还记得我们曾经说过**QPainter是一个状态机因此只要这里我们打开了它之后所有的代码都会是反走样绘制的了。**
看到这里你会发现反走样的效果其实比不走样要好得多那么为什么不默认打开反走样呢这是因为__反走样是一种比较复杂的算法__在一些对图像质量要求不高的应用中是不需要进行反走样的。为了提高效率一般的图形绘制系统如Java2D、OpenGL之类都是默认不进行反走样的。
还有一个疑问既然反走样比不反走样的图像质量高很多不进行反走样的绘制还有什么作用呢前面说的是一个方面也就是在一些对图像质量要求不高的环境下或者说性能受限的环境下比如嵌入式和手机环境是不必须要进行反走样的。另外还有一点在一些__必须精确操作像素__的应用中也是不能进行反走样的。请看下面的图片
{{./2.png}}
上图是使用Photoshop的铅笔和画笔工具画的1像素的点在放大到3200%视图下截下来的。Photoshop里面的铅笔工具是不进行反走样而画笔是要进行反走样的。在放大的情况下就会知道有反走样的情况下是不能进行精确到1像素的操作的。因为反走样很难让你控制到1个像素。这不是Photoshop画笔工具的缺陷而是反走样算法的问题。如果你想了解为什么这样请查阅计算机图形学里面关于反走样的原理部分。

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -0,0 +1,59 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-11-04T18:54:16+08:00
====== Qt学习之路(27)渐变填充 ======
Created Friday 04 November 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/238168
前面说了有关反走样的相关知识下面来说一下渐变。渐变是绘图中很常见的一种功能简单来说就是可以把几种颜色混合在一起让它们能够__自然地过渡__而不是一下子变成另一种颜色。渐变的算法比较复杂写得不好的话效率会很低好在很多绘图系统都内置了渐变的功能Qt也不例外。**渐变一般是用在填充里面的**所以渐变的设置就是在QBrush里面。
Qt提供了三种渐变画刷分别是**线性渐变(QLinearGradient)、辐射渐变(QRadialGradient)、角度渐变(QConicalGradient)**。如下图所示(图片出自C++ GUI Programming with Qt4, 2nd Edition)
{{./1png}}
下面我们来看一下线性渐变QLinearGradient的用法。
void PaintedWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
QLinearGradient linearGradient(60, 50, 200, 200);
linearGradient.setColorAt(0.2, Qt::white);
linearGradient.setColorAt(0.6, Qt::green);
linearGradient.setColorAt(1.0, Qt::black);
painter.setBrush(**QBrush(linearGradient)**);
painter.drawEllipse(50, 50, 200, 150);
}
同前面一样这里也仅仅给出了paintEvent()函数里面的代码。
首先我们打开了反走样然后创建一个QLinearGradient对象实例。QLinearGradient构造函数有四个参数分别是x1, y1, x2, y2即**渐变的起始点和终止点**。在这里,我们从(60, 50)开始渐变,到(200, 200)止。
渐变的颜色是在setColorAt()函数中指定的。下面是这个函数的签名:
void QGradient::setColorAt ( qreal position, const QColor & color )
它的意思是把position位置的颜色设置成color。其中position是一个0 - 1区间的数字。也就是说position是相对于我们建立渐变对象时做的那个起始点和终止点区间的。比如这个线性渐变就是说在从(60, 50)到(200, 200)的线段上在0.2也就五分之一处设置成白色在0.6也就是五分之三处设置成绿色在1.0也就是终点处设置成黑色。
在创建QBrush时把这个渐变对象传递进去就是我们的结果啦
{{./2.png}}
那么我们怎么让线段也是渐变的呢要知道直线是用画笔绘制的啊这里如果你仔细查阅了API文档就会发现QPen是接受QBrush作为参数的。也就是说你可以利用一个QBrush创建一个QPen这样QBrush所有的填充效果都可以用在画笔上了
void PaintedWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
QLinearGradient linearGradient(60, 50, 200, 200);
linearGradient.setColorAt(0.2, Qt::white);
linearGradient.setColorAt(0.6, Qt::green);
linearGradient.setColorAt(1.0, Qt::black);
painter.setPen(QPen(**QBrush(linearGradient),** 5));
painter.drawLine(50, 50, 200, 200);
}
看看我们的渐变线吧!
{{./3.png}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,53 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-11-04T19:06:15+08:00
====== Qt学习之路(28)坐标变换 ======
Created Friday 04 November 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/239585
经过前面的章节我们已经能够画出一些东西来主要就是使用QPainter的相关函数。今天我们要看的是QPainter的坐标系统。
同很多坐标系统一样QPainter的默认**坐标的原点**(0, 0)位于屏幕的左上角X轴正方向是水平向右Y轴正方向是竖直向下。在这个坐标系统中每个像素占据1 x 1的空间。你可以把它想象成是一张坐标值其中的每个小格都是1个像素。这么说来一个像素的中心实际上是一个“__半像素坐标系__”也就是说像素(x, y)的中心位置其实是在(x + 0.5, y + 0.5)的位置上。因此如果我们使用QPainter在(100, 100)处绘制一个像素,那么,这个像素的中心坐标是(100.5, 100.5)。
这种细微的差别在实际应用中,特别是对**坐标要求精确**的系统中是很重要的。首先只有在禁止反走样也就是默认状态下才会有这__0.5像素__的偏移如果使用了反走样那么我们画(100, 100)位置的像素时QPainter会在(99.5, 99.5)(99.5, 100.5)(100.5, 99.5)和(100.5, 100.5)四个位置绘制一个亮色的像素,这么产生的效果就是在这四个像素的焦点处(100, 100)产生了一个像素。如果不需要这个特性就需要将QPainter的坐标系平移(0.5, 0.5)。
这一特性在绘制直线、矩形等图形的时候都会用到。下图给出了在没有反走样技术时使用drawRect(2, 2, 6, 5)绘制一个矩形的示例。在No Pen的情况下请注意矩形左上角的像素是在(2, 2),其中心位置是在(2.5, 2.5)的位置。然后注意下有不同的Pen的值的绘制样式在Pen宽为1时实际画出的矩形的面积是7 x 6的(图出自C++ GUI Programming with Qt4, 2nd Edition)
{{./1.png}}
在具有反走样时使用drawRect(2, 2, 6, 5)的效果如下(图出自C++ GUI Programming with Qt4, 2nd Edition)
{{./2.png}}
注意我们前面说过通过平移QPainter的坐标系来消除着0.5像素的差异。下面给出了使用drawRect(2.5, 2.5, 6, 5)在反走样情况下绘制的矩形(图出自C++ GUI Programming with Qt4, 2nd Edition)
{{./3.png}}
请对比与上图的区别。
在上述的QPainter的默认坐标系下QPainter提供了__视口__(viewport)、__窗口__(window)机制用于绘制与绘制设备的大小和分辨率无关的图形。视口和窗口是紧密的联系在一起的它们一般都是矩形。视口是由__物理坐标__确定其大小而窗口则是由__逻辑坐标__决定。我们在使用QPainter进行绘制时传给QPainter的是逻辑坐标然后Qt的绘图机制会使用__坐标变换__将逻辑坐标转换成物理坐标后进行绘制。
通常视口和窗口的坐标是一致的。比如一个600 x 800的widget(这是一个widget或许是一个对话框或许是一个面板等等),默认情况下,视口和窗口都是一个**320 x 200的矩形**,原点都在(0, 0),此时,视口和窗口的坐标是相同的。
注意到QPainter提供了setWindow()和setViewport()函数用来设置视口和窗口的__矩形大小__。比如在上面所述的320 x 200的widget中我们要设置一个从(-50, -50)到(+50, +50),原点在中心的矩形窗口,就可以使用
painter.setWindow(-50, -50, 100, 100);
其中,(-50, -50)指明了原点100, 100指明了窗口的长和宽。这里的“指明原点”意思是逻辑坐标的(-50, -50)对应着物理坐标的(0, 0)“长和宽”说明逻辑坐标系下的长100宽100实际上对应物理坐标系的长320宽200。
或许你已经发现这么一个好处我们可以__随时改变window的范围而不改变底层物理坐标系__。这就是前面所说的视口与窗口的作用“绘制与绘制设备的大小和分辨率无关的图形”如下图所示(图出自C++ GUI Programming with Qt4, 2nd Edition)
{{./4.png}}
除了视口与窗口的变化QPainter还提供了一个“**世界坐标系**”,同样也可以变换图形。所不同的是,视口与窗口实际上是同一图形在两个坐标系下的表达,而世界坐标系的变换是通过**改变坐标系**来平移、缩放、旋转、剪切图形。为了清楚起见,我们来看下面一个例子:
void PaintedWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QFont font("Courier", 24);
painter.setFont(font);
painter.drawText(50, 50, "Hello, world!");
QTransform** transform**;
transform.rotate(+45.0);
painter.setWorldTransform(transform);
painter.drawText(60, 60, "Hello, world!");
}
为了显示方便我在这里使用了QFont改变了字体。QPainter的drawText()函数提供了绘制文本的功能。它有几种重载形式,我们使用了其中的一种,即制定文本的坐标然后绘制。需要注意的是,这里的坐标是**文字左下角的坐标**(特别提醒这一点因为很多绘图系统比如Java2D都是把左上角作为坐标点的)!下面是运行结果:
{{./5.png}}
我们使用QTransform做了一个rotate变换。这个变换就是旋转而且是顺时针旋转45度。然后我们使用这个变换设置了QPainter的世界坐标系注意到QPainter是一个状态机所以这种变换并不会改变之前的状态因此只有第二个Hello, world!被旋转了。确切的说__被旋转的是坐标系而不是这个文字__请注意体会这两种说法的不同。

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,69 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T13:34:07+08:00
====== Qt学习之路(3)Hello, world!(续) ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/194137
下面来逐行解释一下前面的那个Hello, world!程序尽管很简单但却可以对Qt程序的结构有一个清楚的认识。现在再把代码贴过来
#include <QApplication>
#include <QLabel>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QLabel *label = new QLabel("Hello, world!");
label->show();
return app.exec();
}
第1行和第2行就是需要引入的头文件。和普通的C++程序没有什么两样如果要使用某个组件就必须要引入相应的头文件这类似于Java的import机制。值得说明的是**Qt中头文件和类名是一致的**。也就是说,如果你要使用某个类的话,它的类名就是它的头文件名。
第3行是空行 :)
第4行是main函数函数头。这与普通的C++程序没有什么两样学过C++的都明白。因此你可以看到实际上Qt完全通过普通的main函数进入这不同于wxWidgets因为wxWidgets的Hello, world需要你继承它的一个wxApp类并覆盖它的wxApp::OnInit方法系统会自动将OnInit编译成入口函数。不过在Qt中就不需要这些了。
第5行噢噢大括号…
第6行创建一个QApplication对象。这个对象用于__管理应用程序级别的资源__。QApplication的构造函数要求两个参数分别来自main的那两个参数因此Qt在一定程度上是支持命令行参数的。
第7行创建一个QLabel对象并且能够显示Hello, world!字符串。和其他库的Label控件一样这是用来显示文本的。在Qt中这被称为一个widget(翻译出来是小东西,不过这个翻译并不好…)它等同于Windows技术里面的控件(controls)和容器(containers)。也就是说widget可以放置其他的widget就像Swing的组件。大多数Qt程序使用__QMainWindow或者QDialog作为顶级组件__但Qt并不强制要求这点。在这个例子中顶级组件就是一个QLabel。
第8行使这个label可见。组件创建出来之后通常是__不可见__的要求我们手动的使它们可见。这样在创建出组建之后我们就可以对它们进行各种定制以避免出现之后在屏幕上面会有闪烁。
第9行将应用程序的控制权移交给Qt。这时程序的__事件循环__就开始了也就是说这时可以相应你发出的各种事件了。这类似于gtk+最后的一行gtk_main()。
第10行大括号……程序结束了。
注意我们并没有使用delete去删除创建的QLabel因为在程序结束后操作系统会回收这个空间——这只是因为这个QLabel占用的内存比较小但有时候这么做会引起麻烦的特别是在大程序中因此必须小心。
好了程序解释完了。按照正常的流程下面应该编译。前面也提过Qt的编译不能使用普通的make而必须先使用__qmake__进行预编译。所以第一步应该是在工程目录下使用
qmake -project
命令创建.pro文件(比如说是叫helloworld.pro)。然后再在.pro文件目录下使用
qmake helloworld.pro (make)
或者
qmake -tp vc helloworld.pro (nmake)
生成makefile然后才能调用make或者是nmake进行编译。不过因为我们使用的是IDE所以这些步骤就不需要我们手动完成了。
值得说明一点的是这个qmake能够生成标准的makefile文件因此完全可以利用qmake自动生成makefile——这是题外话。
好了下面修改一下源代码把QLabel的创建一句改成
QLabel *label = new QLabel("<h2><font color='red'>Hello</font>, world!<h2>");
运行一下:
同Swing的JLabel一样__Qt也是支持HTML解析__的。
好了这个Hello, world就说到这里明确一下Qt的程序结构在一个Qt源代码中以下两条语句是必不可少的
QApplication app(argc, argv);
//...
return app.exec();

View File

@@ -0,0 +1,38 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T13:36:10+08:00
====== Qt学习之路(4):初探信号槽 ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/194442
看过了简单的Hello, world! 之后下面来看看Qt最引以为豪的信号槽机制
所谓信号槽,简单来说,就像是插销一样:一个插头和一个插座。怎么说呢?当某种事件发生之后,比如,点击了一下鼠标,或者按了某个按键,这时,这个**组件**就会发出一个信号。就像是广播一样,如果有了事件,它就漫天发声。这时,如果有一个槽,正好对应上这个信号,那么,这个**槽函数**就会执行,也就是回调。就像广播发出了,如果你感兴趣,那么你就会对这个广播有反应。干巴巴的解释很无力,还是看代码:
#include <QtGui/QApplication>
#include <QtGui/QPushButton>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPushButton *button = new QPushButton("Quit");
QObject::connect(button, SIGNAL(clicked()), __&a__, SLOT(quit()));
button->show();
return a.exec();
}
这是在Qt Creator上面新建的文件因为前面已经详细的说明怎么新建工程所以这里就不再赘述了。这个程序很简单只有一个按钮点击之后程序退出。(顺便说一句Qt里面的button被叫做__QPushButton__真搞不明白为什么一个简单的button非得加上push呢呵呵)
主要是看这一句:
QObject::connect(button, SIGNAL(clicked()), &a, SLOT(quit()));
__QObject__是所有类的根。Qt使用这个QObject实现了一个__单根继承的C++__。它里面有一个__connect静态函数__用于连接信号槽。
当一个按钮被点击时它会发出一个clicked信号意思是向周围的组件们声明我被点击啦当然其它很多组件都懒得理他。如果对它感兴趣就告诉QObject说你帮我盯着点只要button发出clicked信号你就告诉我——想了想之后算了你也别告诉我了直接去执行我的某某某函数吧就这样一个信号槽就形成了。具体来说呢这个例子就是QApplication的实例a说如果button发出了clicked信号你就去执行我的quit函数。所以当我们点击button的时候__a的quit__函数被调用程序退出了。所以在这里clicked()就是一个信号而quit()就是槽,形象地说就是把这个信号插进这个槽里面去。
Qt使用信号槽机制完成了__事件监听__操作。这类似与Swing里面的listener机制只是要比这个listener简单得多。以后我们会看到这种信号槽的定义也异常的简单。值得注意的是这个信号槽机制仅仅是使用的QObject的connect函数其他并没有什么耦合——也就是说完全可以利用这种机制实现你自己的信号监听不过这就需要使用qmake预处理一下了
细心的你或许发现在Qt Creator里面SIGNAL和SLOT竟然变颜色了没错Qt确实把它们当成了关键字实际上Qt正是利用它们扩展了C++语言因此才需要使用qmake进行预处理比便使普通的C++编译器能够顺利编译。另外这里的signal和Unix系统里面的signal没有任何的关系哦哦有一点关系那就是名字是一样的

View File

@@ -0,0 +1,80 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T13:40:14+08:00
====== Qt学习之路(5):组件布局 ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/194616
同Swing类似Qt也提供了几种__组件定位__的技术。其中就包括__绝对定位和布局定位__。
顾名思义绝对定位就是使用最原始的定位方法给出这个组件的__坐标和长宽值__。这样Qt就知道该把组件放在哪里以及怎么设置组件的大小了。但是这样做的一个问题是如果用户改变了**窗口大小**比如点击了最大化或者拖动窗口边缘这时你就要自己编写相应的函数来响应这些变化以避免那些组件还只是静静地呆在一个角落。或者更简单的方法是直接__禁止用户改变大小__。
不过Qt提供了另外的一种机制就是布局来解决这个问题。你只要把**组件放入某一种布局**之中当需要调整大小或者位置的时候Qt就知道该怎样进行调整。这类似于Swing的布局管理器不过Qt的布局没有那么多只有有限的几个。
来看一下下面的例子:
#include <QtGui/QApplication>
#include <QtGui/QWidget>
#include <QtGui/QSpinBox>
#include <QtGui/QSlider>
#include <QtGui/QHBoxLayout>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget *window = new QWidget;
window->setWindowTitle("Enter your age");
QSpinBox *spinBox = new QSpinBox;
QSlider *slider = new QSlider(Qt::Horizontal);
spinBox->setRange(0, 130);
slider->setRange(0, 130);
QObject::connect(slider, SIGNAL(valueChanged(int)), spinBox, SLOT(setValue(int)));
QObject::connect(spinBox, SIGNAL(valueChanged(int)), slider, SLOT(setValue(int)));
spinBox->setValue(35);
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(spinBox);
layout->addWidget(slider);
window->setLayout(layout);
window->show();
return app.exec();
}
这里使用了两个新的组件QSpinBox和QSlider以及一个新的__顶级窗口__QWidget。QSpinBox是一个有上下箭头的微调器QSlider是一个滑动杆只要运行一下就会明白到底是什么东西了。
代码并不是那么难懂还是来简单的看一下。首先创建了一个QWidget的实例调用setWindowTitle函数来设置窗口标题。然后创建了一个QSpinBox和QSlider分别设置了它们值的范围使用的是setRange函数。然后进行信号槽的链接。这点后面再详细说明。然后是一个QHBoxLayout就是一个水平布局按照从左到右的顺序进行添加使用addWidget添加好组件后调用QWidget的setLayout把QWidget的layout设置为我们定义的这个Layout这样程序就完成了
编译运行一下,可以看到效果:
{{~/sync/notes/zim/Programme/Qt学习之路/Qt学习之路(5):组件布局/1.png}}
如果最大化的话:
{{~/sync/notes/zim/Programme/Qt学习之路/Qt学习之路(5):组件布局/2.png}}
虽然我并没有添加任何代码但是那个layout就已经明白该怎样进行布局。
或许你发现那两个信号槽的链接操作会不会产生无限递归因为steValue就会引发valueChanged信号答案是不会。这两句语句实现了当spinBox发出valueChanged信号的时候会回调slider的setValue以更新slider的值而slider发出valueChanged信号的时候又会回调slider的setValue。但是如果新的value和旧的value是__一样__的话是不会发出这个信号的因此避免了无限递归。
迷糊了吧举个例子看。比如下面的spinBox->setValue(35)执行的时候首先spinBox会将自己的值设为35这样它的值与原来的不一样了(在没有setValue之前的时候默认值是0)于是它发出了valueChanged信号。slider接收到这个信号于是回调自己的setValue函数将它的值也设置成35它也发出了valueChanged信号。当然此时spinBox又收到了不过它发现这个35和它本身的值是一样的于是它就不发出信号所以信号传递就停止了。
那么你会问它们是怎么知道值的呢答案很简单因为你的信号和槽都接受了一个int参数新的值就是通过这个进行传递的。实际上我们利用Qt的信号槽机制完成了一个__数据绑定__使两个组件或者更多组件的状态能够__同步变化__。
Qt一共有三种主要的layout分别是
QHBoxLayout- 按照水平方向从左到右布局;
QVBoxLayout- 按照竖直方向从上到下布局;
QGridLayout- 在一个__网格__中进行布局类似于HTML的table。
layout使用addWidget添加组件使用addLayout可以添加__子布局__因此这就有了无穷无尽的组合方式。
我是在Windows上面进行编译的如果你要是在其他平台上面应用程序就会有不同的样子
{{~/sync/notes/zim/Programme/Qt学习之路/Qt学习之路(5):组件布局/3.png}}
还记得前面曾经说过Qt不是使用的原生组件而是自己绘制模拟的本地组件的样子不过看看这个截图它模拟的不能说百分百一致也可说是惟妙惟肖了… :)

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

View File

@@ -0,0 +1,19 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T13:48:33+08:00
====== Qt学习之路(6)API文档的使用 ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/198078
今天来说一下有关Qt API文档的使用。因为Qt有一个商业版本因此它的__文档十分健全__而且编写良好。对于开发者来说查看文档时开发必修课之一——没有人能够记住那么多API的使用
在Qt中查看文档是一件很简单的事情。如果你使用QtCreator那么左侧的Help按钮就是文档查看入口。否则的话你可以在Qt的安装目录下的bin里面的assistant.exe中看到Qt的文档。在早期版本中Qt的文档曾以HTML格式发布不过在2009.03版中我没有找到HTML格式的文档可能Qt已经把它全部换成二进制格式的了吧——当然如果你全部安装了Qt的组件是可以在开始菜单中找到assistant的
assistant里面的文档有很多项
其中,第一个是帮助的帮助:-)第二个是Qt Designer的帮助第三个是Qt Linguist的帮助第四个是QMake的帮助最后一个是Qt的API文档在QtCreator中默认打开的就是这部分。
不过,关于文档的内容这里实在不好阐述,因为整个文档太大了,我也并没有看过多少,很多时候都是随用随查,就好像是字典一样——谁也不会天天没事抱着本字典去看不是?还有就是这里的文档都是英文的,不过如果是做开发的话,了解一些英文还是很有帮助的,不是吗?

View File

@@ -0,0 +1,69 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T13:50:26+08:00
====== Qt学习之路(7)创建一个对话框(上) ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/198971
首先说明一点在C++ GUI Programming with Qt4, 2nd中这一章连同以后的若干章一起完成了一个比较完整的程序——一个模仿Excel的电子表格。不过这个程序挺大的而且书中也没有给出完整的源代码只是分段分段的——我不喜欢这个样子我想要看到我写出来的是什么东西这是最主要的而不是慢慢的过上几章的内容才能看到自己的作品。所以我打算换一种方式每章只给出简单的知识但是每章都能够运行出东西来。好了扯完了下面开始
以前说的主要是一些基础知识,现在我们来真正做一个东西——一个查找对话框。什么?什么叫查找对话框?唉唉,先看看我们的最终作品吧!
{{~/sync/notes/zim/Programme/Qt学习之路/Qt学习之路(7)创建一个对话框(上)/1.png}}
好了首先新建一个工程就叫FindDialog吧当然还是Qt Gui Application然后最后一步注意Base Dialog选择__QDialog__而不是默认的QMainWindow因为我们要学习建立对话框嘛名字随便起不过我就叫finddialog啦Ganarate form还是不要的。然后Finish就好了。
打开finddialog.h开始编写头文件。
#ifndef FINDDIALOG_H
#define FINDDIALOG_H
#include <QtGui/QDialog>
class QCheckBox;
class QLabel;
class QLineEdit;
class QPushButton;
class FindDialog : public QDialog
{
Q_OBJECT
public:
FindDialog(QWidget *parent = 0);
~FindDialog();
signals:
void findNext(const QString &str, Qt::CaseSensitivity cs);
void findPrevious(const QString &str, Qt::CaseSensitivity cs);
private slots:
void findClicked();
void enableFindButton(const QString &text);
private:
QLabel *label;
QLineEdit *lineEdit;
QCheckBox *caseCheckBox;
QCheckBox *backwardCheckBox;
QPushButton *findButton;
QPushButton *closeButton;
};
#endif // FINDDIALOG_H
大家都是懂得C++的啊,所以什么#ifndef#define和#endif的含义和用途就不再赘述了。
首先声明四个用到的类。这里做的是前__向声明__否则的话是编译不过的因为编译器不知道这些类是否存在。简单来说所谓前向声明就是告诉编译器我要用这几个类而且这几个类存在你就不要担心它们存不存在的问题啦
然后是我们的FindDialog继承自QDialog。
下面是一个重要的东西__Q_OBJECT__。这是一个宏。凡是定义信号槽的类都必须声明这个宏。至于为什么我们以后再说。
然后是public的构造函数和析构函数声明。
然后是一个signal:这是Qt的关键字——还记得前面说过的嘛Qt扩展了C++语言因此它有自己的关键字——这是对信号的定义也就是说FindDialog有两个public的信号它可以在特定的时刻发出这两个信号就这里来说如果用户点击了Find按钮并且选中了Search backward就会发出findPrevious()否则发出findNext()。
紧接着是private slots:的定义和前面的signal一样这是私有的槽的定义。也就是说FindDialog具有两个槽可以接收某些信号不过这两个槽都是私有的。
为了slots的定义我们需要访问FindDialog的组件因此我们把其中的__组件定义为成员变量__以便访问。正是因为需要定义这些组件才需要对它们的类型进行前向声明。因为我们仅仅使用的是指针并不涉及到这些类的函数因此并不需要include它们的头文件——当然你想直接引入头文件也可以不过那样的话编译速度就会慢一些。
好了,头文件先说这些,下一篇再说源代码啦!休息,休息一下!

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,120 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T14:35:02+08:00
====== Qt学习之路(8) 创建一个对话框(下) ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/198988
接着前一篇,下面是源代码部分:
#include <QtGui>
#include "finddialog.h"
FindDialog::FindDialog(QWidget *parent)
: QDialog(parent)
{
label = new QLabel(tr("Find &what:"));
lineEdit = new QLineEdit;
label->__setBuddy__(lineEdit);
caseCheckBox = new QCheckBox(tr("Match &case"));
backwardCheckBox = new QCheckBox(tr("Search &backford"));
findButton = new QPushButton(tr("&Find"));
findButton->__setDefault__(true);
findButton->__setEnabled__(false);
closeButton = new QPushButton(tr("Close"));
connect(lineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(enableFindButton(const QString&)));
connect(findButton, SIGNAL(clicked()), __this__, SLOT(__findClicked()__));
connect(closeButton, SIGNAL(clicked()), this, SLOT(close()));
QHBoxLayout *__top__//LeftLayout// = new QHBoxLayout;
topLeftLayout->addWidget(label);
topLeftLayout->addWidget(lineEdit);
QVBoxLayout *leftLayout = new QVBoxLayout;
leftLayout->__addLayout__(topLeftLayout);
leftLayout->addWidget(caseCheckBox);
leftLayout->addWidget(backwardCheckBox);
QVBoxLayout *rightLayout = new QVBoxLayout;
rightLayout->addWidget(findButton);
rightLayout->addWidget(closeButton);
rightLayout->__addStretch__();
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addLayout(leftLayout);
mainLayout->addLayout(rightLayout);
setLayout(mainLayout);
setWindowTitle(tr("Find"));
setFixedHeight(sizeHint().height());
}
FindDialog::~FindDialog()
{
}
void FindDialog::findClicked()
{
QString text = lineEdit->__text()__;
Qt::CaseSensitivity cs = caseCheckBox->__isChecked() __? Qt::CaseInsensitive : Qt::CaseSensitive;
if(backwardCheckBox->isChecked()) {
__emit__ findPrevious(text, cs);
} else {
emit findNext(text, cs);
}
}
void FindDialog::enableFindButton(const QString &text)
{
findButton->__setEnabled__(!text.isEmpty());
}
CPP文件要长一些哦——不过它们的价钱也会更高嘿嘿——嗯来看代码第一行include的是QtGui。Qt是分模块的记得我们建工程的时候就会问你使用哪些模块QtCoreQtGuiQtXml等等。这里我们引入QtGui它包括了QtCore和QtGui模块。不过这并不是最好的做法因为QtGui文件很大包括了GUI的__所有组件__但是很多组件我们根本是用不到的——就像Swing的import你可以import到类也可以使用*,不过都不会建议使用*这里也是一样的。我们最好只引入需要的组件。不过那样会把文件变长现在就先用QtGui啦只要记得正式开发时不能这么用就好啦
构造函数有参数初始化列表用来调用父类的构造函数相当于Java里面的super()函数。这是C++的相关知识不是Qt发明的这里不再赘述。
然后新建一个QLabel。还记得前面的Hello, world!里面也使用过QLabel吗那时候只是简单的传入一个字符串啊这里怎么是一个函数tr()函数tr()全名是__QObject::tr()__被它处理的字符串可以使用工具提取出来翻译成其他语言也就是做国际化使用。这以后还会仔细讲解只要记住Qt的最佳实践如果你想让你的程序国际化的话那么所有用户可见的字符串都要使用QObject::tr()但是为什么我们没有写QObject::tr()而仅仅是tr()呢原来tr()函数是定义在Object里面的所有使用了Q_OBJECT宏的类都自动具有tr()函数。
字符串中的&代表快捷键。注意看下面的findButton的&Find它会生成Find字符串当你按下Alt+F的时候这个按钮就相当于被点击——这么说很难受相信大家都明白什么意思。同样前面label里面也有一个&因此它的快捷键就是Alt+W。不过这个label使用了setBuddy函数它的意思是当label获得焦点时比如按下Alt+W它的__焦点会自动传给__它的buddy也就是lineEdit。看这就是伙伴的含义(buddy英文就是伙伴的意思)。
后面几行就比较简单了创建了两个QCheckBox把默认的按钮设为findButton把findButton设为不可用——也就是变成灰色的了。
再下面是三个connect语句用来连接信号槽。可以看到当lineEdit发出textChanged(const QString&)信号时FindDialog的enableFindButton(const QString&)函数会被调用——这就是回调是有__系统自动调用__而不是你去调用——当findButton发出clicked()信号时FindDialog的findClicked()函数会被调用当closeButton发出clicked()信号时FindDialog的close()函数会被调用。注意connect()函数也是QObject的因为我们继承了QObject所以能够直接使用。
后面的很多行语句都是layout的使用虽然很复杂但是很清晰——编写layout布局最重要一点就是__思路清楚__想清楚哪个套哪个就会很好编写。这里我们的对话框实际上是这个样子的
{{~/sync/notes/zim/Programme/Qt学习之路/Qt学习之路(8)_创建一个对话框(下)/1.png}}
注意那个spacer是由rightLayout的addStretch()添加的,就像弹簧一样,把上面的组件“顶起来”。
最后的setWindowTitle()就是设置对话框的标题而setFixedHeight()是设置成固定的高度其参数值sizeHint()返回“最理想”的大小这里我们使用的是height()函数去到“最理想”的高度。
好了下面该编写槽了——虽然说是slot但实际上它就是__普通的函数__既可以和其他函数一样使用又可以被系统回调。
先看findClicked()函数。首先取出lineEdit的输入值然后判断caseCheckBox是不是选中如果选中就返回Qt::CaseInsensitive否则返回Qt::CaseSensitive用于判断是不是大小写敏感的查找最后如果backwardCheckBox被选中就emit(发出)信号findPrevious()否则emit信号findNext。
enableFindButton()则根据lineEdit的内容是不是变化——这是我们的connect连接的——来设置findButton是不是可以使用这个很简单不再说了。
这样FindDialog.cpp也就完成了。下面编写main.cpp——其实QtCreator已经替我们完成了——
#include <QApplication>
#include "finddialog.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
FindDialog *dialog = new FindDialog;
dialog->show();
return app.exec();
}
运行一下看看我们的成果吧!
虽然很简单也没有什么实质性的功能但是我们已经能够制作对话框了——Qt的组件成百上千不可能全部介绍完只能用到什么学什么更重要的是我们已经了解了其编写思路否则的话即便是你拿着全世界所有的砖瓦没有设计图纸你也不知道怎么把它们组合成高楼大厦啊

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,97 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T15:45:11+08:00
====== Qt学习之路(9):深入了解信号槽 ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/199461
信号槽机制是Qt编程的基础。通过信号槽能够使Qt各组件在不知道对方的情形下能够相互通讯。这就将类之间的关系做了最大程度的解耦。
槽函数和普通的C++成员函数没有很大的区别。它们也可以使virtual的可以被重写可以使public、protected或者private的可以由其它的C++函数调用;参数可以是任何类型的。如果要说区别,那就是,槽函数可以和一个信号相连接,当这个信号发生时,它可以被自动调用。
connect()语句的原型类似于:
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
这里sender和receiver都是QObject类型的singal和slot都是没有参数名称的函数签名。SINGAL()和SLOT()宏用于把参数转换成字符串。
深入的说,信号槽还有更多可能的用法,如下所示。
一个信号可以和多个槽相连:
connect(slider, SIGNAL(valueChanged(int)),
spinBox, SLOT(setValue(int)));
connect(slider, SIGNAL(valueChanged(int)),
this, SLOT(updateStatusBarIndicator(int)));
注意,如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。
多个信号可以连接到一个槽:
connect(lcd, SIGNAL(overflow()),
this, SLOT(handleMathError()));
connect(calculator, SIGNAL(divisionByZero()),
this, SLOT(handleMathError()));
这是说,只要任意一个信号发出,这个槽就会被调用。
一个信号可以连接到另外的一个信号:
connect(lineEdit, SIGNAL(textChanged(const QString &)),
this, SIGNAL(updateRecord(const QString &)));
这是说,当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。
槽可以被取消链接:
disconnect(lcd, SIGNAL(overflow()),
this, SLOT(handleMathError()));
这种情况并不经常出现因为当一个对象delete之后Qt自动取消所有连接到这个对象上面的槽。
为了正确的连接信号槽,信号和槽的参数个数、类型以及出现的顺序都必须相同,例如:
connect(ftp, SIGNAL(rawCommandReply(int, const QString &)),
this, SLOT(processReply(int, const QString &)));
这里有一种例外情况,如果信号的参数多于槽的参数,那么这个参数之后的那些参数都会被忽略掉,例如:
connect(ftp, SIGNAL(rawCommandReply(int, const QString &)),
this, SLOT(checkErrorCode(int)));
这里const QString &这个参数就会被槽忽略掉。
如果信号槽的参数不相容或者是信号或槽有一个不存在或者在信号槽的连接中出现了参数名字在Debug模式下编译的时候Qt都会很智能的给出警告。
在这之前我们仅仅在widgets中使用到了信号槽但是注意到connect()函数其实是在QObject中实现的并不局限于GUI因此只要我们继承QObject类就可以使用信号槽机制了
class Employee : public QObject
{
Q_OBJECT
public:
Employee() { mySalary = 0; }
int salary() const { return mySalary; }
public slots:
void setSalary(int newSalary);
signals:
void salaryChanged(int newSalary);
private:
int mySalary;
};
在使用时,我们给出下面的代码:
void Employee::setSalary(int newSalary)
{
__ if __(newSalary != mySalary) {
mySalary = newSalary;
emit salaryChanged(mySalary);
}
}
这样当setSalary()调用的时候就会发出salaryChanged()信号。注意这里的if判断这是避免递归的方式还记得前面提到的循环连接吗如果没有if当出现了循环连接的时候就会产生无限递归。

View File

@@ -0,0 +1,16 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T21:21:51+08:00
====== Qt学习之路(tip)parent参数 ======
Created Monday 31 October 2011
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/214166
这是一篇很简单的文章仅仅是用来说明一下一个参数的作用因此我把它写成了tip而不是接下来的17.
程序写的多了你会发现几乎所有的Qt类的构造函数都会有一个parent参数。这个参数通常是**QObject* **或者是 **QWidget* **类型的。很多情况下它都会有一个初始值0因此即便你不去给它复制也没有丝毫的问题。于是稍微偷懒一下就会不自觉的忽略了这个参数。那么这个参数到底是干什么用的呢
其实这个参数有很多用处。就像它的名字一样这个参数指定了组件的__父组件__。对于一个对话框来说对话框一般是不作为顶层容器出现的因此在任务栏上一般是没有对话框的位置的。怎么指定这个对话框不是顶层容器呢有父组件的组件不就不是顶层容器了吗因此只要你指定对话框的parent属性__任务栏__就不会出现它的身影。当然如果你不指定这个对话框就成为__顶层容器__了任务栏会给它留个位置的——利用这个特性就可以实现特殊对话框可以在任务栏出现的效果比如“关于”对话框的出现。
另外比较通用也是很重要的作用是parent参数指明了组件的父组件这样当父组件delete时Qt可以保证所有子组件——也就是parent指针指向这个组件的所有组件——都会__被正确的delete掉__。这是Qt能够帮助我们管理一部分内存的原因所在。Qt是通过遍历parent属性来防止了这一部分内存泄漏的。因此必要情况下还是不要忘记设置这个parent属性。当然如果你不声明这个属性当整个程序关闭时操作系统会回收内存——因此我们所说的内存泄漏一般是指我们自己写的应用程序的内部而不会影响到整个操作系统——当然如果你实现太可恶操作系统也会受不了自动关掉你的程序的:-)

View File

@@ -0,0 +1,29 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-11-04T22:45:22+08:00
====== 浅析QT中的坐标系统 ======
Created Friday 04 November 2011
QT窗口部件的左上角的位置是0,0,右下角的位置是(width()-1,height()-1),这跟我们数学中的所学的坐标系统是不一样的。
在使用QPAINTER绘图时 视口VIEWPORT,窗口(WINDOW---不是顶级窗口部件)和世界矩阵决定了QPAINTER逻辑坐标和绘制设备的物理坐标之间的映射关系。在默认的情况下逻辑的和物理的坐标系统被设置为**一致大小**的。
视口和窗口是紧密绑定的。视口是物理坐标的任意矩形。窗口指定了相同的矩形但是在物理坐标中它的坐标和物理坐标是__线性的关__系的这点是很重要的对理解后面的窗口视口机制很有用。在默认的情况下视口和窗口都被设置为设备的矩形。例如如果设备是320*320的矩形视口和窗口都是左是角为00的320*320的相同矩形。在这种情况下逻辑坐标和物理坐标就是一致的。
那么视口---窗口机制有什么作用呢?
视口窗口机制对于我们编写__独立于绘制设备大小和分辨率__的代码是十分有用的我们可以自己来进行逻辑坐标和物理坐标之间的映射计算但是让QPAINTER来完成这项工作是很简单的。例如我们想让逻辑坐标从-50-505050并且00在中间我们可以这样来设置我们的窗口
painter.setWindow(QRect(-50,-50,100,100));
(-50-50指定了原点100100指定了宽和高。这也就是说现在的逻辑坐标-50-50和物理坐标00是对应的并且逻辑坐标5050和物理坐标320200)是对应的。
那么painter是怎么计算的呢
这就要注意到一句话:逻辑坐标和物理坐标是成线性关系的。因为**物理坐标是和设备的分辨率**有关的。对于这个设备它的分辨率应该是320*200的。那么应该如何计算逻辑坐标和物理坐标的映射关系呢 我们来交叉对应坐标对于X有-50050320.
所以对于X来说逻辑坐标和物理坐标的映射关系就是**b=(16/5)a+160**.
对于Y有-50050200
所以对于Y来说逻辑坐标和物理坐标的映射关系就是**b=2a+100**
根据上面的2个公式我们任意给出**逻辑坐标中的一个点就可以求出对应的物理坐标中的点**。
例如逻辑坐标(-30-20对应于物理坐标就是6460逻辑坐标1020对应于物理坐标就是192140

View File

@@ -0,0 +1,36 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-11-21T11:39:10+08:00
====== 内置的widget和对话框类 ======
Created Monday 21 November 2011
按钮: QPushButton, QToolButton, QCheckBox, QRadioButton
单页容器: QGroupBox, QFrame
多页容器: QTabWidget, QToolBox
显示条目: QListView, QTreeView, QTabView
显示: QLabel, QLCDNumber, QProgress, QTextBrowser
输入: QSpinBox, QDoubleSpinBox, QComboBox, QDateEdit, QTimeEdit, QDateTimeEdit, QScrollBar, QSlider, QTextEdit, QLineEdit, QDial
反馈对话框: QInputDialog, QProgressDialog, QMessageBox, QErrorMessage
颜色和字体对话框: QColorDialog, QFontDialog
文件和打印对话框: QPageSetupDialog, QFileDialog, QPrintDialog
滚动条QScrollBar的基类为 QAbstractScrollArea
Qt提供富文本(rich text), 支持多格式文本
QLabel支持纯文本, HTML, 图像
QTextBrowser为只读QTextEdit, 可支持带格式文本, 相对于QLabel, 可以用于显示大量的文本内容, 提供滚动条, 键盘和鼠标可以控制浏览.
QLineEditor支持validator, QTextEditor为QAbstractScrollArea的派生类, 可以输入大量的文本. 可以设置输入纯文本还是富文本(rich text)
QLineEditor和QTextEditor都和剪贴板相关联。

View File

@@ -0,0 +1,105 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-02-23T18:44:04+08:00
====== 大道至简 Qt Quick深度解析 ======
Created Thursday 23 February 2012
http://sd.csdn.net/a/20111213/309070.html?bsh_bid=75798262
12月12-13日由全球最大中文IT社区CSDN和诺基亚联合举办的2011中国Qt开发者大会在北京万达索菲特大饭店隆重举行。本次大会拥有强大的专家阵容并聚集众多应用开发精英共同探讨Qt应用开发之道、体验多样化的Qt开发方式。大会不仅包含了Qt在移动开发方面的新特性介绍同时还包含培训、主题研讨会、案例研究和合作伙伴服务等丰富内容以满足各类开发人员的特定需求帮助广大开发者更好地了解Qt应用开发之道。
在12月13日的Qt开发者大会下午的Qt高级应用会场来自诺基亚布里斯班分部的专业支持工程师刘峥带来了题为“Qt Quick深度解析”的精彩演讲。
作为资深的Qt Quick专家刘峥认为Qt Quick是一门**大道至简的开发技术**而其中的QML语言更是适合所有开发者使用再加上丰富的QtDeclarative模块令开发程序成为一个__简便的、高效的过程享受__。除了对Qt Quick的开发特性进行详细讲解外刘峥还对QML、JS与C的混合编程进行了讲解介绍了Qt Quick跨平台的支持性能。
刘峥带来了Qt一个最新的技术Qt3D。Qt3D还未正式推出但它在3D方面的精彩表现值得开发者关注。Qt3D现在已经包括如下内容开发工具集成、模型分场引用、骨髓动画、渲染到纹理FBO、三维路径动画、物理引擎集成、三维输入设备支持。现场演示的Qt3D小游戏获得与会者热烈掌声刘峥介绍Qt3D技术将令开发者在开发3D应用程序时能更轻松高效。
除了Qt Quick在技术方面的优势外刘峥还对Qt Quick的商业价值与未来作了介绍他表示Qt Quick是__诺基亚核心战略组成部分__它拥有强大的开发和测试团队并会进行持续更新和支持对开发者提交的应用程序诺基亚将进行强有力的营销推广。Qt目前已拥有广泛成熟的社区群体并且Qt会一直坚持完全开源及开放式管理模式。
===== 下面是演讲嘉宾刘峥的演讲实录: =====
==== Qt Quick简介与案例展示 ====
我讲的题目是Qt Quick的深度解析我想问问在座各位同胞你们有多少人已经用过Qt Quick或者想用Qt Quick帮忙把手举一下还真不少人我这压力又有点大了。其实Qt Quick它本身是一个很年轻的产品它发布到现在其实只有一年左右的时间我们也是一边学着一边用如果我有讲的不对大家多包含我多指点咱们共同学习共同进步。
首先说起Qt Quick我们第一个想法就是Qt Quick到底是一个什么样的产品它到底是做什么用的我们说Qt Quick从名字上来讲它一部分是QT一部分是QuickQt 是什么呢是它之上建立的产品Quick是给我们带来一些便利让我们开发程序更快。
具体Qt Quick是什么呢我先给大家讲一点历史Qt Quick来自于我们一次演示我们的工程师有一次参加某一个大会然后回来就跟我们说说我在大会上看到了一个卖水果的公司开发了个手机效果真是不错我们这些做Qt Quick的人非常羡慕后来说我们这些人也不比人家差为什么我们不能开发一个类似的东西呢于是我们有了一个新的项目,让开发者可以方便的调用,让你的程序变成带有动画、带有过场的漂亮界面。后来又做了脚本层,让大家通过脚本更方便创建动画效果和界面,**自从有了QMLQt Quick就诞生了**。
__QML是前端的解释语言__通过解释器就是C++的基础实现,然后我们整合到屏幕上,如果基础实现不够好,可以通过**第三方C++插件**可以扩展,这样就可以得到你所需要的功能了。
可能大家听完了以后还是一头雾水到底Qt Quick擅长什么长什么样我给大家几个模型给大家看一下。首先这是一个翻牌的效果常做C++的人应该知道这是一个状态转换的小程序这个代码非常简单然后这个是略微复杂一点的你看这个就是我们即时贴可以往里面挑字可以移动可以有多页有很漂亮的动画效果。这个程序说是复杂也不过是一两百行从这个例子可以看出Qt Quick是什么产品呢__实际上它是描述界面的一种解释型语言__然后它可以实现界面之间的平滑转换可以增加动画效果可以增加过场效果它可以让你拥有一个非常漂亮、光滑像行云流水一般的界面然后在里面你又可以__嵌入可以做一些简单的逻辑计算__。Qt Quick最擅长做界面。它不擅长做计算密集型需要**非常复杂逻辑**的那种程序。它和FLASH不一样FLASH是一点一线画出来的动画__QML是原始是一个图片__这样方便了程序员虽然限制了你一点自由但是整体上非常便利至于程序效率问题我们可以通过用C++插件里实现。
===== Qt Quick的五大优点 =====
下面我就说到Qt Quick它到底有什么特色相比于其他的类似的解决方案它有什么优点
我大概总结了一下这些优点可以用五个汉字来描述。这五个汉字就是速、美、强、广、帅速就是说它虽然是一种解释型的语言但是开发起来非常快运行起来也非常快。美就是你用这个程序的时候你非常自由几乎没有什么限制用很简单的代码可以作出漂亮的图画和以往的C++不一样任何状态你都可以最终连续、带动画、非常漂亮的界面。至于强就是说它有非常大的扩展能力它可以跟其他语言共同工作。广就是说它支持各种各样的平台在平台上有很好的加持。帅指的是3D就是我们组做的那个。本来我做这个想说Qt Quick我想给大家讲QT 3D的东西。
接下来我们以这五个字为主线我们来说一下Qt Quick到底速、美、强、广、帅在哪儿
首先说这个__速__字我们说的是运行时的速它对你开发能提供什么样的加速我们知道开发的成本分成两部分一部分是你学习的__工具__体验这个供给的成本另一部分是__使用__这个东西本身的成本。QML是一个非常简单的大概有十几种元素我觉得QML是我见过__最简单的脚本语言之一__本来它是声明式语言它这个东西把复杂的东西全都隐藏起来给用户看到的QML部分是最简单的你看过一个例子你马上就能模仿出这个功能比如说咱举一个例子举一个HTML5非常复杂。而QML非常简单这一点我可以保证。另外QML这个工具非常__适合员工开发__。现在做员工开发最难的是什么就是画界面虽然有各种各样的画界面的东西但是用起来并不顺手。QML是一个一体化的解决方案你所有的逻辑、界面包括设置全都写在QML里面所以你一个文件里面包含了你所有需要的东西你开发出一个原形所以他非常适合员工开发如果你再学到一些产品就是最终产品。如果你仅用QML进行开发可以把元旦打包发布然后在QML端运行。如果有C++参与其中你可以用我们Qt Quick给你进行打包可以发布到桌面上部署和交互也都很简单。最后一个就是它可以快速修改和重构QML结果非常简单层次明显修改起来非常简单你重构完了完全不用重新设计重构的逻辑照样可以运行。你修改逻辑不影响界面你修改界面不影响逻辑。就是说__QML是一个一站式的解决方案__。
我们设计QML的时候你可以在里面找到各种设计模式的影子。这种东西我建议没有用QML的人赶快试一下看看对你的速度有多大的提高。接下来我们再说一下开发环境和工具的支持。QML虽然好但是也要有好的开发环境支持比如说HTML5大家都说它好但是现在还没有一个好的编辑器械但是QML不一样我们有Qt Quick而且它内部的支持比如说__网络透明__QML你不用写任何的代码自动可以到网上下代码它可以__支持XML__支持你用一句话就可以得到SOFT支持然后它又__支持多线程__用它以后可以好几个线程同时运行这样你的永远可以点击的。还有你可以在QML里面弄__一个小的数据库__而且支持国际化支持下载字体更重要它还__支持QT集成__。另外从开发支持方面你可以在设备上调试QML包括QML一些功能你都可以在里面实现。部署更不用说了Qt Quick可以直接发送上去其实这个是相当容易的你如果熟悉了一点都不难。
总而言之QML或者说Qt Quick它是一个博采众长大道至简的一套程序。有一次我跟一个朋友谈起来我们用的Qt 、HTML5、C++你如果把它比喻成音乐你觉得它是什么类型的音乐我那个朋友是一位音乐家他跟说我觉得这个QT**非常的标致,一板一眼,非常有规律,有节奏,又很优美**它很像莫札特写的曲子让人感觉身心很舒畅。而QML比莫札特更进一步有一些艺术的气质像肖邦钢琴曲__你写代码的时候你感觉不是写代码而是写诗__。我说HTML5是什么呢他说HTML5想半天我觉得有点像周杰伦的歌为什么呢语法含糊说的不明确你还没听懂已经出新版本了。然后粉丝很多但是能听懂的人其实并不多。一个人耳朵里听出一个样。
接下来我们说第二个字就是“美”。美在每个人心里都有不同的定义。究竟什么样是美的呢我想了很久怎么样给大家演示一个美的界面让大家看了眼前一亮后来我想我不是这块料__因为我是一个程序员我没那么高的美学素养__我就拷贝了一些现实中比较受欢迎的这些大家理想的界面在这里我做了几个模型给大家看看我还要做一个和大家晓得互动因为我做这几个模型我想授的时候想让大家猜一下我到底用了多少含代码的功能。
首先我们看这个模仿某建筑公司项目大家猜猜用了多少行的代码支持十行请举手觉得一百行写出来请举手其实我这用了199行程序回头我放网站上大家下载看一下。我们再来看某水果公司生产的手机产品。大家猜一猜这个用了多少代码觉得比刚才那个多的请举手。大家真识货这个用了162行代码。我们再来看水果公司生产的台式电脑产品。这个相对比较简单大家能猜到它用的代码不会那么多但是我还是让大家猜一猜觉得100行程序才能实现的请举手80行实现请举手70行实现请举手我告诉大家一共用了61行程序就完成了这样一个界面。在这里就看出QML是多么简练维护61行的程序和维护一个我想写这样的界面千八百行的程序哪一个更简练已经昭然若揭。
说到这里我就得说一些题外话,编程跟艺术的关系,这个题目比较大,说实话我也不是一个文艺青年,我对艺术这些东西不太懂,但是我一直非常羡慕艺术家。我觉得艺术家设计的东西比咱程序员设计出来要漂亮的多,但是程序员为什么就成不了艺术家呢?我一直在思考这个问题。我想一个程序员要想成为艺术家有两个要素,第一个要素就是要有必要的艺术修养,第二个要素就是要有顺手的工具,我觉得现在程序员成不了艺术家问题所在就是我们的工具太不好了。有的时候大家都有这么一个经验,你**设计的时候你把软件的功能想象非常美好,结果做出来一团糟,非常难看**。我们现在怎么能解决这个工具的问题呢QML就是最好的解决方案__QML为我们提供了一种以编程方式来设计界面的这种方法__而且这种方法非常简练设计出代码非常有效所以我们现在已经解决了第二个问题我们只要提高自己的艺术素养每个程序员都能成为一个艺术家。
另外QML在美的方面提供了更多的价值最点明一个就是粒子系统再就是光影效果这是QML里面的一个小游戏。我们可以看到它消隐的时候实际上是有一个消隐的爆炸效果而且落的时候会稍微落过一点这个游戏的代码非常小大家有工夫可以看一看。这是我花五分钟写了这么一个礼花的程序这就是典型的爆炸效果这个代码可能不到50行。这个是我们另外一个演示程序就是说QML你可以在嵌入光影的着色程序写到里面作为一个字母串QML会给你进入GPO里计算给你创造比较漂亮的效果。这个是水波纹的效果这个是筒射的效果源代码非常简单。
说完了这个就说说“强”强的特点是什么呢什么叫做“强”我想扩展能力才能叫强你能涵盖所有的领域才叫做强QML不可能是这样的总有一天你会用到QML里面没有的元素这时候怎么办呢我们可以用两个方法第一个方法是可以__用QML和C++混合编程__。另外一个办法就是把C++做成一个插件或者是一个扩展加到程序例这种方法就可以同用永久的使用。先来说说QML的扩展能力扩展大概分三部分第一步是创建框架第二定制属性第三定制动作然后再就是包装插件。普通一个实例看一下受到前两位展示代码的刺激我把它画成图了叫QML曲线图因为有一个同事炒股票看到曲线图就闹心这个是最终结果。需要两个文件一个是里面的一条线CURVE另一个是STOCKS就是整个框架还有下面几个文件。
我们先看STOCKS声明在这里面我们声明了一个粒子属性可以把它压到里面底下就是读写这两个属性的数。这个就是以一条CURVE这就定义了属性和方法。我们看看APP和QML是怎么使用扩展我们的属性做出来效果就是这样就这么简单。如果你要包装成一个插件步骤比较复杂虽然是半步但是相当复杂。我们有一个简单的方法我们可以用Qt Quick如果要做成插件几乎什么都有你再进去找我刚才说的方法定制几个属性的方法就完活儿了。然后我们在来说一说QML JS与C++混合编程我们单独抽出做一个JS程序QML就可以直接调用其中的函数通过嵌入CONTEXT进入QMLC++也是透明的两部分都可以调用。混合编程最难解决通信问题在这里面完美解决了。值得一提的是他们俩还可以访问同一系统没有任何门槛如果你熟悉QT简单就像吃一个苹果。
接着我们来说广。其实这个主题没什么可说的了就是对多平台的支持刚才我两位同事已经讲了非常透彻了怎么进行编程讲的非常好比我讲的好多了。我就想强调一下我们现在支持的平台官方支持的平台包括OXX、MeeGo它在这个设备商都很好很流畅放心用吧这段是MOBILITY刚才陈啸天已经讲的非常清楚了联系人、日历、网络、服务目录、值空间邮件通信包括MIS、包括MES还有E—MAIL还有力反馈你可以操纵手机上的振动摩托。另外还要提一下MOBILITY虽然HTML5是周杰伦的歌我们还是要支持它所以我们提供了YP的插件就是你可以在MOBILITY里面做一个插件分析内容都可以做到了。另外对于各个平台来说我们做了统一风格的QT COMPONENT如果你这样用了有一种统一的美。这样也避免了你从头开始重新做按钮的工作。
最后我们说到了最感兴趣的部分就是这个帅。帅直的就是3D3D就是帅我是来自QT3D这个小组我们的老板在走之前千叮万嘱叫我一定要推广3D我说没有问题3D是每一个程序员都会喜欢的东西因为什么呢因为每一个程序员都有一个游戏梦现在3D的门槛降的非常非常低。
先来看一个程序这个程序短至26行第一行引入Qt Quick1.0第二个引入QT3D1.0放一个窗口定一个视点然后放一个ITEM3D我们再给它加一个变形程序让它绕着这个无限转下去最后这一行我们调一个3D模型这个模型是1959年生产的卡莉汽车记住这是一个26行的QML的程序。这就是最终的效果我们用26行的程序写出了一个在空中旋转的小汽车。从此你就可以看出QML有多强大QT 3D有多强大了你如果想在屏幕上画点画线更是没有问题。
我们看一下QML都有哪些功能我们总结成大概有七类。第一个它可以__画基本形状__可以画这几条线可以画多边型也可以画茶壶之类的。第二它可以__装入模型__我们支持二十到三十种模式的图形大部分主流的3D设计程序我们都可以很方便导入进来接着可以__加入动画__这个动画可以是缩放、可以是变形各种各样动画合并一起接着我们可以__增加更多的四周__可以有蓝天白云可以创造个世界创造一个舞台这样方便你创造一些像森林、观众、这样大规模数量很多的物体然后__抓取支持__而且我们还__支持光影效果__同样可以嵌入让GPO替你作出美轮美奂的效果。下面我们就看QT3D的模型。这个英文名叫《机器人小霸》这完全是用QML做出来的动画和条幅可以看到用QML可以实现一些小游戏了。另外一个游戏叫《登录月球》。这个叫《美猴王》你可以看到一群企鹅在一个猴子面前跳舞我的理解就是你要想让领导点头就要在他前面跳舞。这个是我们另外一位工程师叫朱丽叶他在3D相册的时候把他漂亮的老婆放进去了。这是一个真正的3D相册。有兴趣可以看一下QT3D的源代码。这是一个把一个茶壶压扁张开再压扁再张开这是另外一种压扁的方式。可以改变它的形状、颜色、投影这些在QT3D里现在都可以支持。
好了到现在为止我们已经说完了这五个特点不知道大家是不是对这五个特点有所认同。而我觉得Qt Quick这个产品的确称得上这五个字。另外请大家注意Qt Quick是非常非常年轻的产品它开发到现在可能就是三年左右的时间发布时间也就一年左右到现在发布1.1版,在软件业来说是比较早的,能达到这种成熟程度已经不容易了。
===== Qt Quick发展前景与商业展望 =====
Qt Quick工程师更多着眼于未来。我们就来看一下Qt Quick将来会为我们提供一些什么样的激动人心的功能即将登场的功能Qt Quick或者说QML2.0我们提供如下的功能和动画首先是完整的路径动画以后我们可以定义一个QT PAST让Qt Quick按照路径运行。因为QML2.0会和Qt Quick5.0发布Qt Quick5.0是完全模块化的系统你可以选择性的安装减少你系统的消耗会有更多的插件出现包括完整的插件和其他一些更有用的功能请大家期待。而且我们会在更多平台上发布组建库而且还有画度的曲线。另外我们还会有一个全新的引擎就是JS引擎。
我们再看QT3D提供什么功能QT3D比QML和Qt Quick更加年轻它现在连1.0都没发布它预期会和Qt Quick5.0一起发布。在1.0里我们将提供开发工具集成就是可以在Qt Quick模型分块引用我们会支持骨骼动画在里面就可以自由的棒放支持渲染到纹理。我们也会提供三维额路径动画和Qt Quick将会使屏幕有方向还会集成物理引擎有了这个东西你就可以做一个3D愤怒的小鸡我们还支持三维输入设备支持以后体感输入我们都支持。下一年如果有机会我就用体感来写这个东西。
说了这么多大家可能还是比较关心QML到底适合不适合这些功能我说的都是技术方面的东西在商业方面我不是很有发言权但是我还是要说Qt Quick现在已经成为诺基亚软件战略核心部分诺基亚自主开发的新的系统、新的设备都已经以它为中心在运转而且我们所有的团队都已经参与到Qt Quick的使用中来了而且Qt Quick本身有一个非常强大的测试和开发团队它主要是在办公室开发的但是其他办公室参与也很多我们有一批世界上一流的工程师在从事这个工作所以大家能够期待我们有持续的更新和强有力的支持所以对Qt Quick一定要有信心Qt Quick将来一定会更快更美更强更广更帅而且我们市场营销部门已经作出表率现在正在进行强有力的市场规划活动目标就是培养广泛成熟的社区群体我们有了这个社区有了这个生态系统这个产品才能真正的成功更重要的是我们还会坚持完全开源、开放式的办理所有人都能参与进来不仅仅是诺基亚的产品这也是全世界程序员共有的一笔财富。
我们都知道许多产品都号称自己是开源但是我相信大家都是名理的人大家都知道什么样是真开源什么样是假开源诺基亚是真开源每一行代码你都能看见每一行代码你觉得好都可以要求修改你的任何要求我们都会认真考虑。所以我们也需要你参与进来。对Qt Quick和QT3D提供支持我们需要你的意见和建议哪怕你骂我们一顿我们都觉得心里很舒服。
我觉得咱们中国的程序员还是太害羞了有的时候觉得这个东西不好用我们就自己去改自己去想办法不要这样以后你发现了问题要及时上报给Qt包括您有什么解决方案也可以一起发给我们。因为我是做资源控制工作我们发现一个软件的质量和工程师的素质和测试者的素质并不是有很强相关性反倒是它和使用者的数量和素质非常有关系所以我恳请大家使用Qt Quick帮我们改进这个质量。因为__使用就是最好的测试__您可以帮我们把Qt Quick做成一个更好的产品。
如果您愿意,如果您有这个能力,我们更欢迎您为我们奉献代码,因为这个是全世界、全人类的财富,我们把它开放给大家,开放式管理,就是为了让它为人类造福。而且我们现在面临软件界面更新换代走入一个新时代的时期,在这个时候我们希望你们能够参与进来共同见证这个历史,我们共同书写这场历史。
我的演讲大概就到这里。还有一句话诺基亚非常关注在中国的市场诺基亚也认为中国是诺基亚的未来QT的未来在中国Qt Quick的未来更在中国我回头把我演讲的PPT放到网上欢迎大家学习指导。
===== 与会者Q&A环节 =====
提问QML是蛮好的一个接口。不过十月份苹果发布了一个IPHONE4S里面有一个SIRI这提醒我们今后用户的接口可能就是智能了。QML有没有考虑语音的UI我在上午看到Qt Quick里面没有提到对语音UI的支持
刘峥这个问题首先我得说很多用户对SIRI出现了不理智的消费现象但是我个人认为SIRI确实是一个好东西。我们也讨论过这个问题我们做的时候就想过要不要加入语音支持我们觉得这个东西还不是很紧迫相对于其他需求来说这个东西还不是特别重复而且会带来一些很大的负担所以一开始做的时候就没有把它加入里面但是我也注意到有些开源社区做了以QT来实现类似语音输入的产品而且我们现在我觉得应该也有这个计划在做各种输入设备的支持包括三维输入设备、体感输入设备虽然我这个级别还看不到这些信息但是我估计应该有如果您觉得这个东西很好的话我建议您在我们网站上登录一条建议叫他们考虑加速这个东西的开发。
提问以前CBM里有一个多角化类似语音的软件应用现在有没有考虑在QT里面也把这个拿过来
刘峥这个具体细节我不太清楚但是这个东西和SIRI区别很大它还是一个网络的应用所以塞班那个东西继承过来很容易但是如果做就做一个更好的。因为我们有个习惯要做就做世界第一不会做第二。
提问我有一个问题是关于QML第二个问题是直接操作JPU直接显示
刘峥在QT5.0当中他们已经合二为一了。
提问:带网底层是一个接口层吗?因为从来没有听说过?
刘峥S还是在X和E上面里面接什么都可以这些能力更强。
Qt将被打造成应用开发的核心组件而目前世界各地已有超过1亿套的诺基亚Qt智能移动终端操作系统设备这意味着Qt开发者将在未来拥有更庞大的目标受众而从中获得更大利益。关注Qt关注2011中国Qt开发者大会每一个开发者都不应错过。详情请点击http://special.csdn.net/qt2011/index.html

View File

@@ -0,0 +1,103 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T19:00:46+08:00
====== 漫谈QWidget及其派生类(一) ======
Created Monday 31 October 2011
http://blog.csdn.net/dbzhang800/article/details/6737540
对于QWidget总觉得该写点什么。可又不知道具体该写些什么思路又乱又杂。简单理理简单写写就叫漫谈吧
注意:我不知道本文写完会是什么样子,可能写着写着就没下文了。也可能各部分隔很长时间。
注意本系列涉及到的东西都是Qt用户需要理解的但是我们不应该在实际项目中这么用。
===== QWidget =====
QWidget 是Qt中所有widget部件(比如QDialog、QPushButton、QLabel)的__基类__。 任何你可以通过其派生类实现的东西你都可以通过QWidget实现(只要你不怕麻烦)
比如:
QDialog有模态非模态之说 : QWidget 有没有?当然有,不要怀疑
QDialog dlg(this)是一个窗口: QWidget wgt(this)能不能是窗口?当然行
QMainWindow可以有菜单栏、工具栏等,QWidget 可不可以加?: 当然可以,只要你需要。
...
===== Window 与 Widget =====
Qt中的部件有Window和普通widget之说
Window: 窗口, Window是这样的Widget它不是其他Widget的一__部分区域__通常有标题栏等__窗口装饰器__(和是否有parent无关)
Widget: 普通部件(非窗口), 除Window外的部件
如何可以知道一个widget是否是Window注意QWidget::isWindow()
inline bool QWidget::isWindow() const
{ return (windowType() & __Qt::Window__); }
呵呵不小心将源码给出了那就继续吧Qt::Window是一个Qt::WindowFlags类型的枚举值。
windowType() 是什么东西?为了避免混乱,我将它和**windowFlags()**同等看待(其中的细微区别不会影响我们的分析)
看看下面的函数:
QWidget::QWidget(QWidget * parent = 0, Qt::WindowFlags f = 0 )
QDialog::QDialog(QWidget * parent = 0, Qt::WindowFlags f = 0 )
void QWidget::setWindowFlags(Qt::WindowFlags type )
...
你可以通过构造函数或者成员函数setWindowFlags传递这个参数。 而且你从前面的isWindow()的源码可以得出结论: 一个Widget是不是一个Window只取决于它的的WindowFlags中__是否包含__Qt::Window枚举值。
对么?? 你会不会说:不对!!!
例子一
比如一个QPushButton如果没有parent它就是一个窗口。而设置了parent它却不是一个窗口了。分明就是和是否有parent相关嘛(这种例子太常见了,对吧)
例子二
恩,再看一个很多人迷惑的例子:在一个函数内
void Widget::onXXXX()
{
QDialog * dlg = new QDialog(this);
//QWidget * dlg = new QWidget(this);
dlg->show();
}
如果用 QDialog则会出现一个窗口而如果用 QWidget则不会出现窗口(而是一个普通部件)。为什么啊都有parent啊为什么会这样
其实:**例子一是特例**原因就是前面提到的__是否是窗口取决于flags而不是有无parent__
原因: 记住前面的黑体部分!
对于QWidget如果其parent为空构造时会有下面的动作
uint type = (flags & Qt::WindowType_Mask);
if ((type == Qt::Widget || type == Qt::SubWindow) && w && !w->parent()) {
type = Qt::Window;
flags |= Qt::Window;
}
注意看如果__一个widget没有parent且没有Qt::Window标记会被强制设置该标记__(如果有parentw->parent()为真,就不会设置标记了)。这就是parent的影响也是上面例子一的答案。
可是第二个例子怎么回事QDialog默认的flags也是0啊为啥还是窗口呢
QDialog::QDialog(QWidget *parent, Qt::WindowFlags f)
: QWidget(*new QDialogPrivate, parent,
f | ((f & Qt::WindowType_Mask) == 0 ? Qt::Dialog : Qt::WindowType(0)))
{
...
答案很简单QDialog是**派生类**嘛它构造函数中传给基类的参数包含了Qt::Dailog即0x00000002|__Qt::Window__。既然包含了Qt::Widnow标记当然就和是否有parent无关喽(例子一其实才是特例,只不过大家见的多了,反倒习以为常了)
例子三
感兴趣的看看,会有什么结果,嘿嘿,本节完
QPushButton * btn = new QPushButton(this);
btn->setWindowFlags(Qt::Window);

View File

@@ -0,0 +1,212 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-10-31T20:26:24+08:00
====== 漫谈QWidget及其派生类(三) ======
Created Monday 31 October 2011
http://blog.csdn.net/dbzhang800/article/details/6744650
在漫谈QWidget及其派生类(二)一文的最后我们简单提到了QMainWindow的一些东西。但是内容太少了本文中我们换个角度看看QMainWindow希望大家能了解一点与QWidget相比其派生类QMainWindow也不过如此
例子一
上一篇太乱了,应该主要是缺少例子。我们这次改一改,直接上个例子看看:
{{./1.png}}
#include <QtGui/QtGui>
class MainWindow:public QMainWindow
{
public:
MainWindow()
{
menuBar()->addMenu("&File");
menuBar()->addMenu("&Help");
statusBar()->addWidget(new QLabel("Hello from Dbzhang800..."));
statusBar()->addPermanentWidget(new QLabel("2011-09-03"));
** setCentralWidget**(new QTextEdit);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
这个例子实在没什么可说的,对不??
* 菜单栏
* 状态栏
* 中心窗体
这么常规的东西有必要写在这儿么如果只是这样肯定没有必要了。可是如果我说不用QMainWindow直接用QWidget可以轻易实现同样的效果。是不是值得聊聊了
===== HMainWindow =====
其实上面你看到的截图是我用的HMainWindow生成的。使用HMainWindow的话你只需要将前面的 QMainWindow ==> HMainWindow是不是很简单
** HMainWindow 的定义:**
class HMainWindow:public QWidget
{
public:
HMainWindow(QWidget * parent=0)
:QWidget(parent,__ Qt::Window__), m_menuBar(0), m_statusBar(0), m_central(0)
{
m_vbox = new QVBoxLayout(this);
m_vbox->setContentsMargins(0,0,0,0);
m_vbox->setSpacing(0);
}
void setCentralWidget(QWidget * w)
{
if (!m_central) {
m_central = w;
m_vbox->insertWidget(0, w, 1);
}
}
QMenuBar * menuBar()
{
if (!m_menuBar) {
m_menuBar = new QMenuBar(this);
m_vbox->setMenuBar(m_menuBar);
}
return m_menuBar;
}
QStatusBar * statusBar()
{
if (!m_statusBar) {
m_statusBar = new QStatusBar(this);
m_vbox->addWidget(m_statusBar);
}
return m_statusBar;
}
private:
QMenuBar * m_menuBar;
QStatusBar * m_statusBar;
QWidget * m_central;
QVBoxLayout * m_vbox;
};
我们用这个来模拟一个QMainWindow这个类很简单
HMainWindow():
构造函数。创建了一个layout(用来放置菜单栏、状态栏、中心窗体);传递给基类QWidget一个Qt::Window标记当然对我们这个例子这个东西有没有无所谓。
menuBar():
第一次调用它时会生成一个QMenuBar并加入layout
statusBar():
同上生成状态栏并加入layout
setCentralWidget():
除状态栏、菜单栏外的区域,总要放个东西吧?
和 QMainWindow比起来我们这个HMainWindow实在是太简易了。因为QMainWindow所使用的**QMainWindowLayout**(对,有这么一个东西,是个私有类) 比 QVBoxLayout复杂太多了。
不过呢,思想是一样的。一旦理解了这个,也就掌握 QMainWindow 的那点小把戏了。
**例子二**
不少网友抱怨:
* 覆盖基类的paintEvent函数结果画的东西全都看不到
* 覆盖基类的mousePressEvent函数结果收不到鼠标事件
* 覆盖基类的****Event函数结果...
...
//class MainWindow:public QMainWindow
class MainWindow:public HMainWindow
{
public:
MainWindow()
{
//...
}
protected:
protected:
void mousePressEvent(QMouseEvent *)
{
//...
}
void paintEvent(QPaintEvent *)
{
//...
}
};
有了前面的基础想想是不是很简单QMainWindow只不过是一个__带layout的Widget__上面放置了菜单栏、状态栏、中心窗体这些子Widget。挡住了我们的QMainWindow.
**例子三**
如果没有意识到QMainWindow的**中心窗体**的作用,很容易犯下面的错误,你能找到答案么?
* 创建一个子Widget比如按钮。不设置为(或添加到)中心窗体
//class MainWindow:public QMainWindow
class MainWindow:public HMainWindow
{
public:
MainWindow()
{
//...
QPushButton * btn = new QPushButton(this);
}
};
什么现象?哈哈,其实 很有意思:
创建了一个按钮,回想上一节?几何尺寸是如何改变的?只能通过**setGeometry或resize或move**。这些我们都没使用。于是默认大小、默认位置(00)。于是,左上角出现一个按钮!!
可是,左上角一般是什么东西呢?菜单栏嘛? 菜单栏和按钮同时出现在左上角??可能么?
有何不可呢,只不过两个东西必然有一个在上一个在下!!谁上谁下,和什么有关?你可以自己试试看(考虑到文章长度本文不涉及widget的堆放层次的控制)。
**例子四**
如果没有意识到QMainWindow这个东西其实已经__有了__一个layout很容易犯下面的问题你能找到答案么
* layout 不起作用,按钮依然出现在左上角!!
//class MainWindow:public QMainWindow
class MainWindow:public HMainWindow
{
public:
MainWindow()
{
QHBoxLayout * hbox = new QHBoxLayout;
hbox->addWidget(new QPushButton(this));
setLayout(hbox);
}
};
似乎不少人对此不解我用其他Widget都是这么用的啊创建layout添加其他widget设置layout
怎么失败了呢??
看看Manual知道答案了吧
void QWidget::setLayout ( QLayout * layout )
If there already is a layout manager installed on this widget, QWidget__ won't __let you install another.
**例子五**
真不想写了给大家个链接感兴趣的可以看看。QMainWindow一旦概念不清(或用法不对),还会有什么问题
http://hi.baidu.com/cyclone/blog/item/d27c41349e32b75b251f14d4.html
本文完。希望本文的内容对大家有帮助。但本文不能取代Manual用QMainWindow一定要认真看QMainWindow的Manual。

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Some files were not shown because too many files have changed in this diff Show More