QT6 C++ 高仿QQ--3. 主界面MainWindow

主界面布局

MainWindow是继承于QMainWindow的无边框主界面,主界面放了一个QWidget,左右布局,左边widgetLeft是快捷功能导航,右边widgetRight是主要功能区。右边widgetRight也是左右布局,左边widgetRightLeft是中间导航区,右边是点击中间导航后呈现的内容区widgetRightRight,mainwindow.ui如下图
在这里插入图片描述

widgetLeft左边快捷功能导航

UI布局说明

一级功能快捷导航为自定义的QWidget,整体垂直布局。

    1. 最上面是用户头像QLabel,用户更新了头像可以加载显示到QLabel中,可点击弹出个人信息的非模态对话框。
    1. 中间widgetNav为自定义的QWidget,主要承载大的功能的QStackWidget切换。
    1. H为与host通信状态QLabel,S为与服务器通信状态QLable。
    1. 最下面更多功能按钮btnMore,弹出菜单执行用户登录、注册、修改密码、等功能。
      左边功能已经功能快捷导航widgetLeft

widgetRight右边功能导航/title/内容展示区

右侧为QSplitter分割的左右布局,左边widgetRightLeft为中间功能导航区,middleStackedWidge承载左侧一级功能导航窗口。
widgetRightRight为上下布局,上面是自定义title,承载app最大化/最小化/关闭功能按钮;下面部分rightStacktWidget承载中间二级功能导航窗口。
在这里插入图片描述

关键功能代码

初始化左侧工具栏

//初始化左侧工具条的按钮
void MainWindow::initLeftToolBar()
{
    //1.头像QLabel
    ui->headerLabel->setFixedSize(40, 40);
    ui->headerLabel->setAlignment(Qt::AlignCenter);
    ui->headerLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken);
    ui->headerLabel->setStyleSheet("border:none; ");
    ui->headerLabel->installEventFilter(this);  //1.给label安装事件过滤器

    if (userInfo_->headerImage.empty()) {
        //设置默认头像
        ui->headerLabel->setStyleSheet("QLabel{"
            "image:url(:/res/image/header.png) 4 4 4 4 stretch stretch;border-radius:15px;"
            "}");
    }

    //2. stackWidget功能导航区
    initNav();        //左侧QToolbutton导航
    initIcon();       //QToolbutton的字体图标设置

    //3. 清空与host和server通信状态文字
    ui->ipcStateLabel->setText("");
    ui->serverStateLabel->setText("");

    //4. 更多功能
    initBtnMore();    //初始化左侧更多按钮和样式
}

这里代码的关键点在于给QLable安装事件监听,用户点击头像弹出个人详细信息对话框。

bool MainWindow::eventFilter(QObject* object, QEvent* event)
{
    if (object == ui->headerLabel) {//2.监听头像label
        if (event->type() == QEvent::MouseButtonPress && ((QMouseEvent*)event)->button() == Qt::LeftButton) {
            //头像label点击, 弹出个人信息
            on_headerLabel_clicked();
            return true;
        }
        else {
            return QWidget::eventFilter(object, event);
        }
    }

    if (object = ui->widgetTitle) {
        //双击标题栏最大化
        if (event->type() == QEvent::MouseButtonDblClick) {
            on_btnMenuMax_clicked();
        }
    }

    return QWidget::eventFilter(object, event);
}

在事件监听处理函数中,通过QObject来失败事件源和鼠标左键单击事件做on_headerLabel_clicked单出对话框处理。

//头像按钮点击弹出个人信息
void MainWindow::on_headerLabel_clicked()
{
    // 获取按钮的位置和大小
    QRect buttonRect = ui->headerLabel->geometry();

    // 计算对话框的位置
    QPoint dialogPos = ui->headerLabel->mapToGlobal(buttonRect.bottomLeft());
    dialogPos.setX(dialogPos.x() + ui->widgetLeft->rect().width());
    dialogPos.setY(dialogPos.y());

    //如果按钮没有创建则重新创建
    if (!userInfoDialog_) {
        LOG(LS_INFO) << "new UserInfoDialog";
        userInfoDialog_.reset(new UserInfoDialog(dialogPos, userInfo_, loginAuthServerState_, this));
        userInfoDialog_->setModal(false);
        userInfoDialog_->setFixedSize(QSize(400, 350));
        connect(userInfoDialog_.get(), &UserInfoDialog::hideDialog, this, &MainWindow::onHideUserInfoDialog);   //隐藏对话框
        connect(userInfoDialog_.get(), &UserInfoDialog::editUserInfo, this, &MainWindow::onEditUserInfo);   //编辑个人信息
        this->installEventFilter(userInfoDialog_.get());
    }

    if (!showUserInfoDialog) {
        userInfoDialog_->setGeometry(QRect(dialogPos, QSize(400, 350))); // 宽度200,高度100
        userInfoDialog_->show();
        showUserInfoDialog = true;
    }
    else {
        userInfoDialog_->hide();
        showUserInfoDialog = false;
    }
}

