客户端及时通讯系统(2)

一、主界面布局

把MainWidget类做成“单例类”(实现单例模式。让某个类在指定的进程中,只能有唯一实例,我用到的是懒汉模式)
指针指向的唯一实例:
static MainWidget* instance;

声明:

private:
    static MainWidget* instance;
    
public:
 static MainWidget* getInstance();    

定义:

//给静态成员初始化
MainWidget* MainWidget::instance=nullptr;

MainWidget *MainWidget::getInstance()
{
    if (instance == nullptr) {
        // 此处不传入参数, 以桌面为父窗口.
        // 由于此处的窗口是整个程序的主窗口, 父窗口就设定为桌面, 本身就是常规设定.
        instance = new MainWidget();
    }
    return instance;

}

在这里插入图片描述

在这里插入图片描述

1.1 窗口左侧部分

mainwidget.h文件

#include <QWidget>
#include <QPushButton>
#include <QLineEdit>

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWidget;
}
QT_END_NAMESPACE

class MainWidget : public QWidget
{
    Q_OBJECT
private:
    static MainWidget* instance;
    // 对于单例模式来说, 最关键的部分, 不是 "创建实例" , 而是限制别人创建实例.
    MainWidget(QWidget *parent = nullptr);
public:
    static MainWidget* getInstance();
    ~MainWidget();

private:
    Ui::MainWidget *ui;
    // 窗口最左侧部分
    QWidget* windowLeft;
    // 窗口中间部分
    QWidget* windowMid;
    // 窗口右侧部分
    QWidget* windowRight;

    // 用户头像
    QPushButton* userAvatar;
    // 会话标签页按钮
    QPushButton* sessionTabBtn;
    // 好友标签页按钮
    QPushButton* friendTabBtn;
    // 好友申请标签页按钮
    QPushButton* applyTabBtn;

public:
    void initMainWindow();
    void initLeftWindow();
    void initMidWindow();
    void initRightWindow();
};

mainwidget.cpp文件

#include "mainwidget.h"
#include "./ui_mainwidget.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QCloseEvent>

//给静态成员初始化
MainWidget* MainWidget::instance=nullptr;

MainWidget *MainWidget::getInstance()
{
    if (instance == nullptr) {
        // 此处不传入参数, 以桌面为父窗口.
        // 由于此处的窗口是整个程序的主窗口, 父窗口就设定为桌面, 本身就是常规设定.
        instance = new MainWidget();
    }
    return instance;
}

MainWidget::MainWidget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::MainWidget)
{
    ui->setupUi(this);
    this->setWindowTitle("我的聊天室");
    this->setWindowIcon(QIcon(":/resource/image/logo.png"));
    // 初始化主窗口的样式布局
    initMainWindow();
    // 初始化左侧窗口布局
    initLeftWindow();
    // 初始化中间窗口布局
    initMidWindow();
    // 初始化右侧窗口布局
    initRightWindow();

}

MainWidget::~MainWidget()
{
    delete ui;
}

void MainWidget::initMainWindow()
{
    QHBoxLayout* layout = new QHBoxLayout();
    // Spacing 就是 layout 内部元素之间的间隔距离. 设为 0 就是 "紧挨着"
    layout->setSpacing(0);
    // layout 里面的元素距离四个边界的距离.
    layout->setContentsMargins(0, 0, 0, 0);
    this->setLayout(layout);

    windowLeft = new QWidget();
    windowMid = new QWidget();
    windowRight = new QWidget();

    windowLeft->setFixedWidth(70);
    windowMid->setFixedWidth(310);
    windowRight->setMinimumWidth(500);

    windowLeft->setStyleSheet("QWidget { background-color: rgb(46, 46, 46); }");
    windowMid->setStyleSheet("QWidget { background-color: rgb(247, 247, 247); }");
    windowRight->setStyleSheet("QWidget { background-color: rgb(225, 225, 225); }");

    layout->addWidget(windowLeft);
    layout->addWidget(windowMid);
    layout->addWidget(windowRight);
}

