Add New Notes
161
Zim/Programme/Qt/QDialog_模态对话框与事件循环.txt
Normal 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 当然是处于暂停状态了。说到底,就是两个死循环,一个在内,一个在外,只有里面的退出后,外边的循环才会执行。不过由于两个循环执行的命令是基本一样的,都是调用并处理程序收到的各种事件,所以,可能变得不容易理解
|
||||
132
Zim/Programme/Qt/QT_坐标转换_实现图片旋转.txt
Normal 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类对象,并为其添加图片,然后在以(0,0)点开始的宽和高都为100的矩形中显示该图片。你可以改变矩形的大小,看一下效果啊。最终程序运行效果如下。
|
||||
{{~/sync/notes/zim/Programme/Qt/漫谈QWidget及其派生类(三)/4.jpg}}
|
||||
|
||||
(说明:下面的操作都会和坐标有关,这里请先进行操作,我们在下一节将会讲解坐标系统。)
|
||||
|
||||
===== 二、利用更改坐标原点实现平移。 =====
|
||||
|
||||
Qpainter类中的__translate()函数实现坐标原点__的改变,改变原点后,此点将会成为__新的原点(0,0)__;
|
||||
|
||||
例如:
|
||||
|
||||
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); //将(100,100)设为坐标原点
|
||||
painter.drawPixmap(0,0,100,100,pix);
|
||||
}
|
||||
|
||||
这里将(100,100)设置为了新的坐标原点,所以下面在(0,0)点贴图,就相当于在以前的(100,100)点贴图。效果如下。
|
||||
{{~/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);就是在以(0,0)点为起始点的宽和高都是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 sh,qreal 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}}
|
||||
|
||||
|
||||
图片形状的变化,其实就是利用坐标系的变化来实现的。我们在下一节中将会讲解坐标系统。这一节中的几个函数,我们可以在其帮助文件中查看其详细解释。
|
||||
126
Zim/Programme/Qt/QT的Graphics_View框架与坐标系.txt
Normal 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的坐标系统完全相同。如果孩子的位置是(10,0),那么孩子坐标系中的(0,10)点,对 应于父坐标系中的(10,10)点。
|
||||
|
||||
因为**子item的位置与变换是相对于父item的,子item的坐标不会被父亲的变换影响**,尽管父item的变换隐含地对子item做了变换。在上面的例子中,即使父item旋转,缩放,子item的(0,10)点依然对应于父item的(10,10)点。然而, 相对于场景来讲,子item会遵循父item的变换。假如父item被缩放(2X,2X),子item的位置在场景中的坐标是(20,0),它的 (10,0)点则与场景中的(40,0)对应 。除了QGraphicsItem::pos(),QGraphicsItem的函数以Item坐标工作,如一个item's包围矩形总是以item坐标 的形式给出。
|
||||
|
||||
=== 场景坐标 ===
|
||||
场景坐标系统描述了每个**最顶级**item的位置,也是从视图向场景投递场景事件的基础。场景中的每个item有**场景位置与包围矩形**(QGraphicsItem::scenePos(),QGraphicsItem::sceneBoundingRect()), 另外,它有自己**本地item位置与包围矩形**。场景位置描述了item在场景坐标下的位置,它的场景包围矩形则用于QGraphicsScene决定场景中哪块区域发生了变化。场景中的变化通过**QGraphicsScene::changed()**信号来通知,它的参数是场景矩形列表。
|
||||
|
||||
=== 视图坐标 ===
|
||||
视图坐标是widget的坐 标,视图坐标中每个单位对应一个像素。这种坐标的特殊之处在于它是相对于**widget或是视口**的,不会被所观察的场景所影响。QGraphicsView 的视口的左上角总是(0,0),右下角总是(视口宽,视口高)。所有的鼠标事件与拖拽事件,最初以视图坐标表示,就应该把这些坐标映射到场景坐标以便与 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()建组。
|
||||
|
||||
|
After Width: | Height: | Size: 7.8 KiB |
BIN
Zim/Programme/Qt/QT的Graphics_View框架与坐标系/graphicsview-view.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
572
Zim/Programme/Qt/QT编程----事件(一).txt
Normal 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::keyPressEvent(QKeyEvent *k )
|
||||
|
||||
并实现**根据不同的键值,执行不同的动作**。
|
||||
|
||||
步骤二:
|
||||
|
||||
添加头文件<qevent.h>
|
||||
|
||||
在form.h 中为窗体类form1添加** void ** keyPressEvent(QKeyEvent *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::keyPressEvent(QKeyEvent *k )
|
||||
|
||||
并实现根据不同的键值,执行不同的动作。
|
||||
|
||||
步骤二:
|
||||
|
||||
添加头文件<qevent.h>
|
||||
|
||||
在form.h 中为窗体类form1添加 void keyPressEvent(QKeyEvent *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 __ event(QEvent *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);
|
||||
|
||||
248
Zim/Programme/Qt/QT编程----事件(二).txt
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
一旦,事件管理器被注册,发送到firstNameEdit,lastNameEdit,cityEdit,phoneNumberEdit的事件将__首先发送到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);
|
||||
}
|
||||
|
||||
在上面的函数中,我们首先检查目标部件是否是 firstNameEdit,lastNameEdit,cityEdit,phoneNumberEdit。接着,我们判断事件__是否是按键事件__。如果事件是按键事件,我们**把事件转换为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);
|
||||
}
|
||||
}
|
||||
@@ -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()。
|
||||
7
Zim/Programme/Qt/Qt学习之路.txt
Normal 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
|
||||
|
||||
487
Zim/Programme/Qt/Qt学习之路/QT_2D绘图全面解析.txt
Normal 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**,QPrinter,QSvgGenerator.
|
||||
|
||||
要实现自己的绘图设备,必须从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 和上一条规则的效果类似,但对比对较低一些,绘制白色将导致反转成目标颜色,绘制黑色没有
|
||||
24
Zim/Programme/Qt/Qt学习之路/Qt坐标系统与图形绘制.txt
Normal 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类是能够进行绘图的对象的基类,QWidget,QPixmap,QPicture,QImage,以及QPrinter类继承了QPaintEngine类的绘图能力。绘图设备的缺省坐标系统是以**左上角**作为原点,x坐标向右递增,y坐标向下递增。单位对于不同的设备是不同的,在基于像素的设备上,以一个像素作为缺省单位,而在打印机上,以1/72英寸作为缺省单位。
|
||||
|
||||
在编写图形绘制软件时,我们需要进**行逻辑坐标与物理坐标**之间的转换,这部分功能由QPainter的转换矩阵,视口,以及窗口来实现。缺省情况下,逻辑坐标与物理坐标是一致的。QPainter也支持平移与旋转这样的坐标变换。
|
||||
|
||||
图元的尺寸(宽度与高度)总是对应于它的数学模型,并且会__忽略它所使用的笔的宽度__。下图是通过两个点来实现矩形和直线的例子,用到的代码为:
|
||||
{{./1.jpg}}
|
||||
QRect(1,2,7,6) QLine(2,7,6,1)
|
||||
|
||||
在绘图的时候,我们使用QPainter::Antialiasing的渲染暗示来控制像素渲染。计算机绘图会出现走样的情况,如下图所示:在绘制直线的时候,出现了边缘不规则的情况。
|
||||
{{./2.jpg}}
|
||||
|
||||
如果我们设置了QPainter的反走样渲染暗示,像素将会对称的出现在点的两侧。因此,可以使用以下代码来解决绘制直线出现锯齿的问题. 这样绘制的直线如下图所示:
|
||||
{{./3.jpg}}
|
||||
BIN
Zim/Programme/Qt/Qt学习之路/Qt坐标系统与图形绘制/1.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt坐标系统与图形绘制/2.jpg
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt坐标系统与图形绘制/3.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |
25
Zim/Programme/Qt/Qt学习之路/Qt学习之路(1):前言.txt
Normal 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良好的设计等等。
|
||||
|
||||
闲话少说,还是尽快开始下面的学习吧!
|
||||
26
Zim/Programme/Qt/Qt学习之路/Qt学习之路(10)Meta-Object系统.txt
Normal 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()使用这些内省函数实现信号槽的连接。
|
||||
|
||||
以上这些过程是qmake,moc和QObject自动处理的,你不需要去考虑它们。如果实现好奇的话,可以通过查看QMetaObject的文档和moc的源代码来一睹芳容。
|
||||
29
Zim/Programme/Qt/Qt学习之路/Qt学习之路(11)MainWindow.txt
Normal 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进行修改,以满足我们的各种需要。
|
||||
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(11)MainWindow/1.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(11)MainWindow/2.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(11)MainWindow/3.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
109
Zim/Programme/Qt/Qt学习之路/Qt学习之路(12)菜单和工具条.txt
Normal 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实现的呢!
|
||||
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(12)菜单和工具条/1.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
90
Zim/Programme/Qt/Qt学习之路/Qt学习之路(13)菜单和工具条(续).txt
Normal 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*64,Qt会__自动缩小__尺寸。
|
||||
{{./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然后添加就可以的啦!
|
||||
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(13)菜单和工具条(续)/1.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(13)菜单和工具条(续)/2.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(13)菜单和工具条(续)/3.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(13)菜单和工具条(续)/4.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(13)菜单和工具条(续)/5.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
50
Zim/Programme/Qt/Qt学习之路/Qt学习之路(14)状态栏.txt
Normal 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,因此,我们可以构建出很复杂的状态栏。
|
||||
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(14)状态栏/1.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(14)状态栏/2.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
71
Zim/Programme/Qt/Qt学习之路/Qt学习之路(15)Qt标准对话框之QFileDialog.txt
Normal 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,是对话框显示时默认打开的目录,"." 代表程序运行目录,"/" 代表当前盘符的根目录(Windows,Linux下/就是根目录了),也可以是平台相关的,比如"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。
|
||||
43
Zim/Programme/Qt/Qt学习之路/Qt学习之路(16)Qt标准对话框之QColorDialog.txt
Normal 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文档也很详细,大家遇到问题可以查阅文档的哦!
|
||||
78
Zim/Programme/Qt/Qt学习之路/Qt学习之路(17)Qt标准对话框之QMessageBox.txt
Normal 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");
|
||||
}
|
||||
|
||||
其实道理上也是差不多的。
|
||||
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(17)Qt标准对话框之QMessageBox/1.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(17)Qt标准对话框之QMessageBox/2.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(17)Qt标准对话框之QMessageBox/3.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(17)Qt标准对话框之QMessageBox/4.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(17)Qt标准对话框之QMessageBox/5.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(17)Qt标准对话框之QMessageBox/6.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(17)Qt标准对话框之QMessageBox/7.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
46
Zim/Programme/Qt/Qt学习之路/Qt学习之路(18)Qt标准对话框之QInputDialog.txt
Normal 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不仅提供了获取字符串的函数,还有getInteger,getDouble,getItem三个类似的函数,这里就不一一介绍。
|
||||
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(18)Qt标准对话框之QInputDialog/1.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
71
Zim/Programme/Qt/Qt学习之路/Qt学习之路(19)事件(event).txt
Normal 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格式化写法啦!
|
||||
64
Zim/Programme/Qt/Qt学习之路/Qt学习之路(2):Hello,_world!.txt
Normal 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公司的CEO,Haarard 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,而且有Network,OpenGL,XML之类。不过,现在在这里我们不作修改,直接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应该怎么使用了的,无需我多费口舌。呵呵。
|
||||
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(2):Hello,_world!/1.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(2):Hello,_world!/2.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(2):Hello,_world!/3.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(2):Hello,_world!/4.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(2):Hello,_world!/5.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(2):Hello,_world!/6.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(2):Hello,_world!/7.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
75
Zim/Programme/Qt/Qt学习之路/Qt学习之路(20)事件接收与忽略.txt
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
这样,我们经过询问之后才能正常退出程序。
|
||||
51
Zim/Programme/Qt/Qt学习之路/Qt学习之路(21)event().txt
Normal 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。如果返回值是true,QApplication会认为这个事件已经处理完毕,会继续处理事件队列中的下一事件;如果返回值是false,QApplication会尝试寻找这个事件的下一个处理函数。
|
||||
|
||||
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()函数以便将自定义事件进行分发,否则你的自定义事件永远也不会被调用。关于自定义事件,我们会在以后的章节中介绍。
|
||||
50
Zim/Programme/Qt/Qt学习之路/Qt学习之路(22)事件过滤器.txt
Normal 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()函数。这几个层次的控制权是逐层增大的。
|
||||
52
Zim/Programme/Qt/Qt学习之路/Qt学习之路(23)_自定义事件.txt
Normal 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值是1000,QEvent::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)));
|
||||
这个对象不需要手动delete,Qt会自动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的事件机制,包括事件的派发、自定义等一系列的问题。下面的章节将继续我们的学习之路!
|
||||
83
Zim/Programme/Qt/Qt学习之路/Qt学习之路(24)_QPainter.txt
Normal 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在屏幕和打印设备上进行绘制。整个绘图系统基于QPainter,QPainterDevice和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开头的函数,用于各种图形的绘制,比如这里的drawLine,drawRect和和drawEllipse等。具体的参数请参阅API文档。下图给出了QPainter的draw函数的实例,本图来自C++ GUI Programming with Qt4, 2nd Edition.
|
||||
{{./3.png}}
|
||||
|
||||
好了,今天先到这里,我们将在下面一章中继续对这个paintEvent()函数进行说明。
|
||||
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(24)_QPainter/1.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(24)_QPainter/2.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(24)_QPainter/3.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
44
Zim/Programme/Qt/Qt学习之路/Qt学习之路(25)QPainter(续).txt
Normal 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不大清楚)。
|
||||
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(25)QPainter(续)/1.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(25)QPainter(续)/2.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(25)QPainter(续)/3.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
48
Zim/Programme/Qt/Qt学习之路/Qt学习之路(26)_反走样.txt
Normal 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画笔工具的缺陷,而是反走样算法的问题。如果你想了解为什么这样,请查阅计算机图形学里面关于反走样的原理部分。
|
||||
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(26)_反走样/1.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(26)_反走样/2.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
59
Zim/Programme/Qt/Qt学习之路/Qt学习之路(27)渐变填充.txt
Normal 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}}
|
||||
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(27)渐变填充/1png
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(27)渐变填充/2.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(27)渐变填充/3.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
53
Zim/Programme/Qt/Qt学习之路/Qt学习之路(28)坐标变换.txt
Normal 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!被旋转了。确切的说,__被旋转的是坐标系而不是这个文字__!请注意体会这两种说法的不同。
|
||||
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(28)坐标变换/1.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(28)坐标变换/2.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(28)坐标变换/3.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(28)坐标变换/4.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(28)坐标变换/5.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
69
Zim/Programme/Qt/Qt学习之路/Qt学习之路(3):Hello,_world!(续).txt
Normal 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();
|
||||
38
Zim/Programme/Qt/Qt学习之路/Qt学习之路(4):初探信号槽.txt
Normal 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没有任何的关系!哦哦,有一点关系,那就是名字是一样的!
|
||||
80
Zim/Programme/Qt/Qt学习之路/Qt学习之路(5):组件布局.txt
Normal 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不是使用的原生组件,而是自己绘制模拟的本地组件的样子,不过看看这个截图,它模拟的不能说百分百一致,也可说是惟妙惟肖了… :)
|
||||
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(5):组件布局/1.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(5):组件布局/2.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(5):组件布局/3.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
19
Zim/Programme/Qt/Qt学习之路/Qt学习之路(6)API文档的使用.txt
Normal 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中默认打开的就是这部分。
|
||||
|
||||
不过,关于文档的内容这里实在不好阐述,因为整个文档太大了,我也并没有看过多少,很多时候都是随用随查,就好像是字典一样——谁也不会天天没事抱着本字典去看不是?还有就是这里的文档都是英文的,不过如果是做开发的话,了解一些英文还是很有帮助的,不是吗?
|
||||
69
Zim/Programme/Qt/Qt学习之路/Qt学习之路(7)创建一个对话框(上).txt
Normal 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它们的头文件——当然,你想直接引入头文件也可以,不过那样的话编译速度就会慢一些。
|
||||
|
||||
好了,头文件先说这些,下一篇再说源代码啦!休息,休息一下!
|
||||
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(7)创建一个对话框(上)/1.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
120
Zim/Programme/Qt/Qt学习之路/Qt学习之路(8)_创建一个对话框(下).txt
Normal 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是分模块的,记得我们建工程的时候就会问你,使用哪些模块?QtCore?QtGui?QtXml?等等。这里,我们引入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的组件成百上千,不可能全部介绍完,只能用到什么学什么,更重要的是,我们已经了解了其编写思路,否则的话,即便是你拿着全世界所有的砖瓦,没有设计图纸,你也不知道怎么把它们组合成高楼大厦啊!
|
||||
BIN
Zim/Programme/Qt/Qt学习之路/Qt学习之路(8)_创建一个对话框(下)/1.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
97
Zim/Programme/Qt/Qt学习之路/Qt学习之路(9):深入了解信号槽.txt
Normal 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,当出现了循环连接的时候就会产生无限递归。
|
||||
16
Zim/Programme/Qt/Qt学习之路/Qt学习之路(tip)parent参数.txt
Normal 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属性。当然,如果你不声明这个属性,当整个程序关闭时,操作系统会回收内存——因此我们所说的内存泄漏一般是指我们自己写的应用程序的内部,而不会影响到整个操作系统——当然,如果你实现太可恶,操作系统也会受不了自动关掉你的程序的:-)
|
||||
29
Zim/Programme/Qt/Qt学习之路/浅析QT中的坐标系统.txt
Normal 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的矩形,视口和窗口都是左是角为(0,0)的320*320的相同矩形。在这种情况下,逻辑坐标和物理坐标就是一致的。
|
||||
|
||||
那么视口---窗口机制有什么作用呢?
|
||||
|
||||
视口---窗口机制对于我们编写__独立于绘制设备大小和分辨率__的代码是十分有用的,我们可以自己来进行逻辑坐标和物理坐标之间的映射计算,但是让QPAINTER来完成这项工作是很简单的。例如,我们想让逻辑坐标从(-50,-50)到(50,50),并且(0,0)在中间,我们可以这样来设置我们的窗口:
|
||||
painter.setWindow(QRect(-50,-50,100,100));
|
||||
(-50,-50)指定了原点,(100,100)指定了宽和高。这也就是说现在的逻辑坐标(-50,-50)和物理坐标(0,0)是对应的,并且逻辑坐标(50,50)和物理坐标(320,200)是对应的。
|
||||
|
||||
那么painter是怎么计算的呢?
|
||||
|
||||
这就要注意到一句话:逻辑坐标和物理坐标是成线性关系的。因为**物理坐标是和设备的分辨率**有关的。对于这个设备,它的分辨率应该是320*200的。那么应该如何计算逻辑坐标和物理坐标的映射关系呢? 我们来交叉对应坐标,对于X有:(-50,0)和(50,320).
|
||||
|
||||
所以对于X来说,逻辑坐标和物理坐标的映射关系就是:**b=(16/5)a+160**.
|
||||
对于Y有:(-50,0)和(50,200)
|
||||
所以对于Y来说,逻辑坐标和物理坐标的映射关系就是:**b=2a+100**
|
||||
根据上面的2个公式,我们任意给出**逻辑坐标中的一个点就可以求出对应的物理坐标中的点**。
|
||||
例如逻辑坐标(-30,-20)对应于物理坐标就是(64,60);逻辑坐标(10,20)对应于物理坐标就是(192,140)。
|
||||
|
||||
36
Zim/Programme/Qt/内置的widget和对话框类.txt
Normal 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都和剪贴板相关联。
|
||||
105
Zim/Programme/Qt/大道至简_Qt_Quick深度解析.txt
Normal 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,一部分是Quick,Qt 是什么呢,是它之上建立的产品,Quick是给我们带来一些便利,让我们开发程序更快。
|
||||
|
||||
具体Qt Quick是什么呢?我先给大家讲一点历史,Qt Quick来自于我们一次演示,我们的工程师有一次参加某一个大会,然后回来就跟我们说,说我在大会上看到了一个卖水果的公司开发了个手机,效果真是不错,我们这些做Qt Quick的人非常羡慕,后来说我们这些人也不比人家差,为什么我们不能开发一个类似的东西呢?于是我们有了一个新的项目,让开发者可以方便的调用,让你的程序变成带有动画、带有过场的漂亮界面。后来又做了脚本层,让大家通过脚本更方便创建动画效果和界面,**自从有了QML,Qt 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进入QML,C++也是透明的,两部分都可以调用。混合编程最难解决通信问题在这里面完美解决了。值得一提的是他们俩还可以访问同一系统,没有任何门槛,如果你熟悉QT,简单就像吃一个苹果。
|
||||
|
||||
接着我们来说广。其实这个主题没什么可说的了,就是对多平台的支持,刚才我两位同事已经讲了非常透彻了,怎么进行编程,讲的非常好,比我讲的好多了。我就想强调一下,我们现在支持的平台官方支持的平台,包括OXX、MeeGo,它在这个设备商都很好,很流畅,放心用吧!这段是MOBILITY刚才陈啸天已经讲的非常清楚了,联系人、日历、网络、服务目录、值空间,邮件通信,包括MIS、包括MES,还有E—MAIL,还有力反馈你可以操纵手机上的振动摩托。另外还要提一下MOBILITY,虽然HTML5是周杰伦的歌我们还是要支持它,所以我们提供了YP的插件,就是你可以在MOBILITY里面做一个插件,分析内容都可以做到了。另外对于各个平台来说我们做了统一风格的QT COMPONENT,如果你这样用了,有一种统一的美。这样也避免了你从头开始重新做按钮的工作。
|
||||
|
||||
最后我们说到了最感兴趣的部分就是这个帅。帅直的就是3D,3D就是帅,我是来自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
|
||||
103
Zim/Programme/Qt/漫谈QWidget及其派生类(一).txt
Normal 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标记,会被强制设置该标记__(如果有parent,w->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);
|
||||
212
Zim/Programme/Qt/漫谈QWidget及其派生类(三).txt
Normal 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**。这些我们都没使用。于是默认大小、默认位置(0,0)。于是,左上角出现一个按钮!!
|
||||
|
||||
可是,左上角一般是什么东西呢?菜单栏嘛? 菜单栏和按钮同时出现在左上角??可能么?
|
||||
|
||||
有何不可呢,只不过两个东西必然有一个在上一个在下!!谁上谁下,和什么有关?你可以自己试试看(考虑到文章长度,本文不涉及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。
|
||||
BIN
Zim/Programme/Qt/漫谈QWidget及其派生类(三)/1.jpg
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
Zim/Programme/Qt/漫谈QWidget及其派生类(三)/1.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
Zim/Programme/Qt/漫谈QWidget及其派生类(三)/10.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
Zim/Programme/Qt/漫谈QWidget及其派生类(三)/11.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
Zim/Programme/Qt/漫谈QWidget及其派生类(三)/2.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |