Qt软件做到唯一单例的显示



  • 0_1526438042282_1.png
    使用技术手段让Qt开发的软件做到唯一单例的显示,方法有很多。目前总结起来有几种:

    1. 启动的时候写入一个临时文件,并且在关闭程序的时候删除之。如果有第二个实例启动了,那么一旦检测到文件的存在,就会删除。这个方法是很多软件常用的方法。问题在于,一旦程序异常退出了,临时文件就没法得到删除,导致新的实例无法启动。
    2. 使用QLocalSocket来做。具体就是监听端口,一旦有第二个实例启动的话,那么会显示端口被占用,这样也可以达到单例的显示效果。
    3. 使用共享内存来做,目前的操作暂时不了解

    Qt提供了QtSingleApplication这个类,但是似乎已经失传了?github上或许可以看到一些端倪。不过现在第三方开发者已经将其升级到更加新的版本了,虽然也叫QtSingleApplication,但是要复杂,能够解决不同异常。它的下载地址是:

    https://github.com/itay-grudev/SingleApplication

    首先我们来看看别人是如何使用QtSingleApplication的吧。从网上摘抄一些经典的内容:

    @jiangcaiyangQt软件做到唯一单例的显示 中说:

    心得1:
    单例执行一般有两种需求,1种是在本机上,这个程序最多只能运行一个实例,1种是,本机上该exe文件对应的程序只能运行一个实例,但是如果别的位置还有该exe的实例,另外别的该exe实例也可以运行。如果需求是前者,那么就用一个固定的字符串作为标记,无论本机上有多少个该应用程序副本,同时最多也只能有一个实例在运行。如果是需求2,则使用exe对应的路径作为APP_ID。
    需要说明的是,实现单例运行的逻辑是根据APP_ID,在临时目录下存在一个对应于APP_ID的文件。如果开启了一个实例,然后删除对应的文件,然后就可以再次开启一个实例。
    特别需要说明的是,如果是在linux开发,涉及到局域网内远程计算机的执行该程序,无论是使用固定字符串还是文件路径作为APP_ID做为标记都会导致本机、远程计算机同时只能运行一个实例。但有时需求并不是这样,而是一个终端可以有一个实例运行,那么这时, 把APP_ID的参数加上远程访问的计算机IP信息加上。
    心得2:
    上面的例子简单粗暴的处理,如果已存在运行的实例,就退出。实际应用中往往需要执行的是如果有实例在运行,要么给出提示信息,要么直接把正在运行的实例的窗口显示出来。
    QtSingleApplication提供了一个public slots: bool sendMessage(const QString& message, int timeout = 5000);和一个信号void messageReceived(const QString& message);
    那么逻辑就应该是这样的,
    a. 判断是否有实例在运行 b.如果有,则发送一条信息 c.主窗体有个槽函数与QtSingleApplication的messageReceived的信号连接起来,当有程序运行时发现已经有实例运行时,发动了信息,已运行的实例接收到信息就会发送messageReceived的信号,从而去实行主窗体的槽函数。
    我在具体使用的时候,会把程序运行的路径作为信息传递,这样主窗体的槽函数可以对这个路径参数判断,判断是否和自己所在实例的路径一致,如果一致则主窗体前端显示,否则不一致,说明是别的路径下的执行程序,然后给出提示信息。
    另外,主窗体对应的槽函数如果是简单的show(),主窗体会被激活,并显示出来,但是可能不是会在最前端显示。我一般会临时更改主窗体的windowsflag属性,添加最前端显示的flag,然后显示,然后取消掉最前端显示的flag,然后再显示。这样就会保证窗体是在最前端显示。特别说明的是,取消最前端显示的flag后,必须再执行show,否则窗体会隐藏不显示.代码示例如下:

    void FrmMain::sltMessageReceived(const QString &msg)
    {
    Qt::WindowFlags flags = windowFlags();
    flags |= Qt::WindowStaysOnTopHint;
    setWindowFlags(flags);
    show();
    flags &= ~Qt::WindowStaysOnTopHint;
    setWindowFlags(flags);
    showNormal();
    if(msg != qApp->applicationFilePath()) {
    const QString& warnStr(tr("Only on instance is allowed. Close running application first."));
    QMessageBox::warning(this, tr("Warn"), warnStr, QMessageBox::Ok);
    }
    }
    


  • @jiangcaiyang 有种windows方法

    bool checkOnly(){
         //创建互斥量
        HANDLE m_hMutex = CreateMutex(NULL,FALSE,TEXT("label_233"));
        //检查错误代码
        if(GetLastError() == ERROR_ALREADY_EXISTS){
            //如果已有互斥量存在则释放句柄并复位互斥量
            CloseHandle(m_hMutex);
            m_hMutex = NULL;
            //程序退出
            return false;
        }
        else{
            return true;
        }
    }
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        QDir::setCurrent(a.applicationDirPath());
    
        //检查程序是否已经启动过
        if(checkOnly()==false){
            QMessageBox::information(0,"Title","程序已经启动了",QMessageBox::Ok);
            return 0;
        }
    
        Widget w;
        w.show();
    
        return a.exec();
    }
    


  • 这也是一个较好的方法吧,不过Windows平台相关的,不太利于跨平台,所以最好这段代码封装一下。😊 😊 😇



  • dtk中有单例的实现

    /**
     * @brief DApplication::setSingleInstance marks this application to be single instanced.
     * @param key is used as the unique ID of every application.
     *
     * It should be in form of dde-dock, dde-desktop or dde-control-center etc.
     *
     * You can use dbus implement if you use an sandbox like flatpak and so on, just
     * build with DTK_DBUS_SINGLEINSTANCE
     *
     * @return true if succeed, otherwise false.
     */
    bool DApplication::setSingleInstance(const QString &key)
    {
        return setSingleInstance(key, SystemScope);
    }
    
    bool DApplication::setSingleInstance(const QString &key, SingleScope singleScope)
    {
        D_D(DApplication);
    
        QString k = key;
    
    #ifdef Q_OS_UNIX
        switch (singleScope) {
        case DApplication::UserScope:
            k += QString("_%1").arg(getuid());
            break;
        default:
            break;
        }
    #endif
    
    #ifdef Q_OS_UNIX
        if (qgetenv("DTK_USE_DBUS_SINGLEINSTANCE") == "TRUE") {
            return d->setSingleInstanceByDbus(k);
        }
    #endif
        return d->setSingleInstanceBySemaphore(k);
    }
    


  • 
    bool DApplicationPrivate::setSingleInstanceBySemaphore(const QString &key)
    {
        static QSystemSemaphore ss(key, 1, QSystemSemaphore::Open);
        static bool singleInstance = false;
    
        if (singleInstance) {
            return true;
        }
    
        Q_ASSERT_X(ss.error() == QSystemSemaphore::NoError, "DApplicationPrivate::setSingleInstanceBySemaphore:", ss.errorString().toLocal8Bit().constData());
    
        singleInstance = tryAcquireSystemSemaphore(&ss);
    
        if (singleInstance) {
            QtConcurrent::run([this] {
                QPointer<DApplication> that = q_func();
    
                while (ss.acquire() && singleInstance)
                {
                    if (!that) {
                        return;
                    }
    
                    if (that->startingUp() || that->closingDown()) {
                        break;
                    }
    
                    ss.release(1);
    
                    if (that) {
                        Q_EMIT that->newInstanceStarted();
                    }
                }
            });
    
            auto clean_fun = [] {
                ss.release(1);
                singleInstance = false;
            };
    
            qAddPostRoutine(clean_fun);
            atexit(clean_fun);
        }
    
        return singleInstance;
    }
    
    #ifdef Q_OS_UNIX
    /**
    * \brief DApplicationPrivate::setSingleInstanceByDbus will check singleinstance by
    * register dbus service
    * \param key is the last of dbus service name, like "com.deepin.SingleInstance.key"
    * \return
    */
    bool DApplicationPrivate::setSingleInstanceByDbus(const QString &key)
    {
        auto basename = "com.deepin.SingleInstance.";
        QString name = basename + key;
        auto sessionBus = QDBusConnection::sessionBus();
        if (!sessionBus.registerService(name)) {
            qDebug() << "register service failed:" << sessionBus.lastError();
            return  false;
        }
        return true;
    }
    #endif
    
    




  • @大黄老鼠 看来使用其它的技术QDbusConnection来实现的啊。