void MainWidget::initLeftWindow()
{
    QVBoxLayout* layout = new QVBoxLayout();
    layout->setSpacing(20);
    layout->setContentsMargins(0, 50, 0, 0);
    windowLeft->setLayout(layout);

    // 添加用户头像
    userAvatar = new QPushButton();
    userAvatar->setFixedSize(45, 45);
    userAvatar->setIconSize(QSize(45, 45));
    // 把这个设置默认头像的代码干掉, 此时, 就可以避免出现 "头像的变化"
    userAvatar->setIcon(QIcon(":/resource/image/defaultAvatar.png"));
    userAvatar->setStyleSheet("QPushButton { background-color: transparent; }");
    layout->addWidget(userAvatar, 1, Qt::AlignTop | Qt::AlignHCenter);

    // 添加会话标签页按钮
    sessionTabBtn = new QPushButton();
    sessionTabBtn->setFixedSize(45, 45);
    sessionTabBtn->setIconSize(QSize(30, 30));
    sessionTabBtn->setIcon(QIcon(":/resource/image/session_active.png"));
    sessionTabBtn->setStyleSheet("QPushButton { background-color: transparent; }");
    layout->addWidget(sessionTabBtn, 1, Qt::AlignTop | Qt::AlignHCenter);

    // 添加好友标签页按钮
    friendTabBtn = new QPushButton();
    friendTabBtn->setFixedSize(45, 45);
    friendTabBtn->setIconSize(QSize(30, 30));
    friendTabBtn->setIcon(QIcon(":/resource/image/friend_inactive.png"));
    friendTabBtn->setStyleSheet("QPushButton { background-color: transparent; }");
    layout->addWidget(friendTabBtn, 1, Qt::AlignTop | Qt::AlignHCenter);

    // 添加好友申请标签页按钮
    applyTabBtn = new QPushButton();
    applyTabBtn->setFixedSize(45, 45);
    applyTabBtn->setIconSize(QSize(30, 30));
    applyTabBtn->setIcon(QIcon(":/resource/image/apply_inactive.png"));
    applyTabBtn->setStyleSheet("QPushButton { background-color: transparent; }");
    layout->addWidget(applyTabBtn, 1, Qt::AlignTop | Qt::AlignHCenter);

    layout->addStretch(20);
}

void MainWidget::initMidWindow()
{

}

void MainWidget::initRightWindow()
{

}

展示:
在这里插入图片描述

1.2 窗口中间部分

中间部分搜索框和中间部分列表区设置
mainwidget.h文件

    // 用户搜索框
    QLineEdit* searchEdit;
    // 添加好友按钮
    QPushButton* addFriendBtn;

    SessionFriendArea* sessionFriendArea;

mainwidget.cpp文件

void MainWidget::initMidWindow()
{
    QGridLayout* layout = new QGridLayout();
    // 距离上方有 20px 的距离, 另外三个方向都不要边距
    layout->setContentsMargins(0, 20, 0, 0);
    layout->setHorizontalSpacing(0);
    layout->setVerticalSpacing(10);
    windowMid->setLayout(layout);

    searchEdit = new QLineEdit();
    searchEdit->setFixedHeight(30);
    searchEdit->setPlaceholderText("搜索");
    searchEdit->setStyleSheet("QLineEdit { border-radius: 5px; background-color: rgb(226, 226, 226); padding-left: 5px;}");

    addFriendBtn = new QPushButton();
    addFriendBtn->setFixedSize(30, 30);
    addFriendBtn->setIcon(QIcon(":/resource/image/cross.png"));
    QString style = "QPushButton { border-radius: 5px; background-color: rgb(226, 226, 226); }";
    style += " QPushButton:pressed { background-color: rgb(240, 240, 240); }";
    addFriendBtn->setStyleSheet(style);

    sessionFriendArea = new SessionFriendArea();

    // 为了更灵活的控制边距, 只影响搜索框按钮这一行, 不影响下方列表这一行
    // 创建空白的 widget 填充到布局管理器中.
    QWidget* spacer1 = new QWidget();
    spacer1->setFixedWidth(10);
    QWidget* spacer2 = new QWidget();
    spacer2->setFixedWidth(10);
    QWidget* spacer3 = new QWidget();
    spacer3->setFixedWidth(10);

    layout->addWidget(spacer1, 0, 0);
    layout->addWidget(searchEdit, 0, 1);
    layout->addWidget(spacer2, 0, 2);
    layout->addWidget(addFriendBtn, 0, 3);
    layout->addWidget(spacer3, 0, 4);
    layout->addWidget(sessionFriendArea, 1, 0, 1, 5);
}

