Qt中实现窗口单例的3中常见方法

用Qt开发GUI应用时,经常需要设置窗口单例显示。趁着最近工作不忙,总结下实现窗口单例的3种常见方法。

  1. 借助共享内存,QSharedMemory。
  2. 借助QLocalServer/QLocalSocket。
  3. 借助DBus框架。

1. 共享内存

共享内存的思路是第一个主窗口初始化时,以关键词key创建一片共享内存。当第二个主窗口初始化时,以同样的key创建共享内存时,就会失败。此时就可以判断主窗口已初始化,第二个主窗口自动退出即可。

这个思路的实现方案已经有一个不错的第三方开源库可以使用,MIT协议,对商业和闭源友好。
GitHub地址如下:
https://github.com/itay-grudev/SingleApplication

2. QLocalServer/QLocalSocket

借助QLocalServer/QLocalSocket实现的思路与共享内存相似,第一个主窗口初始化时以关键词serverName创建一个socke服务。当第二个主窗口初始化时,以同样的serverName创建socket服务时,就会失败。

第二个主窗口可以通过socket服务,将数据发送到第一个主窗口。整个实现逻辑非常简单,不需要继承Q[Core|Gui]Application类。其实本方法就是刚才提到的开源项目的简化。

完整实现代码如下,不超过70行。

#include "SingleApplicationHelper.h"

#include <QLocalSocket>
#include <QDataStream>
#include <QFile>
#include <QDir>

bool SingleApplicationHelper::registerApplication(const QString &appName, Options options, int timeout)
{
    QLocalSocket socket;
    socket.connectToServer(appName);
    if (socket.waitForConnected(timeout)) {
        return false;
    }

    if( options & Mode::User ){
        localServer.setSocketOptions(QLocalServer::UserAccessOption );
    } else {
        localServer.setSocketOptions(QLocalServer::WorldAccessOption );
    }

    connect(&localServer, &QLocalServer::newConnection, this, &SingleApplicationHelper::slotNewLocalConnect);

    // Note: On Unix if the server crashes without closing listen will fail with AddressInUseError.
    // To create a new server the file should be removed.
    // On Windows two local servers can listen to the same pipe at the same time, but any connections will go to one of the server.
    if (!localServer.listen(appName)
        && QAbstractSocket::AddressInUseError == localServer.serverError()) {
        // Read Qt sources file, we know the path.
        QString socketFile = QDir::tempPath() + '/' + localServer.serverName();
        QFile::remove(socketFile);
        localServer.listen(appName);
    }
    return true;
}

bool SingleApplicationHelper::sendSecondApplicationArguments(const QString &appName, const QString &arguments, int timeout)
{
    QLocalSocket socket;
    socket.connectToServer(appName);
    if (!socket.waitForConnected(timeout)) {
        return false;
    }
    QDataStream stream(&socket);
    stream.setVersion(QDataStream::Qt_DefaultCompiledVersion);
    stream << arguments;
    socket.waitForBytesWritten();
    return true;
}

void SingleApplicationHelper::slotNewLocalConnect()
{
    QLocalSocket *socket = localServer.nextPendingConnection();
    if (nullptr != socket) {
        socket->waitForReadyRead(500);
        QDataStream stream(socket);
        stream.setVersion(QDataStream::Qt_DefaultCompiledVersion);
        QString args;
        stream >> args;
        emit sigSecondApplicationArguments(args);
    }
}

在项目里集成使用也是非常简单。

#include <QApplication>
#include <QMainWindow>
#include "SingleApplicationHelper.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    a.setApplicationName("MyApp");

    QMainWindow w;

    SingleApplicationHelper singleAppHelper;
    // The first application will register the server successfully, and listen for connections. 
    // The second try to register the server, but failed.
    if (singleAppHelper.registerApplication(a.applicationName(), SingleApplicationHelper::User, 500)) {
        QObject::connect(&singleAppHelper, &SingleApplicationHelper::sigSecondApplicationArguments,
                         &w, &QMainWindow::raise);
        w.show();
    } else {
    // The second application can send the arguments to first application by the method below.    
        singleAppHelper.sendSecondApplicationArguments(a.applicationName(), "--show", 500);
        return 0;
    }

    return a.exec();
}

项目已经开源,欢迎大家体验,提Bug,哈哈。
https://github.com/ShawZG/SingleApplicationHelper

3. DBus框架

DBus框架是Linux上的一种进程间通信的框架,几乎在所有Linux发行版上都默认集成。但是在Windows上需要先安装DBus框架才可使用此方法。其思路仍然与前两种方法一致。第一个主窗口初始化时,注册DBus服务,第二个主窗口初始化时,以相同参数注册DBus服务,就会失败。

相关代码逻辑如下:

DBusDemo dbusDemo;
QDBusConnection dbus = QDBusConnection::sessionBus();  
// 尝试注册D-Bus服务,如果注册成功,表示应用没有启动,启动应用。
// 如果注册失败,则说明应用已经启动了,调用接口,让已启动的应用去完成任务。  
if (dbus.registerService("org.example.QDBusDemo")) {        
    dbus.registerObject("/org/example/QDBusDemo", 
                        &dbusDemo, 
                        QDBusConnection::ExportAllSlots 
                        | QDBusConnection::ExportAllProperties 
                        | QDBusConnection::ExportAllSignals);
    // TODO 启动APP等逻辑
} else {        
    // TODO 通过上述D-Bus方法调用本服务的接口完成业务逻辑
}

之前曾经写过Qt调用DBus相关总结《Qt使用D-Bus几种写法》,感兴趣的可以瞅瞅。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值