Log in to reply
 

走马观花

最近的回复

  • Z

    我按照楼主说的,除了windeployqt以外,还需要Qt\labs\folderlistmodel,QtQuick\Layouts,QtQuick\VirtualKeyboard,QtQuick.2,platforminputcontexts这么几个目录,另外Qt5Qml.dll, Qt5Quick.dll也要加进来,还是不行,楼主可以发个完整的文件夹给我吗,release模式的,debug模式没问题

    read more
  • 1.jpg

    简介

    USD全称是“Universal Scene Description”,它主要着力的是电影、游戏复杂制作的流程的规范化。这回我们主要来研究USD在Maya中有关渲染部分究竟是如何实现的。

    USD通过附属的子项目Hydra来实现在其工具“UsdView”以及Maya中渲染方面的实现。Hydra是一款经过多年锤炼的渲染引擎(据说自从2012年就开始研发),Hydra与Maya中有关渲染的结合得益于Maya支持第三方渲染框架通过它提出的“Viewport (1.0、2.0)”方式支持。

    初探

    我们打开USD项目,会发现它有很多子项目。其中包含了imaging和usd子项目。这里我们主要关注的是imaging项目。由单词意思可知,其主要关注的是产出图片的,也就是有关渲染的。
    ca7ce8ff-7e7d-4ec3-9246-fbee48109c7b-image.png

    有关Hydra是三个子项目。包含了hdSt、hdStream以及hdx。我们主要关注的是hdSt。因为这个项目是和OpenGL渲染密切相关的。由于OpenGL是做渲染的大家通用知识,因而它是我们关注的主要子项目。

    调试

    调试.jpg
    我们调试这部分代码,截了此图。我们在图中至少发现几个问题:

    Maya底层是用OpenSceneGraph(OSG)管理场景的。我们可以看到Maya 2018的文件夹里有很多OSG开头的动态链接库,这么说具体视口渲染的部分都是建立在OSG上的。Maya的Viewport 2.0也是建立在OSG上的。在可见的将来它们不会替换掉OSG。

    USD的Hydra和Maya结合的类叫做UsdMayaGLBatchRenderer。由名字可知,它仅工作在OpenGL下,换句话说,如果Maya使用的是DirectX11进行渲染的,那么它将会失效。
    d7063ec4-242e-40bd-bf05-5103c38fedf1-image.png

    libhd项目只是一个前端库,后端通过libhdx以及libhdSt来实现。尤其是libhdSt,它主要是和OpenGL打交道的。它十分复杂。主要基于的是OpenGL 3.3+,也就是包含了各种着色器以及高级栅格化技术,并且整合了网格细分库:OpenSubdiv。

    后续

    由于我们的研究方向是思考一个方式来让让Hydra支持过程化纹理,因此我们还需要继续对此进行研究。

    read more
  • 最近我开始在Maya工作啦。Maya是一款优秀的三维软件,可以处理布景、建模、纹理、装备、渲染等操作。而且它可以支持C++和Python的开发。文档也是非常多的(参考这里)。

    我们最近的工作呢,是想要利用Maya的资源,尤其是图片资源,来制作新的界面。由于Maya是基于Qt开发的,因此要获取图片资源,除了Maya文件夹里D:\Develop\Autodesk\Maya2018\icons文件之外,还需要从资源文件中获取到。而资源文件一般是编译成C++代码放在程序的某个位置了,所以我们一般是看不到的。我们就开始想,既然Maya能够成功地读取并且显示,我们通过Maya的插件开发,不也能够获取并且显示到需要的图标吗?由于Qt的经验,我开始研究通过写Maya插件来获取到Maya资源文件的方法。

    新打开Maya软件,点击右下角的脚本图标,我们开始输入脚本:
    ad141613-63a5-4a93-9b79-3d6ca44da782-image.png

    2、我们通过Qt for Python来从Maya中获取到图标信息。由于Maya是构建在Qt 5.6.1上的,当时还不叫Qt for Python,而是PySide2。当然用法也是差不多的。更重要的是Qt 5.6.1已经支持QML了,可以支持QML的基本绘图方法。所以我们打算结合Qt for Python和QML来实现相关的功能。其实这样一组合就和Maya没有什么关系了。剩下的都是Qt的技术。
    我们的脚本是这样的:

    from PySide2.QtQuick import QQuickView from PySide2.QtCore import QDir, QFileInfo, QUrl def getMayaResourceFileList( nameFilter ): dir = QDir( ":/" ) return dir.entryList( nameFilter ) view = QQuickView( ) view.setResizeMode( QQuickView.SizeViewToRootObject ) mainUrl = QUrl.fromLocalFile( "C:/Users/huawei/Documents/ImageGridView.qml" ) view.setSource( mainUrl ) view.show( ) rootItem = view.rootObject( ) if rootItem != None: rootItem.setProperty( "prefix", "qrc:/" ) rootItem.setProperty( "model", getMayaResourceFileList( '*.png' ) )

    其中C:/Users/huawei/Documents/ImageGridView.qml是我本地的路径,可以改为任意的路径甚至是http路径呢。

    我们还得完成ImageGridView.qml文件内容,其实也非常简单,大概是这样的: import QtQuick 2.6 GridView { id: root width: 320 height: 480 cellWidth: 80 cellHeight: 80 delegate: Image { width: 80 height: 80 source: root.prefix + modelData Text { anchors { left: parent.left right: parent.right bottom: parent.bottom } text: modelData wrapMode: Text.Wrap } } model: 40 property string prefix }

    22ae8472-0bb3-4342-ae72-e1cb54bd87a7-image.png

    4、这些文件准备就位了!我们打开一下Maya软件,看看结果~
    f430332d-c824-47d3-8d1c-b17b3c53bc97-image.png
    它是一个可滑动的界面,每行显示4列,然后下面是文字的内容,展示了图标的名称。我们可以借此工具拿到我们感兴趣的图标的路径,然后应用到我们制作的界面上。其实,如果你觉得图片好,也可以通过QImage以及QPixmap给转存出去自己用。

    如果你觉得文章很棒,记得点赞哦。

    read more

关注我们

微博
QQ群











召唤伊斯特瓦尔