在这里插入图片描述
注意,列表区需要新建一个sessionFriendArea的qt文件继承QWidget

1.2.1 窗口中间部分sessionFriendArea的实现

包含会话好友列表
sessionFriendArea.h文件

#include <QWidget>
#include <QScrollArea>
#include <QLabel>

//
/// 滚动区域中的 Item 的类型
//

enum ItemType {
    SessionItemType,
    FriendItemType,
    ApplyItemType
};


///
/// 整个滚动区域的实现
///
class SessionFriendArea : public QScrollArea
{
    Q_OBJECT
public:
    explicit SessionFriendArea(QWidget *parent = nullptr);
    
private:
    // 后续往 container 内部的 layout 中添加元素, 就能够触发 QScrollArea 滚动效果.
    QWidget* container;
    
    
    // 清空该区域中所有的 item
    void clear();
    
    // 添加一个 item 到该区域中, itemType 表示添加哪种 item, id 跟着不同的 itemType 有不同的含义.
    // 如果是 SessionItem, id 就是 chatSessionId
    // 如果是 FriendItem / ApplyItem, id 就是 userId
    void addItem(ItemType itemType, const QString& id, const QIcon& avatar, const QString& name, const QString& text);
    
    // 选中某个指定的 item, 通过 index 下标来进行选择
    void clickItem(int index);
    
signals:
};

///
/// 滚动区域中的 Item 的实现
///

class SessionFriendItem : public QWidget {
    Q_OBJECT
public:
    SessionFriendItem(QWidget* owner, const QIcon& avatar, const QString& name, const QString& text);
    
    void paintEvent(QPaintEvent* event) override;
    void mousePressEvent(QMouseEvent* event) override;
    void enterEvent(QEnterEvent* event) override;
    void leaveEvent(QEvent* event) override;
    
    void select();
    // active 函数期望实现 Item 被点击之后的业务逻辑.
    virtual void active();
private:
    // owner 就指向了上述的 SessionFriendArea
    QWidget* owner;
    
    // 这个变量用来表示当前 Item 是否是 "选中" 状态
    bool selected = false;
protected:
    // 让这个成员被子类访问
    QLabel* messageLabel;
    
};

///
/// 会话 Item 的实现
///


class SessionItem : public SessionFriendItem {
    Q_OBJECT
    
public:
    SessionItem(QWidget* owner, const QString& chatSessionId, const QIcon& avatar,
                const QString& name, const QString& lastMessage);
    void active() override;
private:
    // 当前会话 id
    QString chatSessionId;
    
};

/
/// 好友 Item 的实现
//

class FriendItem : public SessionFriendItem {
    Q_OBJECT
    
public:
    FriendItem(QWidget* owner, const QString& userId, const QIcon& avatar, const QString& name,
               const QString& description);
    void active() override;
    
private:
    // 好友的用户id
    QString userId;
};

//
/// 好友申请 Item 的实现
//
class ApplyItem : public SessionFriendItem {
    Q_OBJECT
    
public:
    // 此处不需要显示一个 附加的文本了. 比上面的两个 Item 的构造函数, 少了一个参数
    ApplyItem(QWidget* owner, const QString& userId, const QIcon& avatar, const QString& name);
    void active() override;
    
private:
    // 申请人的 userId
    QString userId;
};

sessionFriendArea.cpp文件

#include <QScrollBar>
#include <QVBoxLayout>
#include <QPushButton>
#include <QLabel>
#include <QStyleOption>
#include <QPainter>


#include "model/data.h"
#include "debug.h"

