Qt Window Lifecycle & Signal Governance

05. 客户端交互与窗口治理

MainWindow 负责全局入口,业务子窗口按用户、群组和通话对象唯一化管理,统一处理关闭、退出和资源回收。

6 个核心函数4 个重点文件基于当前工程源码

模块职责

MainWindow 负责全局入口,业务子窗口按用户、群组和通话对象唯一化管理,统一处理关闭、退出和资源回收。

重点文件

调用链

  1. 1用户选择对象
  2. 2查询窗口映射
  3. 3复用或创建窗口
  4. 4绑定业务信号
  5. 5关闭时移除映射并释放资源
主窗口信号治理

MainWindow::bindLogicSignals

按登录态、好友、群组、通话和更新等领域集中绑定 ClientLogic 信号。

mainwindow.cpp · L145–L352
原型void MainWindow::bindLogicSignals()

调用时机

MainWindow 完成页面构建后调用。

返回说明

无返回值。

参数

无显式参数。

执行流程

  1. 校验 logic 对象
  2. 绑定强制退出
  3. 绑定好友与通知
  4. 绑定群组事件
  5. 绑定通话事件

工程说明

集中连接降低构造函数复杂度并便于生命周期审查。

关联接口

查看完整实现
mainwindow.cpp
void MainWindow::bindLogicSignals()
{
    if (!logic) {
        return;
    }

    connect(logic, &ClientLogic::sigForceLogout,
            this, &MainWindow::handleForceLogout);

    connect(logic, &ClientLogic::sigAddFriendResult, this,
            [=](bool success, QString, QString msg) {
                if (success) {
                    QMessageBox::information(this, "提示", msg);
                } else {
                    QMessageBox::warning(this, "添加失败", msg);
                }
            });

    connect(logic, &ClientLogic::sigFriendListReceived, this,
            [=](QJsonArray friends) {
                if (!friendListWidget) {
                    return;
                }

                friendListWidget->clear();

                for (int i = 0; i < friends.size(); ++i) {
                    QString friendId = friends[i].toString();
                    addFriendToList(friendId, "👤");
                }
            });

    connect(logic, &ClientLogic::sigLoginResult, this,
            [=](bool success, const QString& message) {

                m_loginRequestPending = false;

                if (loginBtn) {
                    loginBtn->setEnabled(true);
                    loginBtn->setText("登录");
                }

                if (success) {
                    stackedWidget->setCurrentIndex(1);

                    updatePersonalInfo(logic->getCurrentUser(), "👤");

                    logic->requestPullFriends();
                    logic->requestPullGroups();
                    logic->requestPullNotifications();

                    QMessageBox::information(this, "登录成功", "欢迎回来,客户端已连接到服务器。");
                } else {
                    QMessageBox::warning(this, "登录失败", message);
                }
            });

    connect(logic, &ClientLogic::sigRegisterResult, this,
            [=](bool success, QString msg) {
                m_registerRequestPending = false;

                if (registerBtn) {
                    registerBtn->setEnabled(true);
                    registerBtn->setText("创建账号");
                }

                if (success) {
                    QMessageBox::information(this, "成功", msg);
                } else {
                    QMessageBox::warning(this, "注册失败", msg);
                }
            });

    connect(logic, &ClientLogic::sigCreateGroupResult,
            this,
            [=](bool success,
                qulonglong groupId,
                const QString& groupName,
                const QString& msg) {
                if (success) {
                    QMessageBox::information(
                        this,
                        "创建群成功",
                        QString("群组创建成功。\n群名:%1\n群ID:%2")
                            .arg(groupName)
                            .arg(groupId));

                    logic->requestPullGroups();
                } else {
                    QMessageBox::warning(this, "创建群失败", msg);
                }
            });

    connect(logic, &ClientLogic::sigInviteGroupMemberResult,
            this,
            [=](bool success, const QString& msg) {

                m_pendingGroupInviteKeys.clear();

                if (success) {
                    QMessageBox::information(this, "入群邀请", msg);
                } else {
                    QMessageBox::warning(this, "入群邀请失败", msg);
                }

                if (logic) {
                    logic->requestPullGroups();
                    logic->requestPullNotifications();
                }
            });

    connect(logic, &ClientLogic::sigGroupsReceived,
            this,
            [=](const QJsonArray& groups) {
                refreshGroupListUI(groups);
            });

    connect(logic, &ClientLogic::sigGroupMembersReceived,
            this,
            [=](qulonglong groupId, const QJsonArray& members) {
                showGroupMembersDialog(groupId, members);
            });

    connect(logic, &ClientLogic::sigGroupError,
            this,
            [=](const QString& msg) {
                QMessageBox::warning(this, "群组错误", msg);
            });

    connect(logic, &ClientLogic::sigCallInviteReceived,
            this,
            &MainWindow::handleIncomingCallInvite);

    connect(logic, &ClientLogic::sigCallReady,
            this,
            [=](const QString& peer,
                const QString& callId,
                const QString& mode,
                const QString& callType) {
                if (peer.isEmpty() || callId.isEmpty()) {
                    qDebug() << "[Call] invalid call_ready, peer or callId empty";
                    return;
                }

                if (mode != "relay") {
                    QMessageBox::warning(this,
                                         "通话暂不可用",
                                         "当前网络环境暂不支持该通话模式,请稍后重试。");
                    return;
                }

                openOrActivateCallWindow(peer, callId, callType);
            });

    connect(logic, &ClientLogic::sigCallRejected,
            this,
            [=](const QString& peer, const QString& callId) {
                Q_UNUSED(callId);

                if (!callWindowMap.contains(peer)) {
                    QMessageBox::information(this,
                                             "通话结束",
                                             QString("[%1] 拒绝了通话。").arg(peer));
                }
            });

    connect(logic, &ClientLogic::sigCallEnded,
            this,
            [=](const QString& peer, const QString& callId) {
                Q_UNUSED(callId);

                if (!callWindowMap.contains(peer)) {
                    qDebug() << "[Call]" << peer
                             << "ended call, but no CallWindow exists.";
                }
            });
}
私聊窗口唯一化

