QT显示机制
2013-03-23
AlbertChen
标签: QT

QT被Nokia收购了,我们以后的项目用QT做UI开发的可能性也不大,这些都无所谓,嵌入式系统的UI开发包大体架构应该还是相通的,深入了解QT 对以后理解新的平台应该是有帮助的,QT 有很多免费的版本在网上很容易找到并下载,这样的话大家都可以参看源代码共同学习。我不是个技术水平高的人,所以大家不要给我扔砖头。

QT的窗体系统的管理,窗体事件是如何派发的,在前一篇《QT 窗体事件底层派发机制》中已经作了简要的分析,QT 的显示机制算是对上一篇文章的补充。

了解QT 显示机制,最重要的就是要了解QT 是如何管理窗体的显示区域的,这里有个重要的类:QRegion, 在QT 中可以通过QRegion 定义一个窗体的显示区域,也可以通过QRegion 定义窗体的可修改区域,比如在QPainter()中通过QPainter::setClipRect 设定一个区域,我们绘图则只能在这个区域,此区域外绘图都是无效的。通过QRegion 可以作一系列的逻辑运算,如两个区域相加,相减等。QRegion定义的区域不一定是连续的,但一定是由封闭的区域组成的,我们常会碰到一个窗体的显示区域被其他窗体分割为几块的情况。QT 对这些显示区域的管理,类似于对窗体的管理,也是通过服务器与客户端的方式。参照以前的说法Server 表示为全局的Global ,客户端为本地得Local。那么WindowsServer管理一个全局的显示区域即所有的Top-Level widget 显示区域。而其他的child windget 的管理则在每一个QT 应用程序中由QWSRegionManager 管理,Top_Level widget 显示区域也会加载在其中,这个不难理解,因为Server 只是负责将窗体事件发送到客户端,具体处理还是由客户端来操作。具体的流程还是来看代码吧。

显示区域管理者QWSRegionManager 的初始化

服务器:

通过调用openDisplay()。

客户端:

在QWSDisplayData 类的构造函数中通过调用QWSDisplayData::init()完成。

考虑一个比较简单的情况,我们要显示的widget 是一个Top_Leverl widget。在调用Show()函数中,这个widget 将通过showWindows()向服务器请求做三件事:(以下窗体是指在global windows statck中的TOP_Level widget)

  • 调用QWSDisplay::requestRegion 向服务器请求窗体显示区域。
  • 调用QWSDdisplay::setAltitude 向服务器请求设置窗体的优先级。此优先级是指在windows statck 中的位置,而不是指QWSWidow 中的窗体优先级属性。Windows statck 中的第一个窗体就是显示在LCD上最前面的窗体。
  • 调用QWSDisplay::requestFocus 向服务器请求设置窗体为焦点窗体。焦点窗体能接收Key, Mouse 事件,但不是所有的焦点窗体都能接收Key,Mouse 事件,如果有窗体设置为GrabKey 或则GrabMouse 则Key, Mouse 事件将分别传递至此窗体。

下面将通过代码分析winddows Server 对这三个请求的处理过程:

一: QWSDisplay::requestRegion 的处理

void QWSServer::invokeRegion( QWSRegionCommand *cmd, QWSClient *client )

{

................

QRegion region;

region.setRects(cmd->rectangles, cmd->simpleData.nrectangles);

if ( !region.isEmpty() )

changingw->setNeedAck( TRUE );

bool isShow = !changingw->isVisible() && !region.isEmpty();

setWindowRegion( changingw, region ); //***设置窗体显示区域

syncRegions( changingw ); //***通知客户端 刷新显示区域

if ( isShow )

emit windowEvent( changingw, Show );

if ( !region.isEmpty() )

emit windowEvent( changingw, Geometry );

else

emit windowEvent( changingw, Hide );

if ( focusw == changingw && region.isEmpty() )

setFocus(changingw,FALSE);

.................

}

invokeRegion 调用setWindowRegion 设置窗体显示区域,调用syncRegions 通知客户端 刷新显示区域,并产生一些窗体事件如:Show, Geometry,Hide 。

setWindowRegion 函数的实现如下:

QRegion QWSServer::setWindowRegion( QWSWindow* changingw, QRegion r )