SessionFriendArea::SessionFriendArea(QWidget *parent)
    : QScrollArea{parent}
{
    // 1. 设置必要的属性
    // 设置了这个属性, 才能够开启滚动效果
    this->setWidgetResizable(true);
    // 设置滚动条相关的样式
    this->verticalScrollBar()->setStyleSheet("QScrollBar:vertical { width: 2px; background-color: rgb(46, 46, 46);}");
    this->horizontalScrollBar()->setStyleSheet("QScrollBar:horizontal { height: 0px; }");
    this->setStyleSheet("QWidget { border: none;}");

    // 2. 把 widget 创建出来
    container = new QWidget();
    container->setFixedWidth(310);
    this->setWidget(container);

    // 3. 给这个 widget 指定布局管理器, 以便后续添加元素进去
    QVBoxLayout* layout = new QVBoxLayout();
    layout->setContentsMargins(0, 0, 0, 0);
    layout->setSpacing(0);
    layout->setAlignment(Qt::AlignTop);
    container->setLayout(layout);

    // 简单测试一下滚动的效果, 先去掉, 后续添加 Item
    // for (int i = 0; i < 50; ++i) {
    //     QPushButton* btn = new QPushButton();
    //     btn->setText("按钮");
    //     layout->addWidget(btn);
    // }

    // 构造出一些临时数据, 用来作为 "界面调试" 依据. 后 续要删除掉
#if TEST_UI
    QIcon icon(":/resource/image/defaultAvatar.png");
    for (int i = 0; i < 30; ++i) {
        this->addItem(SessionItemType, QString::number(i), icon, "张三" + QString::number(i), "最后一条消息" + QString::number(i));
    }

#endif

}

void SessionFriendArea::clear()
{
    QLayout* layout = container->layout();
    // 遍历布局管理器中的所有元素, 并依次从布局管理器中删除掉
    for (int i = layout->count() - 1; i >= 0; --i) {
        // takeAt 就能移除对应下标的元素
        QLayoutItem* item = layout->takeAt(i);
        // 别忘了, 还需要对这个对象进行 "释放"
        if (item->widget()) {
            // 把这个移除的内容的 widget 进行释放.
            // 正常使用的时候, new 出来的对象添加到布局管理器的....
            delete item->widget();
        }
    }

}

// 此时这个函数添加的就不是 SessionFriendItem 了, 而是 SessionFriendItem 的子类.
// SessionItem, FriendItem, ApplyItem 其中的一个.

void SessionFriendArea::addItem(ItemType itemType, const QString& id, const QIcon &avatar, const QString &name, const QString &text)
{
    SessionFriendItem* item = nullptr;
    if (itemType == SessionItemType) {
        item = new SessionItem(this, id, avatar, name, text);
    } else if (itemType == FriendItemType) {
        item = new FriendItem(this, id, avatar, name, text);
    } else if (itemType == ApplyItemType) {
        item = new ApplyItem(this, id, avatar, name);
    } else {
        LOG() << "错误的 ItemType! itemType=" << itemType;
        return;
    }
    container->layout()->addWidget(item);
}

void SessionFriendArea::clickItem(int index)
{
    if (index < 0 || index >= container->layout()->count()) {
        LOG() << "点击元素的下标超出范围! index=" << index;
        return;
    }
    QLayoutItem* layoutItem = container->layout()->itemAt(index);
    if (layoutItem == nullptr || layoutItem->widget() == nullptr) {
        LOG() << "指定的元素不存在! index=" << index;
        return;
    }
    SessionFriendItem* item = dynamic_cast<SessionFriendItem*>(layoutItem->widget());
    item->select();
}

///
/// 滚动区域中的 Item 的实现
///