MainWindow::openOrActivateChatWindow

以好友 ID 为键复用已有 ChatWindow,避免同一会话创建多个窗口。

mainwindow.cpp · L516–L548
原型ChatWindow* MainWindow::openOrActivateChatWindow(const QString& targetId)

调用时机

双击好友或收到消息需要打开会话时调用。

返回说明

返回可用的 ChatWindow 指针。

参数

参数说明
targetId目标好友账号

执行流程

  1. 查询 chatWindowMap
  2. 激活已有窗口
  3. 创建新窗口
  4. 写入映射
  5. 连接 destroyed 清理

工程说明

映射表仅保存当前存活窗口。

关联接口

查看完整实现
mainwindow.cpp
ChatWindow* MainWindow::openOrActivateChatWindow(const QString& targetId)
{
    if (targetId.isEmpty()) {
        return nullptr;
    }

    if (chatWindowMap.contains(targetId)) {
        ChatWindow* existing = chatWindowMap.value(targetId);

        if (existing) {
            existing->show();
            existing->raise();
            existing->activateWindow();
            return existing;
        }

        chatWindowMap.remove(targetId);
    }

    ChatWindow* chatWin = new ChatWindow(targetId, logic);
    chatWin->setAttribute(Qt::WA_DeleteOnClose);

    chatWindowMap[targetId] = chatWin;

    connect(chatWin, &QObject::destroyed, this, [=]() {
        if (chatWindowMap.value(targetId) == chatWin) {
            chatWindowMap.remove(targetId);
        }
    });

    chatWin->show();
    return chatWin;
}
通话窗口唯一化

MainWindow::openOrActivateCallWindow

按通话对象与 call_id 创建或复用 CallWindow,避免音视频设备重复占用。

mainwindow.cpp · L558–L592
原型CallWindow* MainWindow::openOrActivateCallWindow(const QString& peer, const QString& callId, const QString& callType)

调用时机

发起、接听或恢复通话界面时调用。

返回说明

返回 CallWindow 指针。

参数

参数说明
peer通话对端
callId通话标识
callType语音或视频类型

执行流程

  1. 生成窗口键
  2. 查找现有窗口
  3. 创建 CallWindow
  4. 连接关闭与挂断信号
  5. 显示并激活

工程说明

通话窗口关闭时会触发媒体资源收尾。

关联接口

查看完整实现
mainwindow.cpp
CallWindow* MainWindow::openOrActivateCallWindow(const QString& peer,
                                                 const QString& callId,
                                                 const QString& callType)
{
    if (peer.isEmpty() || callId.isEmpty()) {
        return nullptr;
    }

    if (callWindowMap.contains(peer)) {
        CallWindow* existing = callWindowMap.value(peer);

        if (existing) {
            existing->show();
            existing->raise();
            existing->activateWindow();
            return existing;
        }

        callWindowMap.remove(peer);
    }

    CallWindow* callWin = new CallWindow(peer, logic, callId, callType);
    callWin->setAttribute(Qt::WA_DeleteOnClose);

    callWindowMap[peer] = callWin;

    connect(callWin, &QObject::destroyed, this, [=]() {
        if (callWindowMap.value(peer) == callWin) {
            callWindowMap.remove(peer);
        }
    });

    callWin->show();
    return callWin;
}
群聊窗口唯一化

MainWindow::openOrActivateGroupChatWindow

以 groupId 为键复用 GroupChatWindow,并统一传递群名称和当前用户。

