蛇舞2013抽奖程序的编写和QT技巧问题梳理总结

    为适合公司年会需要,同时为QT练手,编写了2013蛇年抽奖程序.同需求方交流过几次增加和调整了一些功能.再次对其中的知识进行梳理总结.
本文讨论点/技巧点:

●当前路径和运行路径
●QPixmap: It is not safe to use pixmaps outside the GUI thread
●Sleep与QThread::usleep
●添加GraphicsItem到场景
●获取分辨率
●全分辨率支持
●全区域绘图
●缩放中心问题
●平铺背景图
●控制窗口状态
●多个同名对象发射的SIGNAL到同一个SLOT无法判断谁是谁的问题
●GraphicsItem的ZValue问题
●绘制文字
●ProgressBar自动关闭问题
●QT5也不是完美的
●QT打包发布问题

▼当前路径和运行路径
    通常当前路径是用来给用户选文件用的,他会记忆上次使用的路径.对程序来讲运行路径更有用的多.QT获取运行路径的方法是QApplication::applicationDirPath();获取当前路径是QDir::CurrentPath();都是静态函数.QT在DLL中获取DLL自身的路径目前还未试验.

▼QPixmap: It is not safe to use pixmaps outside the GUI thread
    在线程中加载大量图片分担费事操作,对应4.8.3版本会有如上提示,但是使用5.0版本则不会. 按理来说在GUI线程以外加载QPixmap是个很单纯的事情,不会对UI造成不良影响,所以5.0是一个进步
    那么既然不能在线程中加载QPixmap,4.8.2版本有什么办法在GUI线程加载又不卡死UI呢.这里提供一种办法.因为有一个预读界面,读取完成以前不加载主界面,加载图片在预读界面完成.

    // 加载进度
    Loader *load = new Loader();
    load->show();
    while(!load->isDone()  ) //
    {
        QApplication::processEvents(QEventLoop::AllEvents,30);
        //QThread::usleep(30); 4.8.3是保护成员
    }

Sleep与QThread::usleep
    在windows中非常顺手的Sleep函数在其他平台就要多操好多心.还好QT也有类似的函数QThread::usleep.不过在4.8.3中他是protected成员.但是在5.0中是公用了.QT5.0又是一个进步.
    参见上面的代码,QApplication::processEvents的第二参数就有Sleep毫秒的功能,真是省心了.


▼添加GraphicsItem到场景
    添加GraphicsItem到场景有两种方法,一是创建的时候提供父对象,他将自动使用父对象的坐标系并显示出来,不需要调用show;二是作为顶层对象,父不是GraphicsItem而是场景GraphicsScene,那么需要用scene来添加Item,同样不需要show就会显示.但是坐标不确定在哪.

    Photo *pp = mPhotos.at(i);
    this->scene()->addItem(pp);

▼获取分辨率

    QDesktopWidget *w = QApplication::desktop();
    gSceneWidth = w->width();
    gSceneHeight = w->height();

▼全分辨率支持

    因为是相片相关的程序,拉伸图片是不能接受的.场景容器GraphicsView能够实现整体的拉伸变幻.重新实现resizeEvent函数,在里面调用fitInView(sceneRect(),Qt:KeepAspectRatio);将实现保持宽高比的缩放,最后保证 第一个参数指定的矩形一定会在View中被完整的看到.
    fitInView完成之后,Graphics区域内坐标原点经常处在窗口左上角略右下的位置,可以自己实现类似功能,保持原点在左边缘或上边缘
void View::__myFitInView(const QSize &size)
{
    QTransform _tform;
    qreal ScaleX = (qreal)size.width()/gSceneWidth;
    qreal ScaleY = (qreal)size.height()/gSceneHeight;
    _tform.scale(ScaleX,ScaleY);
    this->setTransform(_tform,false);
    this->centerOn(QPointF(gSceneWidth/2,gSceneHeight/2));
}

其中gSceneWidth代表作为参照的宽度,gSceneHeight是作为参照的高度.通常取程序启动是的全屏分辨率.

▼全区域绘图
    有这一步还不够,当窗口或者全屏时屏幕宽高比不同,不同情况下可能左右也可能上下会显示出更多的内容.迫切需要知道整个GraphicsView可见区域对应Graphics区域内坐标系的矩形. 我们在resizeEvent的时候计算更新和保存这个值.

    QPointF lt,rb;
    lt = this->mapToScene(0,0);
    rb = this->mapToScene(this->width(),this->height());
    mRectScene.setCoords(lt.x(),lt.y(),rb.x(),rb.y());
    然后绘图将时刻提醒自己是在一个几乎肯定会比预想要大一些的区域画图.例如要在正中间画一张牌,牌的x坐标应该是(mRectScene.width()-card.width())/2+mRectScene.x().很多坐标不准确都是这个问题.


▼缩放中心问题
    GraphicsItem可以支持缩放scale();当时它是以对象自己坐标系的原点作为缩放中心.一个默认的QGraphicsPixmapItem是以图片左上角作为对象坐标系原点的.这样对他进行缩放就总是以左上角放大缩小,在很多地方这种效果是不满足需求的.所以需要定义一个以图片中心为原点的GraphicsItem对象

class BigBack : public QGraphicsItem
{
public:
    BigBack(const QPixmap &pix)
        : _pix(pix)
    {
    }
    void adjustSize(int width,int height)
    {
        _pix = _pix.scaled(width,height,Qt::IgnoreAspectRatio,Qt::SmoothTransformation);
        update();
    }

    virtual QRectF boundingRect() const
    {
        return QRectF(-_pix.width()/2,-_pix.height()/2,_pix.width(),_pix.height());
        //return QRectF();
    }
protected:
    virtual void paint ( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0 )
    {
        painter->drawPixmap(-_pix.width()/2,-_pix.height()/2,_pix);
    }
private:
    QPixmap _pix;
};
以上代码示范了一个以图片中心为对象中心的对象.adjustSize可以接受缩放命令(IgnoreAspectRatio为 拉伸方式),我用它来展示我的拉伸背景图.


▼平铺背景图
    实现平铺背景图的方式有很多,根据需要.有在paint中重画的,有用styleSheet.这里提供一种简单的办法setBackgroundBrush(QPixmap)

▼控制窗口状态
    QT控制窗口状态非常方便windowState(),setWindowState()将窗口置最大化,最小化,普通化,全屏化都很容易,唯一要注意的是获取值是复合值,要用&判断.

    if(event->key() == Qt::Key_Escape)
    {
        if(this->windowState()&Qt::WindowFullScreen)
            this->showNormal();
        else this->showMinimized();
    }

    以上的示例演示了如何在keyPressEvent中处理Esc按键,将全屏切到普通,非全屏切到最小化.QT还提供一套便捷函数,直接就是showMinimized,showNormal等

▼多个同名对象发射的SIGNAL到同一个SLOT无法判断谁是谁的问题

    例如计算器程序,每个按钮需要一个onClick()函数;多个并发动画,动画完成finished()后都有同样的事情要做.但是在槽中,到底是谁发射的SIGNAL,无法知道.如今使用QSignalMapper后就能实现这一点.我们将原本connect(ani_pos,SIGNAL(finished()),this,SLOT(onFinished()))的连接方式,改成经由signalMapper转接.
finished连接到signalMapper的map()槽,同时告诉signalMapper,发射信号的这个对象的key值是什么

connect(ani_pos,SIGNAL(finished()),signalMapper,SLOT(map()));
            signalMapper->setMapping(ani_pos,curNum);


这个key值将来被用来传递给最终槽函数.signalMapper支持int,QString,QWidget*,QObject*类型的key值传递.你可以创建自己想要的接受槽函数,如onFinished(int id),然后将mapped(int)与它连上.

    signalMapper = new QSignalMapper(this);
    connect(signalMapper,SIGNAL(mapped(int)),this,SLOT(onFinish(int)));
这样在onFinish中我可以知道到底是哪个id的动画执行完毕了.


▼GraphicsItem的ZValue问题
    通过setZValue可以设置对象的摆放层次,ZValue越大,摆放越上面,也就是比较后画,会覆盖别人.现在我有一批对象,全部都是相同的ZValue,他们会按创建和加入到场景的顺序,越后加的越上面.并且保持不变.这样当我想实现将底下的对象抽出来盖在最上面,不修改ZValue是不能实现的. (后加的图片总是挡住)
    如此有两种办法,一种是每次抽牌都修改ZValue到一个更大的值,当然累加可以设少一点,如+0.00001.它是qreal型,精度比较高的.另一种是把所有牌的zValue都减0.0001,置顶的牌设置为x.取值上x只要稍微大一点就好.
    两种方法,第一种方法的好处是效率好,缺点是如果界面有需要置顶的对象的话,也许某天图片的z值就超过那个置顶对象了. 第二种方法没有第一种方法的问题,他的问题是效率有拖累.

绘制文字
    在设置文字信息的时候就计算好文字的宽高,在paint的时候直接画好是个不错的办法

    void setName(const QString &_name)
    {
        mName = _name;
        QFont f("arial",36,36);
        //f.setPixelSize(32);
        QFontMetricsF m(f);
        mTWidth = m.width(mName);
        mTHeight = m.height();
    }


    virtual void paint ( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0 )
    {
        painter->drawPixmap(-_pix.width()/2,-_pix.height()/2,_pix);
        if(bOpen && mName.size())
        {
            painter->setPen(Qt::NoPen);
            painter->setBrush(QColor(0, 0, 0, 120));
            painter->drawRoundedRect(-mTWidth/2-15,_pix.height()/2-mTHeight-15-20,mTWidth+30,mTHeight+30-20,3,3);
            painter->setPen(QColor(255,255,255,255));
            painter->setFont ( QFont("arial",36,36) );
            painter->drawText(-mTWidth/2-15,_pix.height()/2-mTHeight-15-20,mTWidth+30,mTHeight+30-20,Qt::AlignCenter,mName);
        }
    }
绘制文字是依赖pen和font的,绘制矩形则同时依赖pen和brush. 而计算文字的绘制区域是用QFontMetricsF 


▼ProgressBar自动关闭问题

class ProBar : public QProgressBar
{
    Q_OBJECT
public :
    ProBar(){
        this->setWindowFlags(Qt::ToolTip|Qt::FramelessWindowHint);
在4.8.3中,此处初始化setWindowFlags(Qt:Popup)的话,运行时只要窗口失去焦点,该进度条窗就被自动关闭.但是修改为上面的ToolTip和FramelessWindowHint则正常.QT5.0则都是正常的.


▼QT5也不是完美的
    在QT5+MSVC10+WIN7的环境中,QDateTime的头文件会报一些语法错误,需要在所有直接和简介包含QDateTime的地方之前定义#define NOMINMAX

▼QT打包发布问题
    QT4程序的强依赖只有QtCore4.dll,UI程序加QtGui4.dll,网络程序加QtNetwork4.dll等等,非常干净利索,而QT5程序的强依赖就变成Qt5Core.dll,UI程序加Qt5Gui.dll,Qt5Widgets.dll,这些都还好,不能接受的是它拖泥带水的还要依赖icudt49.dll,icuin49.dll,icuuc49.dll,libEGL.dll,libGLESv2.dll,还有D3DCompiler_43.dll可能Win7下需要.不知道为什么要打的那么散,打散之后最后的依赖库尺寸不减反增.
    QT打包还有个很需要注意的问题,很多扩展库以plugins的形式存在,如qico4.dll,qjpeg4.dll,qsqlite4.dll等,这些库的寻找方法竟然是写死在QtCore4.dll里面的. 假如不篡改QtCore4.dll的话,在客户机器上,我们必须创建一个一模一样的路径存放那些扩展库文件,这样这些功能才能被正确加载(QT5一样).
    所以,如果你有足够的恒心毅力,不妨自己去改改plugins的动态加载,然后重编一下QT库.或者用二进制办法从固定的地址修改固定的DLL,为其配置路径.


抽奖程序已经由OSC代码分享 http://www.oschina.net/code/snippet_916295_17619

秉承开源精神,以上内容欢迎纠正,补充!

尊重原创,尊重知识产权,从我做起.@2013/01/25

你可能感兴趣的:(技巧,qt,sleep,Signal,打包发布)