SessionFriendItem::SessionFriendItem(QWidget *owner, const QIcon &avatar, const QString &name, const QString &text)
    : owner(owner)
{
    this->setFixedHeight(70);
    this->setStyleSheet("QWidget { background-color: rgb(231, 231, 231); }");

    // 创建网格布局管理器
    QGridLayout* layout = new QGridLayout();
    layout->setContentsMargins(20, 0, 0, 0);
    layout->setHorizontalSpacing(10);
    layout->setVerticalSpacing(0);
    this->setLayout(layout);

    // 创建头像
    QPushButton* avatarBtn = new QPushButton();
    avatarBtn->setFixedSize(50, 50);
    avatarBtn->setIconSize(QSize(50, 50));
    avatarBtn->setIcon(avatar);
    avatarBtn->setStyleSheet("QPushButton {border: none;}");
    avatarBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);

    // 创建名字
    QLabel* nameLabel = new QLabel();
    nameLabel->setText(name);
    nameLabel->setStyleSheet("QLabel { font-size: 18px; font-weight: 600; }");
    nameLabel->setFixedHeight(35);
    nameLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);

    // 创建消息预览的 label
    messageLabel = new QLabel();
    messageLabel->setText(text);
    messageLabel->setFixedHeight(35);
    messageLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);

    // 头像处于 0, 0 位置, 占据 2 行, 占据 2 列
    layout->addWidget(avatarBtn, 0, 0, 2, 2);
    // 名字处于 0, 2 位置, 占据 1 行, 占据 1 列
    layout->addWidget(nameLabel, 0, 2, 1, 1);
    // 消息预览处于 1, 2 位置, 占据 1 行, 占据 1 列
    layout->addWidget(messageLabel, 1, 2, 1, 1);
}

void SessionFriendItem::select()
{
    // 鼠标点击时会触发这个函数
    // 拿到所有的兄弟元素
    const QObjectList children = this->parentWidget()->children();
    for (QObject* child : children) {
        if (!child->isWidgetType()) {
            // 判定是否是 widget.
            continue;
        }
        // 确实是 widget, 就把这里的child 强转成 SessionFriendItem
        SessionFriendItem* item = dynamic_cast<SessionFriendItem*>(child);
        if (item->selected) {
            item->selected = false;
            item->setStyleSheet("QWidget { background-color: rgb(231, 231, 231); }");
        }
    }

    // 点击时, 修改背景色.
    // 此处不仅仅要设置当前 item 背景色, 也要还原其他元素的背景色.
    this->setStyleSheet("QWidget { background-color: rgb(210, 210, 210); }");
    this->selected = true;


}

void SessionFriendItem::active()
{

}

void SessionFriendItem::paintEvent(QPaintEvent *event)
{
    (void) event;
    QStyleOption opt;
    opt.initFrom(this);
    QPainter p(this);
    style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);


}

void SessionFriendItem::mousePressEvent(QMouseEvent *event)
{
    (void) event;
    select();
}

void SessionFriendItem::enterEvent(QEnterEvent *event)
{
    (void) event;

    // 当前这个 item 是选中状态, 则背景色不受到该逻辑影响
    if (this->selected) {
        return;
    }
    // 设置一个更深的颜色
    this->setStyleSheet("QWidget { background-color: rgb(215, 215, 215);}");

}

void SessionFriendItem::leaveEvent(QEvent *event)
{
    (void) event;

    // 当前这个 item 是选中状态, 则背景色不受到该逻辑影响
    if (this->selected) {
        return;
    }
    // 还原背景色
    this->setStyleSheet("QWidget { background-color: rgb(231, 231, 231);}");
}

///
/// 会话 Item 的实现
///


SessionItem::SessionItem(QWidget *owner, const QString &chatSessionId, const QIcon &avatar, const QString &name, const QString &lastMessage)
    : SessionFriendItem(owner, avatar, name, lastMessage), chatSessionId(chatSessionId)
{

}

void SessionItem::active()
{

}


/
/// 好友 Item 的实现
//
FriendItem::FriendItem(QWidget *owner, const QString &userId, const QIcon &avatar, const QString &name, const QString &description)
: SessionFriendItem(owner, avatar, name, description), userId(userId)
{

}

void FriendItem::active()
{

}