mainwindow.cpp · L601–L635
原型GroupChatWindow* MainWindow::openOrActivateGroupChatWindow(qulonglong groupId, const QString& groupName, const QString& role)

调用时机

双击群组或跳转群会话时调用。

返回说明

返回 GroupChatWindow 指针。

参数

参数说明
groupId群组 ID
groupName群组名称

执行流程

  1. 查询 groupChatWindowMap
  2. 激活已有窗口
  3. 创建群聊窗口
  4. 写入映射
  5. 关闭时清理

工程说明

群聊窗口可独立维护历史、文件和直播状态。

关联接口

查看完整实现
mainwindow.cpp
GroupChatWindow* MainWindow::openOrActivateGroupChatWindow(qulonglong groupId,
                                                           const QString& groupName,
                                                           const QString& role)
{
    if (groupId == 0) {
        return nullptr;
    }

    if (groupChatWindowMap.contains(groupId)) {
        GroupChatWindow* existing = groupChatWindowMap.value(groupId);

        if (existing) {
            existing->show();
            existing->raise();
            existing->activateWindow();
            return existing;
        }

        groupChatWindowMap.remove(groupId);
    }

    GroupChatWindow* win = new GroupChatWindow(groupId, groupName, role, logic);
    win->setAttribute(Qt::WA_DeleteOnClose);

    groupChatWindowMap[groupId] = win;

    connect(win, &QObject::destroyed, this, [=]() {
        if (groupChatWindowMap.value(groupId) == win) {
            groupChatWindowMap.remove(groupId);
        }
    });

    win->show();
    return win;
}
子窗口集中回收

MainWindow::closeAllChildWindows

关闭所有私聊、群聊和通话窗口,并清空窗口映射。

mainwindow.cpp · L359–L390
原型void MainWindow::closeAllChildWindows()

调用时机

退出登录、强制下线或主窗口销毁时调用。

返回说明

无返回值。

参数

无显式参数。

执行流程

  1. 复制窗口集合
  2. 逐个关闭窗口
  3. 清空私聊映射
  4. 清空群聊映射
  5. 清空通话映射

工程说明

集中关闭避免退出阶段残留采集器和信号连接。

关联接口

查看完整实现
mainwindow.cpp
void MainWindow::closeAllChildWindows()
{

    const QList<CallWindow*> callWindows = callWindowMap.values();
    for (CallWindow* win : callWindows) {
        if (win) {
            win->close();
        }
    }
    callWindowMap.clear();

    const QList<GroupChatWindow*> groupWindows = groupChatWindowMap.values();
    for (GroupChatWindow* win : groupWindows) {
        if (win) {
            win->close();
        }
    }
    groupChatWindowMap.clear();

    const QList<ChatWindow*> chatWindows = chatWindowMap.values();
    for (ChatWindow* win : chatWindows) {
        if (win) {
            win->close();
        }
    }
    chatWindowMap.clear();
}
登录态失效处理

MainWindow::handleForceLogout

停止业务活动、关闭子窗口、清理认证状态并切回登录页。

mainwindow.cpp · L399–L447
原型void MainWindow::handleForceLogout(const QString& reason)

调用时机

收到 force_logout 或认证失效信号时调用。

返回说明

无返回值。

参数

参数说明
reason下线原因

执行流程

  1. 关闭业务窗口
  2. 停止媒体与心跳
  3. 清理列表和状态
  4. 切换登录页
  5. 展示状态说明

工程说明

保证单账号会话切换时界面和设备资源同步收口。

关联接口

查看完整实现
mainwindow.cpp
void MainWindow::handleForceLogout(const QString& reason)
{
    closeAllChildWindows();

    pendingRequests.clear();
    pendingNotifications = QJsonArray();
    m_pendingIncomingCallIds.clear();
    m_processingNoticeIds.clear();
    m_pendingGroupInviteKeys.clear();

    if (friendListWidget) {
        friendListWidget->clear();
    }

    if (groupListWidget) {
        groupListWidget->clear();
    }

    if (profileNameLabel) {
        profileNameLabel->setText("未登录");
    }

    if (loginPwdEdit) {
        loginPwdEdit->clear();
    }

    if (loginBtn) {
        loginBtn->setEnabled(true);
        loginBtn->setText("登录");
    }

    if (registerBtn) {
        registerBtn->setEnabled(true);
        registerBtn->setText("创建账号");
    }

    m_loginRequestPending = false;
    m_registerRequestPending = false;

    if (stackedWidget) {
        stackedWidget->setCurrentIndex(0);
    }

    QString msg = reason.trimmed().isEmpty()
                      ? "登录状态已失效,请重新登录。"
                      : reason;

    QMessageBox::warning(this, "需要重新登录", msg);
}