{

QRegion exposed;

if (changingw) {

changingw->requested_region = r;

r = r - serverRegion; //exposed不为空则有显示区域被释放

exposed = changingw->allocation() - r; //低等级窗体增加可见区域

} else {

exposed = serverRegion-r;

serverRegion = r;

}

QRegion extra_allocation;

int windex = -1;

bool deeper = changingw == 0;

for (uint i=0; i

QWSWindow* w = windows.at(i);

if ( w == changingw ) {

windex = i;

extra_allocation = r - w->allocation(); //如果extra_allocation不为空

deeper = TRUE; //需要增加新的新的显示区域

} else if ( deeper ) {

w->removeAllocation(rgnMan, r);//低优先级窗体去掉被覆盖的区域

r -= w->allocation();//如果r为空 则更低优先级的窗体被完全覆盖

} else { //如果窗体是第一次调用Show 直接走这

//higher windows

r -= w->allocation();//如果r为空 则窗体被高优先级窗体完全覆盖

}

if ( r.isEmpty() ) { //窗体被完全覆盖

break; // Nothing left for deeper windows

}

}

...................

if ( changingw && !changingw->requested_region.isEmpty() )

changingw->addAllocation( rgnMan, extra_allocation & screenRegion );

//为changingw窗体增加新的可见区域 置modifed标志为TRUE

else if ( !disablePainting )

paintServerRegion();

exposeRegion( exposed, windex+1 );//增加低级窗体可见区域。

return exposed;

}

注:增加新的显示区域不一定是整个显示区域的面积增大了,而是显示区域的块变多了。 一个显示区域可能由多个不连续和连续的Region 组成。

void QWSServer::exposeRegion( QRegion r, int start )

{

r &= screenRegion;

for (uint i=start; i

if ( r.isEmpty() ) //可见区域为空

break; // Nothing left for deeper windows

QWSWindow* w = windows.at(i);

w->addAllocation( rgnMan, r ); //增加新的可见区域 置modifed标志为TRUE

r -= w->allocation(); //r 更低级窗体可见区域

}

dirtyBackground |= r; //得到需要刷新的背景区域 如果r为空 则新增区域为0

}

exposeRegion 为低等级窗体增加可见区域。

syncRegions:此函数主要是向客户端发送RegionModified 事件,真正的绘图也是由客户端来完成。 还是通过代码来分析:

void QWSServer::syncRegions( QWSWindow *active )

{

rgnMan->commit(); //拷贝数据到一段共享内存,服务器为读写权限,客户端为只读

notifyModified( active );//通过客户端显示区域已更改,客户端绘制相关区域

paintBackground( dirtyBackground );//绘制背景区域修改部分。

dirtyBackground = QRegion();

}

void QWSServer::notifyModified( QWSWindow *active )

{

// notify active window first

if ( active )

active->updateAllocation(); //首先通知active 窗体

// now the rest //通知所有modified标志为TRUE的窗体

for (uint i=0; i

QWSWindow* w = windows.at(i);

w->updateAllocation();

}

}

void QWSWindow::updateAllocation()

{

if ( modified || needAck) {

c->sendRegionModifyEvent( id, exposed, needAck ); // 发送消息

exposed = QRegion(); //复位低级窗体新增显示区域

modified = FALSE; //modified为真表示窗体的显示区域被修改。

needAck = FALSE;

}

}

客户端对RegionModifyEvent的处理。

客户端接收到消息后会调用translateRegionModifiedEvent函数来进行处理

bool QETWidget::translateRegionModifiedEvent( const QWSRegionModifiedEvent

*event )

{

QWSRegionManager *rgnMan = qt_fbdpy->regionManager();

if ( alloc_region_index < 0 ) {

alloc_region_index = rgnMan->find( winId() ); //从共享内存中得到region索引

if ( alloc_region_index < 0 ) {

return FALSE;

}

}

QWSDisplay::grab();

int revision = *rgnMan->revision( alloc_region_index );

if ( revision != alloc_region_revision ) {

alloc_region_revision = revision;

QRegion newRegion = rgnMan->region( alloc_region_index );//得到显示区域

QWSDisplay::ungrab();

alloc_region = newRegion;

// set children's allocated region dirty

................

} else {

QWSDisplay::ungrab();

}

if ( event->simpleData.nrectangles )

{ // alloc_region >= exposed

QRegion exposed; //需要刷新区域的大小

exposed.setRects( event->rectangles, event->simpleData.nrectangles );

QSize s( qt_screen->deviceWidth(), qt_screen->deviceHeight() );

exposed = qt_screen->mapFromDevice( exposed, s );

qwsUpdateActivePainters();

repaintDecoration( exposed, FALSE );//绘制窗体的一些修饰如边框,caption等

repaintHierarchy( exposed, FALSE ); //绘制窗体显示区域及子窗体通过发送

} //PaintEvent事件到各窗体

qws_regionRequest = FALSE;

return TRUE;

}

