基于Qt的电视机UI实现技术
更新时间:2023-03-11 10:54:01 阅读量: 教育文库 文档下载
基于Qt技术的可移植UI设计
—— 基于Qt的电视机UI实现技术
1 UI软件架构
基于Qt来实现一套电视机用户界面(User Interface),首先需要选择一个软件框架,在Qt中,Main Window 为创建应用程序的UI提供了一个框架。QMainWindow类用于管理UI页面。
及其相关类共同完成Main Window中的页面管理。本项目选择了Qt的Main Window 框架作为实现电视机UI的软件架构。下面介绍一下Qt的Main Window 框架在实际项目中的应用。
1.1 Qt的Main Window 框架
Qt的Main Window 框架,以QMainWindow类作为程序主窗口。QMainWindow类拥有自己的布局,如图1-1-1所示:
图1-1-1 主窗口布局
根据电视机UI的特点,用户只能通过遥控器及本机按键来操作UI,因此只需要一个中心区域显示交互内容就足够了。对于QMainWindow布局中的Menu Bar、Toolbars、Dock Widgets、Status Bar这几个部分是我们所不需要的。我们只需要关注Central Widget即可。
QMainWindow的Central Widget可以是多种类型: ? Qt提供的标准窗口部件,比如QWidget、QTextEdit等; ? 用户自定义的窗口部件;
? 布局管理器组织起来的多个widgets;
? 分裂器—QSplitter。QSplitter作为一个容器可以容纳多个窗口部件,此时中央部件是
一个包容多个窗口部件的容器;
? 多文档区部件—QMdiArea。如果应用程序使用MDI,则Central Widget将被一个
QMdiArea部件占据。每个MDI窗口都是这个QMdiArea部件的一个子部件。 应用程序选择哪种类型作为Central Widget,需要由具体需求决定。电视机UI通常具有主菜单、一级子菜单、二级子菜单、快捷菜单、信息提示菜单等多个菜单显示页面。本项目将每个菜单页面视作一个文档(Document),各级菜单之间的切换,采用多文档界面(Multiple Document Interface)模式进行管理,因此选择QMdiArea作为主窗口的Central Widget。通过调用QMainWindow的setCentralWidget()方法来设置Central Widget。
QMdiArea提供了一个管理/显示多文档界面的区域。它通常作为应用程序多文档界面主窗口的Central Widget,实现对子窗口的管理、绘制和排布。QMdiArea具有独特的多文档子窗口类QMdiSubWindow,它在多文档区部件内表现为一个顶层窗口,可以关闭、最小化和最大化,具有独立的窗口标题。QMdiSubWindow具有自己的布局管理器,该布局管理器管理窗口标题栏和放置窗口部件的中心区域。多文档子窗口QMdiSubWindow和多文档区部件QMdiArea共同实现应用程序的多文档功能。通常通过调用函数QMdiArea::addSubWindow()为一个多文档部件添加一个多文档窗口,并返回该多文档子窗口的指针。 总结:一个基于MDI的Qt Main Window 框架,由QMainWindow、QMdiArea、QMdiSubWindow三个核心类构成。其中,
QMainWindow类主要提供了一个应用程序的主窗口,在主窗口中提供了 Central Widget区域,用于页面管理。
QMdiArea类被设置成Central Widget,实行具体的子窗口管理任务;在QMdiArea中加入的每个子窗口都是QMdiSubWindow类或其派生类的对象;该类负责管理菜单页面的创建、销毁、显示、隐藏等等。需要注意的是,新菜单创建、老菜单销毁过程中,要防止内存泄露;此外,如何高效快速完成菜单间的切换是非常重要的。
QMdiSubWindow类是具体的每个子窗口,或者称为子菜单页面的基类;实际的电视机主菜单、图像子菜单、声音子菜单等等的实现,均由该类派生。 1.2 Qt的Main Window 框架的实际应用
实际应用中,将根据具体的UI方案需求,以QMainWindow、QMdiArea、本项目中,由QMainWindow作为基类,派生出MainWindow类,除去基本的继承QMdiSubWindow三个类作为基类,派生出满足需求的类加以应用。
自QMainWindow的属性外,在MainWindow的构造函数中,加入了对主窗口几何位置、尺寸的设定;加入了主窗口背景属性的设定。最后将由QMdiArea类派生来的WindowHandler类的对象设置为Central Widget。
本项目中,由QMdiArea作为基类,派生出WindowHandler类,用以实现菜单页面
的管理。WindowHandler类中添加了用于页面之间跳转的槽函数slot_GotoScreen(int curSID, int dstSID, int param); 添加了注册各子菜单页面的函数 RegisterWindow(int nKey);
并给予每页菜单一个独一无二的ID号,将ID号与指向该页面类型的指针函数以
本项目中,由QMdiSubWindow作为基类,派生出MdiSubWindow类。在MdiSubWindow中添加了用于页面跳转的信号
void sig_goto_screen(int curSID, int dstSID, int param); 添加了公共参数用于接收前一页菜单传递过来的参数 int m_PrevWindow_Param;
在构造函数中增加了设置窗口标志的语句 setWindowFlags(Qt::FramelessWindowHint);
在具体菜单页面实现时,跟菜单、主菜单、设置菜单在MdiSubWindow的基础上派生出了BaseMenu、MainMenu、SetupHandler类。软件框架相关的类之间的关系如图1-2-1所示:
QMdiSubWindowQMainWindowQMdiAreaMdiSubWindowMainWindow WindowHandler
BaseMenuMainMenuSetupHandler 图1-2-1 软件框架相关类间关系
1.3 Qt Graphics View 框架
本项目设置菜单实现动画切换效果,用到了Qt Graphics View 框架。该框架提供了一个用于管理大量自定义的二维图形item的平面,还提供了一个用于显示、缩放、旋转这些二维图形item的视图控件。结合本项目,与该框架相关的三个类分别为QGraphicsScene、QGraphicsView、QGraphicsProxyWidget。
QGraphicsScene类是诸多item的容器。需要动作的item首先被加入到QGraphicsView类是一个视口,用于观察QGraphicsScene及其中的各个item。可以QGraphicsScene中,归QGraphicsScene统一管理。
通过QGraphicsView来改变被其观察的QGraphicsScene的坐标轴,从而实现场景的动态效果。
QGraphicsProxyWidget是本项目用到的类。通常向QGraphicsScene这个容器中加入的需要产生动画效果的item,都是通过QGraphicsItem及其派生类产生的对象。这些item通常可以是图片、文字、路径、几何形状等简单的动画元素。由于本项目实现的UI中,设置菜单中需要产生动画效果的item是一个由图像、文字以及各种widget组合而成的一个复杂widget,所以QGraphicsItem及其派生类产生的对象无法满足要求。在此用到了QGraphicsScene中的一个添加widget作为item的函数:
QGraphicsProxyWidget * QGraphicsScene::addWidget ( QWidget * widget, Qt::WindowFlags wFlags = 0 )
这样,由Picture、Sound等类产生的含有图像、文字、控件的对象便可以顺利加入到场景中,并对其进行旋转缩放等操作。 1.4 “设置”菜单中的subIem架构
“设置”菜单对应的类名为SetupHandler。在该菜单下,包含“图像”、“声音”、“菜单”、“网络”、“时间”、“频道”设置子项。每个子项并未独立为一页菜单,而是以subItem的形式,嵌入到SetupHandler类所构建的场景中,由场景管理每个subItem。
由于在SetupHandler类中,需要将按键响应传递到每个subItem内部的调整函数中,这就要求在SetupHandler类中,可以调用subItem的方法。每个subItem的实现都是一个类,但是在SetupHandler中,每个subItem是动态生成的,这就希望有一个类型的指针可以指向各个有所区别的subItem类的对象。基于上述两点考虑,在实现每个具体的subItem之前,构建了一个SetupSubItem类,在该类中实现了一个公有的currentItem变量用于描述subItem中当前选中项;实现了两个字体对象并在构造函数中初始化,用于subItem中选中与未选中项的字号区别;实现了两个虚函数,用于满足在SetupHandler类中可以通过SetupSubItem型的指针调用具体的subItem对象的按键响应函数。
与subItem相关的类之间的关系如下图所示:
QWidgetSetupSubItemPicturePictureAdvanceSound…...图1-4-1 SubItem相关类间关系
每个subItem的实现非常类似,根据UI设计方案,在实现之前,将subItem划分几个
部分,最后用QFram组装到了一起。下面以Picture类为例,分析一下subItem的架构。
Picture子项的UI设计外观如图1-4-2所示:
图1-4-2 Picture子项的UI设计外观
将Picture子项的实现分为4部分:
A. 背景框与上下键选择提示图标,由QLabel及QPixmap组合实现; B. 左侧文字列表由一个QListWidget实现; C. 中部选项列表由一个QListWidget实现;
D. 右侧图示列表由QFrame、QGroupBox包裹QSlider及QRadioButton实现。 划分后的控件示意图如图1-4-3所示:
AQFrameBCQFrameQRadioButtonQListWidgetQListWidgetQFrameDTextListTextListTextListTextListTextListTextListTextListTextListQGroupBoxQGroupBoxQGroupBoxQGroupBoxQSlider
图1-4-3 控件划分示意图
上图中的D区域,使用了两个QFrame,外侧较大的QFrame目的是将所有的QGroupBox装到一起,在响应上下键时做整体移动;内存较小的QFrame目的是遮罩,只显示出4组QGroupBox。B,C区域使用的QListWidget类有一个很好的属性,当设置当前的item(QListWidget::setCurrentItem)时,QListWidget会自动调整列表的位置,并且只要预先设置了QListWidget的几何属性,便决定了QListWidget的可见区域。
2 内存管理
2.1 Qt内存管理规则
C++语言没有提供自动的内存管理。通常,应用程序的编写者必须自己处理内存分配与从QObject类派生而来的类的一些对象,可以由一个树形结构来管理。这些对象可能会回收的问题。Qt可以接管一部分内存管理的工作。
产生子对象。如果一个对象被delete掉了,那么Qt会自动delete掉它所有的子对象,并且这些子对象会依序delete掉它们的子孙。也可以这样理解,如果一个对象树的根对象消失了,则Qt自动删除整棵树。
Qt这种自动管理内存的功能,可以使编程人员从追踪对象的子对象及释放它们所占用的内存空间的工作中解脱出来。但是,为了使这种自动内存管理的功能起作用,所有的子对象(以及子对象的子对象)必须在堆中分配内存(即通过使用操作符new来产生子对象)。用new操作符创建对象会返回一个指向堆的指针。
在每一个Qt对象中,都有一个链表,这个链表保存有它所有子对象的指针。当创建一个新的Qt对象的时候,如果把另外一个Qt对象指定为这个对象的父对象, 那么父对象就
会在它的子对象链表中加入这个子对象的指针。另外,对于任意一个Qt对象而言,在其生命周期的任何时候,都还可以通过setParent函数 重新设置它的父对象。当一个父对象在被delete的时候,它会自动的把它所有的子对象全部delete。当一个子对象在delete的时候,会把它自己 从它的父对象的子对象链表中删除。对象的父子关系大大简化了内存管理,并且降低了内存泄露的风险。对于使用new创建的对象,我们只需要delete那些没有父对象的便可以了。
父对象、子对象及父对象的子对象列表关系如图2-1-1所示:
ParentObjChild1ObjParent子对象列表Child1Child2...ObjChild2Parent图2-1-1 父对象、父对象的子对象列表及子对象间的关系
以通过其它方式让其具有父对象,形成父子关系:
情况1:布局管理器对其所管理的对象,可以统一指定父对象。例如: #include
int main(int argc, char *argv[]) {
QApplication a(argc, argv); QWidget window;
有以下几种情况,在使用new操作符创建一个对象时,不需要显式地指定父对象,但可
QVBoxLayout* mainLayout = new QVBoxLayout(&window);//指定布局管理
//器的父对象,同时该布局管理器内的所有部件的父对象,也被指定
QLabel* label1 = new QLabel(\//创建时未指定父对象 QLabel* label2 = new QLabel(\//创建时未指定父对象 mainLayout->addWidget(label1);//加入布局管理器 mainLayout->addWidget(label2); //加入布局管理器
window.show(); return a.exec(); }
以上代码中,在用new创建两个QLabel对象时,并没有指定其父对象。但是通过布局管理器的addWidget()被加入到布局管理器之后,它们的父对象就成了window,因为布局管理器创建时指定了自己的父对象为window。它们之间的父子关系如图2-1-2所示:
父对象window子对象子对象子对象mainLayout label1 label2 图2-1-2 父子关系
情况2:将一个对象设置为一个QMainWindow对象的Cnetral Widget时,QMainWindow接管该对象的指针,并在合适的时间将其删除。例如: #include
int main(int argc, char *argv[]) {
QApplication a(argc, argv); QMainWindow mainWindow;
QLabel *label = new QLabel(\//未指定父对象
mainWindow.setCentralWidget(label);//label对象被mainWindow“收养” mainWindow.show(); return a.exec(); }
在以上代码中,用new创建label对象时,没有指定父对象。通过调用setCentralWidget将label设置为mainWindow的Central Widget之后,label对象被mainWindow“收养”,即mainWindow成为label的父对象。
情况3:将一个QGraphicsItem对象加入到一个QGraphicsScene对象中之后,场景对象成为item对象的父对象。???
2.2 动态创建与删除
在菜单页面之间切换时,伴随着老菜单的删除(或隐藏),新菜单的创建(或显示)的内
存操作动作。Qt是基于C++语言的。内存的分配回收采用new与delete操作符完成。如何动态的创建或删除指定类型的页面对象,有一定的技巧性。
为了在不同菜单之间加以区分,通常会给每一个菜单页分配一个ID号。 typedef enum{ ID_MENU=0, ID_NETWORK, ID_PICTURE, ID_SOUND, ID_CHANNEL, ID_TIME,
//add new item here ID_PICTUREADVANCE, ID_SETUPITEM_MAX }E_ID_SETUPITEM;
在程序中执行菜单切换时,希望给定一个ID号,便能创建一个相应类型的菜单对象,或者给定一个ID号,便能删除一个相应类型的菜单对象。最简单的方法可以用switch-case结构实现。但是代码冗长,效率不高。一下提供了一种实现技巧: 定义两个带模板类的函数:
//用于创建指定类型的页面对象,在堆中分配内存 template
return new _T(); }
//用于删除指定类型的页面对象,回收堆中已分配的内存 template
delete static_cast<_T*>(ptr); }
//定义两个函数指针,去指向分配内存的函数 typedef MdiSubWindow* (*fnAlloc)();
typedef void (*fnDelete)(MdiSubWindow*);
//定义一个结构体,将创建、删除函数指针封装在一个结构中 struct SFunc { fnAlloc pfnAlloc; fnDelete pfnDelete; };
在类的声明中加入 private:
QMap
template
int RegisterWindow(int nKey);//向m_mapSc中添加一个ID-函数指针 对 protected:
void RegisterWindows();//将所有的ID-函数指针 对加入m_mapSc int CreateSubWindow(int nKey);//动态创建一个指定页面 int DeleteSubWindow(int nKey);//动态删除一个指定页面 在类的实现中,实现注册函数,完成一张map表: template
int WindowHandler::RegisterWindow(int nKey) { SFunc *funcP=new SFunc;
funcP->pfnAlloc=allocSc<_T>;//将函数指针指向分配内存的函数 funcP->pfnDelete=delSc<_T>;//将函数指针指向释放内存的函数 if(m_mapSc.contains(nKey)) {
return 1; } else {
m_mapSc.insert(nKey, funcP);//将ID-函数指针 对 插入m_mapSc return 0; } }
void WindowHandler::RegisterWindows()
{
RegisterWindow
RegisterWindow
动态创建与删除的函数:
int WindowHandler::CreateSubWindow(int nKey) {
if(nKey >= SID_Max) {
return 1; } else {
m_pCurSubWin = m_mapSc[nKey]->pfnAlloc();//分配内存
connect(m_pCurSubWin, SIGNAL(sig_goto_screen(int, int, SLOT(slot_GotoScreen(int, int, int)));//动态关联信号-槽 return 0; } }
Int WindowHandler::DeleteSubWindow(int nKey) {
if(nKey >= SID_Max) {
return 1; } else {
m_mapSc[nKey]->pfnDelete(m_pCurSubWin);//释放内存 return 0; } }
int)), this, 本项目实现中,有两个地方用到了这种动态创建与删除的技巧。一处是在
WindowHandler类中,实现BaseMenu、MainMenu、SetupHandler之间的切换;一处是在SetupHandler类中,实现Picture、PictureAdvance、Sound等subItem之间的切换。
3 控件美化
在电视机UI界面中,通常会用到一些控件进行信息描述,比如亮度调节时的滑块调节控件,自动搜台时的进度控件等。Qt提供了许多现成控件可供使用,并且每种控件都有一个setStyleSheet()方法,可以设置控件的各种样式。掌握了setStyleSheet()的使用方法,就很容易实现UI设计方案中外观精美的控件。 过
Qt Style Sheets 功能非常强大,利用它可以定制各种控件的外观。Style sheets可通QApplication::setStyleSheet()应用到整个应用程序中,也可以通过
QWidget::setStyleSheet()应用于某个特定的控件。若有多个style sheets在不同的层级上应用,Qt会将这些应用叠加起来。
Qt Style Sheets的概念、术语、语法受HTML的层叠样式表(CCS:Cascading Style Sheets)启发甚深,其使用的术语、语法几乎与HTML CSS相同。要熟练的使用Qt Style Sheets,对CSS语法要有一定的了解。 3.1 CSS中的若干概念
Qt控件在设置Style Sheets时,需要清楚每种属性的语法,下面就最常用到的几个CSS概念加以介绍。
3.1.1 CSS中的Box model
当使用style sheets时,每个控件都被当作一个具有四个同中心矩形的box来对待,这四个矩形分别是:margin矩形、border矩形、padding矩形及content矩形。四个同心矩形可由图3-1-1表示如下:
图3-1-1 box的矩形概念
每个box都有一个content 区域(文字,图像等),周围可选择的padding,border,以及margin区域。每个区域的尺寸有下面定义的属性加以指定。图3-1-1显示了这些区域之间的依赖关系。
margin, border,padding,这三者可以分成top,right,bottom,left四个段(例如,图3-1-2中,“LM”代表left margin,“RP”代表right padding,而“TB”代表top border,等等)。
这四个区域(content, padding, border, margin)的周界被称作edge,所以每个box有4个edge:
content edge(或者称为inner edge):
content edge 环绕在box的width与height定义的矩形的周围,通常由元素的渲染内容决定。四个content edges定义出了box的content box。
图3-1-2 Box model区域
padding edge:
padding edge 围绕在box padding的四周。如果padding的width为0,则padding
edge与content edge相同。四个padding edges定义了box的padding box。 border edge:
border edge围绕在box的border四周。如果border的width为0,则border edge与padding edge相同。四个border edges定义了box的border box。 margin edge(或者称为outer edge):
margin edge围绕在box margin的四周。如果margin的width为0,则margin edgemargin、border-width、padding属性在缺省情况下均被置为0。此时,四个矩形将与border edge相同。四个margin edges定义了box的margin box。 精确重合。
3.1.2 background相关属性
每个box都有一个背景层,这个背景层可能是全透明的(缺省值),可能填充了某种颜色,也可能填充了一幅或几幅图像。背景属性指明了需要使用哪种颜色(background-color),使用哪些图像(background-image),以及它们的尺寸,位置等等。
可以用background-color属性指定box的背景色。背景色将绘制于任何背景图像之后。例如:
QLabel { background-color: red } /* opaque red */ QLabel { background-color: #FF0000 } /* opaque red */ QLabel { background-color: rgba(255, 0, 0, 75%) } /* 75% opaque red */
QLabel { background-color: rgb(255, 0, 0) } /* opaque red */ QLabel { background-color: rgb(100%, 0%, 0%) } /* opaque red */ QLabel { background-color: hsv(60, 255, 255) } /* opaque yellow */ QLabel { background-color: hsva(240, 255, 255, 75%) } /* 75% blue */
可以用background-image属性为控件指定一个背景。缺省情况下,background-image仅被绘制在border区域的内部。若想绘制在border区域以外,可以使用background-clip属性。使用background-repeat与background-origin来控制背景图像是否在空间上重复以及重复绘制的方向。
一个background-image不会根据控件的尺寸来缩放自身的大小。若想让背景图像按照控件尺寸进行缩放,必须使用border-image属性。border-image属性提供了一个可变的背景,当指定了一个border-image时,便不需要再指定background-image了。若是同时指定了这两个属性,则border-image会将background-image覆盖。例如: QFrame { background-color: rgba(0,255,0,100) }/*设置背景色*/ QFrame { background-image: url(:/ball.png) }/*设置背景图片*/ QFrame { background-repeat: repeat-x }/*在x轴方向重复绘制背景*/
/*repeat-y在y轴方向重复绘制背景*/
/*no-repeat不重复绘制背景*/
/*默认情况下,x,y方向均会被重复绘制*/
QFrame { border: 1px solid #ff0000 }/*设置border的宽度,线形,颜色*/ 综合设置后的结果图3-1-3所示:
图3-1-3 style sheet设置效果
3.2 利用style sheet设置QSlider控件
Qt中默认的QSlider控件外观如图3-2-1所示:
图3-2-1 QSlider控件默认外观
QSlider控件由两部分组成:groove与handle。可以看成是由两个box叠加构成的。 用style sheet来改变QSlider控件外观最直接的方法就是分别设置groove与handle两个box的background image。其中的handle部分的box,其高度是跟groove的box高度相关的。在设置handle box的尺寸时,只需设置其宽度。若需要调整handle box的高度,则可利用handle box的margin来调整。
下面给出一段设置QSlider外观的参考代码,其效果如图3-2-2所示: QSlider *slider = new QSlider(parent);
slider->setMaximum(100);//设置最大值 slider->setMinimum(0); //设置最小值
slider->setGeometry(10,60,350,35);//设置几何参数 slider->setValue(45);//设置当前值
slider->setOrientation(Qt::Horizontal);//设置控件方向,水平
slider->setStyleSheet(\/*设置groove属性*/ \ \ \
\ \ \ \
\/*设置handle属性*/ \ \ \
\/*设置上下边距缩小height*/ \
\/*设置border的弯角属性*/ \
图3-2-2 slider设置之后的效果
还可以将QSlider控件分成4部分:groove、handle、add page、sub page。其中
add page是控制handle右侧的groove外观;sub page是控制handle左侧的groove外观。下面给出一段示例代码,其效果如图3-2-3所示: QSlider *slider = new QSlider(Qt::Horizontal); slider->setRange(0, 100);//set default range slider->setStyleSheet(
\\
\
\
\
\qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.34, fx:0.5, fy:0.5, stop:0.293785 rgba(255, 255, 255, 255), stop:0.379831 rgba(236, 236, 236, 180), stop:0.830508 rgba(255, 158, 158, 0));}\
\
\
\
#7B7B7B);}\
\
图3-2-3 slider设置之后的效果
在上段示例代码中,handle并未使用背景图片,而是用color pattern填充了background-color。sub page部分同样使用了color pattern进行颜色填充。型如qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #D6D6D6, stop:1 #7B7B7B);的颜色样式可以利用Qt Designer工具进行编辑,同时工具中也提供了多种固定的样式,可以在这些固定样式的基础上进行修改,也可以进行全新的创作。Qt Designer中的property?style sheet?Add Gradient? background-color中的各种pattern对应的效果如图3-2-4所示:
图3-2-4 Qt Designer中提供的固定样式
4 事件处理机制
所有Qt应用程序都是由事件驱动的,程序的每个动作都是由幕后某个事件所触发。应用程序在main函数中进行初始化,然后在main函数中由一个QApplication对象调用exec方法,这就启动了应用程序的事件循环。
事件循环负责处理两个任务:
A 事件循环负责处理来自系统的事件(比如重新绘制一个窗口区域)。事件循环将接收到的来自系统的事件转换成基于Qt的事件类型,转换后的事件被封装成以QEvent作为基类的派生类。
B 事件循环负责处理Qt自身产生的事件(比如某个指定时间到而触发的定时事件)。这些事件也是基于QEvent的,被事件循环处理。
每个QEvent都有一个类型。QEvent的子类可以包含所需的全部信息。比如,Qt通过QCoreApplication::postEvnets()将事件传递给特定的对象。这些接收事件QMouseEvent处理按键及鼠标位置的信息。
的对象必须继承自QObject。这个方法的第一个参数是接收事件的对象,第二个参数是QEvent对象。postEvents()将事件传递给目标对象的event()方法。event()方法的任务是依据接收事件的对象的所属类的需求来决定处理或忽略接收到的事件。这个event()方法也被称作事件句柄(event handler)。如果一个事件不能被接收方立即处理,该事件将被放入一个队列然后延迟处理。
根据Qt处理事件的机制,可以将事件处理由强到弱分成几个级别(强者先处理): 给QApplication对象安装事件过滤器:一旦给qApp(每个程序中唯一的QApplication对象)装上过滤器,那么所有的事件在发往任何其他的过滤器时,都要先经过当前这个eventFilter()。
在Qt对象上安装事件过滤器:事件过滤器可以让对象A接收、处理对象B的事件。对于对象B的每个事件,先由A接收并处理(被A的eventFilter()过滤一遍),然后剩下还需处理的事件再传递给对象B去处理。
重载event()函数:通过重载event()函数,可以在事件被特定的事件处理函数处理之前处理它。
重载特定事件处理函数:比如重载keyPressEvent(), paintEvent(),timerEvent()等。 4.1 事件过滤器的应用(eventFilter)
在进行事件过滤之前,必须先安装事件过滤器。假设需要给对象B安装事件过滤器,对象B的事件先被A过滤一遍,再传到B。那么,首先要在对象A的构造函数中调用installEventFilter()函数来监视对象B的事件。
安装过滤器之后,对象B完全放弃了它自己的事件,由对象A来决定是将本属于B的事件过滤掉还是将它们传递给B。在A中需要重载eventFilter()函数,其定义如下: bool QObject::eventFilter(QObject *watched, QEvent *e);
参数watched允许对来自多个被监视的对象加以区分,参数e是被处理的事件。返回值告诉Qt的事件系统,该如何处理事件。如果返回false,事件e将被传递给被监视的对象;如
果返回true,事件e将被过滤掉,这意味着该事件将不会传递到原本想到达的目标对象。
event filter的常见应用场合:
用来处理热键 -- 比如一个界面上可以由用户热键来触发的多个按钮。 由于只有得到焦点的控件才能获得键盘的事件, 如果不用event filter就需要给每个button都加上键盘事件的处理, 还要在button里去访问兄弟button的指针, 逻辑非常混乱。 如果由主窗体做各个按钮的eventFilter, 则只需要在主窗体里去处理键盘事件就好, 而且主窗体可以很容易的访问到各个button的指针, 很方便。
用来代替派生和重写虚函数 -- Qt里的键盘鼠标事件基本上都是以虚函数的方式来处理, 要想重写虚函数则必须派生一个子类, 这样的话如果只是一个简单的事件处理也去派生子类代价未免大了些, 这时候就值得用用eventFilter。 比如我的MDI界面想在每个子窗体关闭的时候做一些统一的操作, 一般的做法是处理子窗体的closeEvent。 但显然给每个子窗体都去派生个子类太不现实, 最好的方法是把mainwindow作为子窗体的eventFilter去处理CloseEvent事件。
以下是一个应用事件过滤器的实例。该实例所实现的主要功能是,在一个含有QTextEdit对象与QTextBrowser对象的对话框中,向QTextEdit控件中输入字符,然后按“Return”键或“Enter”键,内容被追加到QTextBrowser对象中,同时清除QTextEdit对象中的内容。实例的运行效果如图4-1-1所示:
4-1-1 eventFilter实例运行效果
实现代码如下所示: // chatwindow.h
#ifndef CHATWINDOW_H #define CHATWINDOW_H #include
class ChatWindow : public QWidget {
Q_OBJECT public:
ChatWindow(QWidget *parent = 0);
bool eventFilter(QObject *watched, QEvent *e);//事件过滤器 void submitChatText();//负责发送text private:
QTextBrowser *conversationView;//文本浏览控件 QTextEdit *chatEdit;//文本编辑控件 };
#endif // CHATWINDOW_H
// chatwindow.cpp #include
ChatWindow::ChatWindow(QWidget *parent) : QWidget(parent) {
QVBoxLayout *lay = new QVBoxLayout(this); QSplitter *splitter = new QSplitter(Qt::Vertical, this); lay->addWidget(splitter);
conversationView = new QTextBrowser; chatEdit = new QTextEdit;
splitter->addWidget(conversationView); splitter->addWidget(chatEdit);
chatEdit->installEventFilter(this);//给chatEdit对象安装事件过滤器 setWindowTitle(tr(\
setTabOrder(chatEdit, conversationView);//设置焦点顺序 };
bool ChatWindow::eventFilter(QObject *watched, QEvent* e) {
if (watched == chatEdit && e->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast
if (ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Return) { submitChatText(); return true; } }
return QWidget::eventFilter(watched, e); }
void ChatWindow::submitChatText() {
// append text as new paragraph
conversationView->append(chatEdit->toPlainText()); // clear chat window chatEdit->setPlainText(\}
在ChatWindow的构造函数中,调用installEventFilter()函数,给chatEdit安装事件过滤器,并由this指针参数指出,将由ChatWindow自身对chatEdit对象的事件进行过滤。
在chatEdit对象中产生的事件,会先经由ChatWindow 的eventFilter()过滤,然后才实例的主函数代码入下所示: 会被传递给chatEdit对象自己。 // main.cpp
#include
QApplication app(argc, argv); ChatWindow win; win.show();
return app.exec();//事件循环 }
整个实例的事件处理流程为:
(1)在chatEdit控件中输入字符串,然后按“Return”或“Enter”键,触发按键事件。 该事件在chatEdit中产生,如无意外,将由chatEdit所属类的event()方法处理。 (2)主函数中的exec()事件循环中检测到了按键事件,按照程序设定,该事件被先传递给了ChatWindow的eventFilter()函数进行处理。
(3)在ChatWindow的eventFilter()函数中,根据条件判断,由于按下的是“Return”或“Enter”键,该按键行为导致submitChatText()函数被调用。然后返回true。 (4)由于ChatWindow的eventFilter()函数返回了true值,Qt事件系统将该次按键事件给过滤掉了,最终,chatEdit并没有接收到“Return”或“Enter”的按键事件。
如果在chatEdit控件中按下的并非“Return”或“Enter”键,那么按键事件流入ChatWindow的eventFilter()函数之后,将交由QWidget::eventFilter(watched, e)进行过滤,最终传递到chatEdit控件。 4.2 通用事件句柄的应用(event)
通过重载event()函数,可以在事件被特定的事件处理函数处理之前(像keyPressEvent())处理它。比如, 当我们想改变tab键的默认动作时,一般要重载这个函数. 在处理一些不常见的事件(比如:LayoutDirectionChange)时,evnet()也很有用,因为这些函数没有相应的特定事件处理函数. 当我们重载event()函数时, 需要调用父类的event()函数来处理我们不需要处理或是不清楚如何处理的事件。
下面这个例子演示了如何重载event()函数, 改变Tab键的默认动作: (默认的是键盘焦点移动到下一个控件上. )
bool CodeEditor::event(QEvent * event) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast
insertAtCurrentPosition('\\t'); return true; } }
return QWidget::event(event);//将其它事件交由父类的event()函数处理 }
4.3 特定事件处理函数--QTimerEvent的应用
Qt中的int QObject::startTimer ( int interval )函数可以开启一个定时器,其返回值为被创建的定时器的ID号(若开启定时器失败则返回0)。定时器开启后,每隔interval个毫秒就会产生一个timer事件。该timer事件会被事件循环捕获,然后被传递到接收对象,由接收对象的event()方法进行处理。下面以一个实例说明timer事件的应用。实例运行效果如图4-3-1所示:
图4-3-1 实例运行效果
QLCDNumber继承。其clockwidget.h文件内容如下所示: // clockwidget.h
#ifndef CLOCKWIDGET_H #define CLOCKWIDGET_H #include
class ClockWidget : public QLCDNumber
下面的代码实现一个数字时钟,构建一个名为ClockWidget的类,该类从
{
Q_OBJECT public:
ClockWidget(QWidget *parent = 0); protected:
void timerEvent(QTimerEvent *e);//负责处理接收到的timer事件 private:
int updateTimer, switchTimer;//存储创建的timer ID号 bool showClock;//用于切换时间与日期的显示 };
#endif // CLOCKWIDGET_H clockwidget.cpp文件内容如下所示: // clockwidget.cpp #include
ClockWidget::ClockWidget(QWidget *parent) : QLCDNumber(parent), showClock(true)
{//构造函数,在参数初始化类表中已经给showClock赋值为true setFrameShape(QFrame::NoFrame); setSegmentStyle(QLCDNumber::Flat);
updateTimer = startTimer(1000);//开启一个1秒间隔的定时器 switchTimer = startTimer(10000);//开启一个10秒间隔的定时器
QTimerEvent *e = new QTimerEvent(updateTimer);//创建一个QTimerEvent类型 //的事件对象,该对象以updateTimer作为时间间隔。及该事件每隔1秒发生一次 QCoreApplication::postEvent(this, e); }
void ClockWidget::timerEvent(QTimerEvent *e) {//处理timer事件的函数
if (!e) return;//检查指向事件的指针是否是安全有效 if (e->timerId() == switchTimer) showClock = !showClock; if (e->timerId() == updateTimer) { if (showClock) {
QTime time = QTime::currentTime();//获得当前系统时间
QString str = time.toString(Qt::LocalDate);//将时间转换成QString型字符串 setNumDigits(str.length());//设置LCD数字位数 display(str);//显示字符串 } else {
QDate date = QDate::currentDate();//获得当前系统日期
QString str = date.toString(Qt::LocalDate); //将日期转换成QString型字符串 setNumDigits(str.length());//设置LCD数字位数 display(str); //显示字符串 } } }
上述代码中,定时器用于触发QTimerEvent类型的事件。updateTimer与switchTimer存储了两个定时器的ID号,这两个ID号可用于在timerEvent()中对两个定时器加以区分。没有必要等上1秒钟之后再让控件显示,我们可以用updateTimer这个ID号手动发送一个timer事件,用QCoreApplication的postEvent()方法将事件发送出去。将目的对象指定为当前的widget。注意:手动创建QTimerEvent对象的目的就是要在updateTimer定时器自然到达触发事件之前,发送出一个事件。如果不是如此的话,是不需要手动创建QTimerEvent对象的。因为定时器到了所设定的事件间隔之后会自动触发一个QTimerEvent类型的事件的。
main函数实现代码如下所示: // main.cpp #include
QApplication app(argc, argv); ClockWidget w; w.show();
return app.exec();//事件循环 }
5 动态语言切换
Qt内部采用的全Unicode编码,这从根本上保证了多国语言界面实现的正确性和便捷性。Qt本身提供的linguist工具,就是来实现这个翻译过程的。实现多国语的步骤如下: 步骤1、在需要被翻译的字符串前面标识tr,如QString str=tr(“hello,world!”); ,这很重要,因为翻译工具会把源码中tr标识的字符串提取出来,翻译成其他语言,如果没有用tr标识的,不会被工具提取。在界面中输入的文字,默认已经是加上tr的了,所以在翻译时也能看见。建议:在程序中的字符串使用英文,汉语等通过多国语翻译来实现,而不要采取把汉字写在代码中。
在需要实现动态切换语言时,较好的方法是将所有需要动态切换的字符串集中在同一个函数中进行设定,如下:
// initGUI() 中会有大量的tr函数 void WizarDialog::initGUI() {
this->setWindowTitle(tr(\ Button->setText(tr(“Cancel”)); /* ...... */ }
步骤2、在工程文件***.pro中,添加一项
TRANSLATIONS += ***_CN.ts ****_EN.ts \\
***.ts
扩展名为.ts是翻译的源文件,表示生成这几个文件。一般我们会在命名中把区域加进去,更好的注释这些文件是用于什么语言的,比如中文大多会这样命名 myapp_zh_CN.ts, zh_CN表示的就是中国。
步骤3、使用lupdate工具提取翻译源文件, 命令是这样的 lupdate ***.pro
lupdate会解析***.pro即工程文件,生成TRANSLATIONS中的 ***.ts 几个文件,这些文件可以被linguist工具打开,按照提示一个一个的翻译成需要的文件,然后保存就OK。 (lupdate 是一个命令行工具,用于在指定的源文件、头文件及Qt Designer 接口文件中寻找可翻译的字符串,然后产生或更新.ts翻译文件。需要翻译的文件以及需要更新的文件可以在命令行下设置,也可以将一个.pro文件作为命令行参数传递给lupdate命令。由lupdate产生的.ts文件将交给翻译人员,翻译人员使用Qt Linguist读取文件,并插入翻译内容。.ts文件是xml格式的。Lupdate还可以处理XLIFF格式(Localization Interchange File
Format)的文件,这种文件以.xlf作为后缀名。可以使用lupdate –help 命令获取该命令所支持的操作列表。)
步骤4、用linguist翻译***.ts文件中的词条(每个tr项包含的内容)。打开刚才 的***.ts文件,linguist是在qt的bin的目录下的一个界面工具。。在linguist中用菜单栏file ->open 打开相应的.ts文件。打开后会看到左边是相应的类,右边的上半部是相应的类里面提取出来供翻译的内容,下半部是要翻译 的语言的相应的东西,即为需要输入中文的地方。在翻译中,要注意标点符号的翻译最好还是用英文输入状态下的标点符号。当翻译状态前出现绿色勾的提示为翻译 成功,叹号的表示没有翻译对,交叉的表示没有翻译。 步骤5、使用lrelease工具发布翻译文件的二进制文件, lrelease ***.pro
这样在程序运行时载入会大大的加快速度。这个工具会提示你多少语句被翻译,多少被忽略了等。生成的文件是 ***.qm,于同名的 ***.ts只是换了一个扩展名。而这才是我们程序需要使用到的文件。
(lrelease是一个命令行工具,用于从.ts文件中产生出.qm文件。.qm文件格式是压缩二进制格式,它将被本地应用程序使用。.qm文件为翻译器提供极其快速的查找。Lrelease处理的.ts文件可以在命令行指定,也可以由Qt的.pro工程文件间接给出。)
注:本步骤也可以在linguist中完成。用linguist界面工具里面菜单file里面的release... 步骤6、使用***.qm文件 切换语言分为两种情况:
1. 程序载入的时候,根据当前的区域设置,自动选择语言包(.qm),即可; 2. 要求在程序运行过程中动态切换语言;
第一种情况,一般在main函数中程序启动的部分加入如下代码:
QString locale = QLocale::system().name());// for example: zh_CN, en_US QTranslator *translator = new QTranslator(app);
translator->load(QString(\ // 会在当前目录下的
//language目录下寻找,可以不带\后缀名
app->installTranslator( translator ); // 安装翻译器
第二种情况,我们假设有一个QComboBox连接了changeLang的槽: connect(langCombo, SIGNAL(currentIndexChanged(int)),
this, SLOT(changeLang(int)) );
// 载入不同的语言包
void WizarDialog::changeLang( int langIndex ) {
QTranslator *translator = new QTranslator(qApp); switch( langCombo->currentIndex() ){ case 0:
translator->load(QString(\ break; case 1:
translator->load(QString(\ break; case 2:
translator->load(QString(\ default: break; }
qApp->installTranslator( translator ); //截止到该步骤只是加载安装了翻译器 this->initGUI(); //该函数中所包含的tr,将会调用之前加载安装的翻译器 }
// initGUI() 中会有大量的tr函数 void WizarDialog::initGUI() {
this->setWindowTitle(tr(\ /* ...... */ }
这两种情况,也可以复合起来用。
需要说明的是,一般我们使用设计器来设计界面UI,也就是程序源码中我们看到的 ***.ui文件,在载入翻译器后,我们应该调用 ui->retranslateUi() ,这个函数实际上就是把界面控件的text重新载入一遍,可以在 ui_***.cpp中看到该函数的实现。
注:QTranslator在load以后,并没有把qm文件中的数据拷贝一份,而是在需要的时候去查询字符串。如果qm在这期间被删除或修改,对程序都是有影响的。扩展开来,QTranslator必须保证要一直有效,如果在函数中定义的局部变量,函数结束后就自动释放掉了,那么翻译工作就不能正常进行。所以建议在private中定义个成员变量 QTranslator* app_translator;来确保整个翻译工作的正确性。
6 数据结构
7 硬件加速:OpenVG、OpenGL、DirectFB
Qt中提供的控件、动画框架、软件框架等资源,可以方便的实现UI中所设计的各种场景(动作、动画等)。对于同一种场景,在Qt中或许可以找到多种解决方案。当把Qt实现的代码运行在嵌入式系统中,由具体的电视芯片来运行时,代码的运行效率将变成最主要的问题。
8 编程技巧
8.1 借用。。。。生成.cpp与.h文件及代码 8.2 借用Qt Designer自动生成的代码
8.3 快捷键“F2”可以追查光标所指处item的定义
9 注意事项
9.1 如果child设定了parent,请不要自己删除child,因为会引起double delete,导致系统死掉 9.2
正在阅读:
基于Qt的电视机UI实现技术03-11
学习高数的心得体会12-21
EVA热熔胶03-15
贵州2014年公务员面试指导:深度剖析排序类题(精)10-23
家乡的变化作文400字07-16
2017年宁波大学法学院821综合课2之民法学考研题库04-28
后经济危机时代下的中国面临的机遇与挑战08-24
围墙广告合同03-21
某西餐厅装修改造工程监理细则 - 图文05-25
食品微生物教学实施方案03-08
- exercise2
- 铅锌矿详查地质设计 - 图文
- 厨余垃圾、餐厨垃圾堆肥系统设计方案
- 陈明珠开题报告
- 化工原理精选例题
- 政府形象宣传册营销案例
- 小学一至三年级语文阅读专项练习题
- 2014.民诉 期末考试 复习题
- 巅峰智业 - 做好顶层设计对建设城市的重要意义
- (三起)冀教版三年级英语上册Unit4 Lesson24练习题及答案
- 2017年实心轮胎现状及发展趋势分析(目录)
- 基于GIS的农用地定级技术研究定稿
- 2017-2022年中国医疗保健市场调查与市场前景预测报告(目录) - 图文
- 作业
- OFDM技术仿真(MATLAB代码) - 图文
- Android工程师笔试题及答案
- 生命密码联合密码
- 空间地上权若干法律问题探究
- 江苏学业水平测试《机械基础》模拟试题
- 选课走班实施方案
- 电视机
- 基于
- 实现
- 技术
- 一年级思品- 副本
- 《以国学经典为载体,培养小学生良好行为习惯策略与方法的实践研究》课题研究中期报告
- 清华班组长-公众演讲与语言表达艺术自测
- 2016年全国大学生西门子杯工业自动化挑战赛ITEM2逻辑控制赛项样题高校组
- 讲话稿:市委书记在全市重点项目集中观摩推进会上的讲话
- A组高一
- 关于工商管理类专业会计课程实践教学的研究-2019年精选文档
- 用人单位未为员工购买社会医疗保险的法律责任
- 2016-2021年天然蜂蜜行业深度调查及发展前景研究报告
- 优化语用课堂注重语用能力 - 《雷雨》教学实录及评析档
- 供应商开发表单 - 图文
- JJF1033试题
- 小区道路及排水施工组织设计1
- 古诗应用题
- 2018届江苏省高三高考学科基地密卷英语试题(四)(word版)
- 深圳市科研人员入库操作指南
- 总包管理方案
- 监理员考试 质量控制(第三章) 二、电力建设工程质量控制
- 美国法特瑞互助保险公司(FM Global)简介
- 省政府办公厅转发省经济和信息化委 省发展改革委江苏省工业和信息产业结构调整限制淘汰目录和能耗限额通知 - 图文