//
/// 好友申请 Item 的实现
//
ApplyItem::ApplyItem(QWidget *owner, const QString &userId, const QIcon &avatar, const QString &name)
:SessionFriendItem(owner, avatar, name, ""), userId(userId)
{
    // 1. 移除父类的 messageLabel
    QGridLayout* layout = dynamic_cast<QGridLayout*>(this->layout());
    layout->removeWidget(messageLabel);
    // 要记得释放内存, 否则会内存泄露.
    delete messageLabel;

    // 2. 创建两个按钮出来
    QPushButton* acceptBtn = new QPushButton();
    acceptBtn->setText("同意");
    QPushButton* rejectBtn = new QPushButton();
    rejectBtn->setText("拒绝");

    // 3. 添加到布局管理器中
    layout->addWidget(acceptBtn, 1, 2, 1, 1);
    layout->addWidget(rejectBtn, 1, 3, 1, 1);

}

void ApplyItem::active()
{

}

在这里插入图片描述

1.3 窗口右侧部分

mainwidget.h文件

    // 消息展示区
    MessageShowArea*  messageShowArea;

    // 消息编辑区
    MessageEditArea* messageEditArea;


mainwidget.cpp文件

  // 1. 创建右侧窗口的布局管理器
    QVBoxLayout* vlayout = new QVBoxLayout();
    vlayout->setSpacing(0);
    vlayout->setContentsMargins(0, 0, 0, 0);
    vlayout->setAlignment(Qt::AlignTop);
    windowRight->setLayout(vlayout);

    // 2. 创建上方标题栏
    QWidget* titleWidget = new QWidget();
    titleWidget->setFixedHeight(62);
    titleWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    titleWidget->setObjectName("titleWidget");
    titleWidget->setStyleSheet("#titleWidget { border-bottom: 1px solid rgb(230, 230, 230); border-left: 1px solid rgb(230, 230, 230); }");
    vlayout->addWidget(titleWidget);

    // 3. 给标题栏, 添加标题 label 和 一个按钮
    QHBoxLayout* hlayout = new QHBoxLayout();
    hlayout->setSpacing(0);
    // 使标题的 label 和 按钮距离左右两侧的边界, 有点间距.
    hlayout->setContentsMargins(10, 0, 10, 0);
    titleWidget->setLayout(hlayout);

    sessionTitleLabel = new QLabel();
    sessionTitleLabel->setStyleSheet("QLabel { font-size: 22px; border-bottom: 1px solid rgb(230, 230, 230);}");
#if TEST_UI
    // 为了测试界面临时增加的. 实际这里的内容, 应该是使用从服务器获取的数据来设置.
    sessionTitleLabel->setText("这是会话标题");
#endif

    hlayout->addWidget(sessionTitleLabel);

    extraBtn = new QPushButton();
    extraBtn->setFixedSize(30, 30);
    extraBtn->setIconSize(QSize(30, 30));
    extraBtn->setIcon(QIcon(":/resource/image/more.png"));
    extraBtn->setStyleSheet("QPushButton { border:none; background-color: rgb(225, 225, 225); } QPushButton:pressed { background-color: rgb(210, 220, 220); }");
    hlayout->addWidget(extraBtn);

      // 4. 添加消息展示区
    messageShowArea = new MessageShowArea();
    vlayout->addWidget(messageShowArea);

    // 5. 添加消息编辑区
    messageEditArea = new MessageEditArea();
    // 确保消息编辑区, 处于窗口的下方.
    vlayout->addWidget(messageEditArea, 0, Qt::AlignBottom);




一、额外操作

1.为了设置图标,需要把图标图片导入到项目中,通过qrc文件管理
在这里插入图片描述
在这里插入图片描述
添加文件,创建文件,然后把需要的图片放入文件夹里,进行打开。
在这里插入图片描述

在这里插入图片描述

2.设置测试文件debug
在这里插入图片描述

然后界面测试

    // 构造出一些临时数据, 用来作为 "界面调试" 依据. 后 续要删除掉
#if TEST_UI
    QIcon icon(":/resource/image/defaultAvatar.png");
    for (int i = 0; i < 30; ++i) {
        this->addItem(SessionItemType, QString::number(i), icon, "张三" + QString::number(i), "最后一条消息" + QString::number(i));
    }
#endif

3.编辑右侧界面时,新建消息展示区,新建消息编辑区

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值