个人信息弹框的位置是处理的关键点有两点:1.每次点击需要获取头像QLabel的左下角位置,根据此信息重新设置个人信息对话框位置;2. 个人信息对话框为非模态对话框,点击时如果没有创建则新创建,如果当前是显示状态则隐藏,再次点击则显示。

一级功能导航stackWidgets

这里一次性初始化了四个功能QToolButton,保存到按钮列表QList<QAbstractButton*> leftToolbarBtns_,并给所有按钮绑定了事件响应函数buttonClicked(),点击按钮切换中间导航,关键代码如下:

void MainWindow::initNav()
{
    //按钮文字集合
    QStringList names, tips;
    names << tr("IM") << tr("Friends") << tr("Meetings") << tr("Settings");
    tips << tr("Chat messages") << tr("My friends") << tr("Join a meeting") << tr("Settings");
    int topMargin = 3, otherMargin = 3;
    ui->layoutNav->setContentsMargins(otherMargin, topMargin, otherMargin, otherMargin);

    //自动生成按钮
    for (int i = 0; i < names.count(); i++) {
        QToolButton* btn = new QToolButton;

        //设置按钮固定高度
        btn->setFixedHeight(45);//调整到合适大小, 解决字体图标与文字的距离

        //设置按钮的文字
        //btn->setText(QString("%1").arg(names.at(i)));

        //设置按钮提示文字
        btn->setToolTip(QString("%1").arg(tips.at(i)));

        //设置按钮可选中按下类似复选框的功能
        btn->setCheckable(true);

        //设置按钮图标在左侧文字在右侧
        btn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);

        //设置按钮拉伸策略为横向填充
        btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);

        //关联按钮单击事件
        connect(btn, SIGNAL(clicked(bool)), this, SLOT(buttonClicked()));
        //将按钮加入到布局
        //ui->layoutNav->addWidget(btn);
        ui->widgetNav->layout()->addWidget(btn);

        //可以指定不同的图标
        leftToolbarBtns_ << btn;
    }

    //设置flag的方向要与下面 icon的 position保持一致
    ui->widgetNav->setProperty("flag", "left");

    //底部加个弹簧
    QSpacerItem* verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
    ui->widgetNav->layout()->addItem(verticalSpacer);

    //根据配置文件存储的子窗体的索引触发按钮单击进行切换
    AppConfig::IndexDemo = AppConfig::IndexDemo >= leftToolbarBtns_.count() ? 0 : AppConfig::IndexDemo;
    leftToolbarBtns_.at(AppConfig::IndexDemo)->click();
}

按钮的事件响应函数,leftToolbarBtns_.indexOf(btn)即是上面按钮初始化顺序的索引。

//选中按钮切换到对应的页面
void MainWindow::buttonClicked()
{
    //切换到当前窗体
    QAbstractButton* btn = (QAbstractButton*)sender();//触发的按钮,按钮的顺序与middleStackedWidget有一一对应
    ui->middleStackedWidget->setCurrentIndex(leftToolbarBtns_.indexOf(btn));

    //保存最后的窗体索引
    AppConfig::LastFormMain = btn->objectName();
    AppConfig::writeConfig();

    //取消其他按钮选中
    foreach(QAbstractButton * button, leftToolbarBtns_)
    {
        button->setChecked(button == btn);
    }
}

中间功能导航stackWidgets的创建和切换

创建中间聊天导航和通信录导航widget,创建的顺序要与左侧widgetNav的功能顺序保持一致,通过头文件的枚举可以直观的看到,每次点击左侧快捷功能导航按钮切换,保存当前点击中间导航索引,关键代码如下:

//中间导航窗口widget索引
enum EMiddleNavWidget
{
    MIDDLE_NAV_CHAT = 0,                    //0. 聊天
    MIDDLE_NAV_CONTACT,                     //1. 通信录
    MIDDLE_NAV_MEETING,                     //2. 会议
    MIDDLE_NAV_BUTT                         //3. ....
};