repaintHierarchy函数中所有需要刷新的子窗体都会收到Paint事件。在Paint事件中,开始绘图。显示中只刷新exposed这个区域而不是将分配的区域alloc_region 全部刷新一次,这样做可以提高效率。

二:QWSDdisplay::setAltitude 的处理

invokeSetAltitude(const QWSChangeAltitudeCommand *cmd,

QWSClient *client)

{

int winId = cmd->simpleData.windowid;

int alt = cmd->simpleData.altitude;

bool fixed = cmd->simpleData.fixed;

...................

QWSWindow* changingw = findWindow(winId, 0);

...................

changingw->setNeedAck( TRUE );

if ( fixed && alt >= 1) {

changingw->onTop = TRUE;

}

if ( alt < 0 )

lowerWindow( changingw, alt ); //窗体优先级下降

else

raiseWindow( changingw, alt ); // 提升窗体优先级

if ( !changingw->forClient(client) ) {

refresh();

}

}

invokeSetAltitude 通过调用lowerWindow,raiseWindow 来调整窗体的优先级,如果一个Widget 被显示,即调用Show 此时alt == 0; 如果alt == 1 则此窗体应该为最上层,如果alt == 2则窗体位FULL-SCREEN 即全屏显示的窗体,可以通过setWFlags(WStyle_StaysOnTop) 来设定这个属性。 优先级较高的窗体将被优先显示, 在没有显式通过SetRegionPriority 命令来改变窗体优先级的话, 在Windows Stack 中窗体将按照后进的优先级较高为原则。 可以参考insertPrioritizedWindow 函数,在qt-embedded-free-3.3.6 可能没有这个函数,因为在这个版本中不存在窗体优先级,除了WStyle_StaysOnTop 属性的窗体为第一级优先级外,其他窗体都按照后进的优先为原则。

三:QWSDisplay::requestFocus 的处理请参考invokeSetFocus 函数。

绘图的底层操作

LCD 屏幕上的每个点都与显示缓冲区中的数据有特定的关系,对16 位色的显示缓冲区,每两个字节对应LCD 上的一个像素,因此我们只要修改缓冲区某两个地址上的数据就可以改变LCD 上这个点的色彩,如果我们能够得到这个显示缓冲区的地址,应用程序编写者就可以抛开驱动程序那些抽象的接口,直接对显示缓冲区操作。Linux 上有这种专门的驱动支持就是Framebuffer 驱动程序,Framebuffer 也就是帧缓冲,驱动程序创建一个缓冲区用做显示buffer。应用程序开发人员可以通过MMAP 将Framebuffer设备重新映射,这样我们对LCD 的操作就可以象操作一个二位数组一样方便。QT 正是这么做的。QWidget 继承了QPaintDevice,通过graphicsContext 接口,我们可以方便地得到这个窗体的设备上下文。这一过程是如何实现的呢?

QGfx * QWidget::graphicsContext(bool clip_children) const

{

QGfx * qgfx_qws;

qgfx_qws=qwsDisplay()->screenGfx(); //创建设备上下文

updateGraphicsContext( qgfx_qws, clip_children );

return qgfx_qws;

}

Qapplication 调qt_get_screen( int display_id, const char *spec )来获得相应的驱动程序。创建驱动程序后调用qt_screen->connect 将客户端与FrameBuffer 即帧缓冲映射起来,具体代码可以参考QLinuxFbScreen 。当一个窗体就收到PaintEvent 事件后就可以创建QPaint 对象绘图,通过设备上下文获得的绘图接口实际上都是在对帧缓冲进行操作。 如果驱动程序不支持双缓冲操作的话,绘图的结果将直接显示在LCD 上。

可能会用到的工具/仪表
本站简介 | 意见建议 | 免责声明 | 版权声明 | 联系我们
CopyRight@2024-2039 嵌入式资源网
蜀ICP备2021025729号