//初始化中间导航
void MainWindow::initMiddleNav()
{
    /*----------------   关联切换widget     --------------*/
    //0. 好友聊天导航
    chatNavWidget_.reset(new ChatNavWidget(serverApi_, userInfo_, friendsMap_, myGroupsMap_, joinedGroupsMap_));
    ui->middleStackedWidget->addWidget(chatNavWidget_.get());

    //1. 通信录导航 关联
    contactNavWidget_ = new ContactNavWidget(userInfo_, friendsMap_, myGroupsMap_, joinedGroupsMap_);
    ui->middleStackedWidget->addWidget(contactNavWidget_);

    //2. 会议

    //最后, 关联中间导航切换的时间响应
    connect(ui->middleStackedWidget, &QStackedWidget::currentChanged, this, &MainWindow::middleNavWidgetChanged);
}
//中间导航窗口发生了切换, 恢复窗口上次的选中状态
void MainWindow::middleNavWidgetChanged(int index)
{
    switch (index) {
    case MIDDLE_NAV_CHAT:
        if (chatNavWidget_)
            chatNavWidget_->restoreLastCheck();
        break;

    case MIDDLE_NAV_CONTACT:
        contactNavWidget_->restoreLastCheck();
        break;

    case MIDDLE_NAV_MEETING:
        break;
    default:
        break;
    }

    lastMiddleNavIndex_ = index;

    //LOG(LS_INFO) << "Main middle stactwidgets changed, index:" << index;
}

rightWidget右侧stackWidgets创建

右侧stackwidgets是根据中间导航点击切换显示对应的内容,如默认空窗口rightDefaultWidget,聊天主窗口chatInfoWidget,好友通知窗口friendInfoWidget,设备信息窗口deviceInfoWidget,好友信息窗口friendInfoWidget,群信息窗口groupInfoWidget等,关键代码如下:

//初始化右侧内容widget
void MainWindow::initRightWidget()
{
    /*----------------   右侧 widgets     --------------*/
    //0. 默认空窗口
    rightDefaultWidget_.reset(new RightDefaultWidget());
    ui->rightStackedWidget->addWidget(rightDefaultWidget_.get());

    //1. 聊天
    chatInfoWidget_.reset(new ChatInfoWidget(serverApi_, userInfo_, friendsMap_, myGroupsMap_, joinedGroupsMap_));
    connect(chatInfoWidget_.get(), &ChatInfoWidget::sendMessageToIpc, this, &MainWindow::sendMessageToIpc);     //发送Ipc消息
    ui->rightStackedWidget->addWidget(chatInfoWidget_.get());
    LOG(LS_INFO) << "ui->rightStackedWidget width: " << ui->rightStackedWidget->width() << ", rightRightWidget width: " << ui->widgetRightRight->width() << ", widget right width: " << ui->widgetRight->width();

    //2. 朋友通知列表
    friendNoticeList_ = new FriendNoticeList(ipcThread_);
    connect(friendNoticeList_, &FriendNoticeList::sendMessageToIpc, ipcThread_.get(), &IpcThread::onSendMessage);
    connect(friendNoticeList_, &FriendNoticeList::newFriendNotice, this, [&](uint32_t newNoticeCount) {
        contactNavWidget_->setNewFriendNoticeCount(newNoticeCount);
        });

    ui->rightStackedWidget->addWidget(friendNoticeList_);

    //3. 设备信息: ContactNavWidget中点击某个设备, 切换为展示某个设备的详细信息
    deviceInfoWidget_ = new DeviceInfoWidget(ipcThread_);
    ui->rightStackedWidget->addWidget(deviceInfoWidget_);

    /*4. 好友信息展示 ----------------- 点击好友列表某个好友, 展示好友详细信息 -----------------*/
    friendInfoWidget_ = new FriendInfoWidget(ipcThread_);
    ui->rightStackedWidget->addWidget(friendInfoWidget_);

    //5. 群信息展示 -----------------------------
    groupInfoWidget_.reset(new GroupInfoWidget(userInfo_));
    ui->rightStackedWidget->addWidget(groupInfoWidget_.get());
}

中间stackWidgets点击切换展示到右侧窗口在后续章节讲解。

总结

主界面功能比较多,一个章节很难讲清楚,本节主要概述了主窗口布局,左边一级功能导航工具条,中间导航stackWidgets创建与切换,右侧stackWidgets窗口的创建,未尽之处欢迎加入QQ群 738529266 交流。

鸣谢:感谢飞扬青云大侠开源了很多有用的功能,链接:https://gitee.com/feiyangqingyun/qtkaifajingyan

  • 10
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chrisLee_sz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值