四,中间界面搭建
前言:当项目越来越复杂的时候,或许画草图是非常好的选择
一,初始化中间窗口initMidWindow
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(40);
searchEdit->setPlaceholderText("这查找哦(●ˇ∀ˇ●)!");
searchEdit->setStyleSheet(
"QLineEdit {"
" border: 2px solid rgb(237,190,135);" // 边框颜色
" border-radius: 10px;" // 圆角
" padding: 5px;" // 内边距
" background-color: #F0F0F0;A" // 背景色
" color: #333333;" // 字体颜色
" font-size: 14px;" // 字体大小
" text-align: center;" // 使用样式表来居中
"}"
"QLineEdit:focus {"
" border: 2px solid rgb(239,109,15);" // 聚焦时边框颜色
" background-color: #FFFFFF;" // 聚焦时背景色
"}"
);
addUserBtn =new QPushButton();
addUserBtn->setFixedSize(40,40);
addUserBtn->setIcon(QIcon(":/resource/image/cross.png"));
addUserBtn->setStyleSheet(
"QPushButton {"
" background-color: #F0F0F0;" // 背景颜色
" color: white;" // 字体颜色
" border: none;" // 无边框
" border-radius: 20px;" // 圆角
" padding: 8px 18px;" // 内边距
" cursor: pointer;" // 鼠标指针
"}"
"QPushButton:hover {"
" background-color: #FFFFFF;" // 鼠标悬停时的背景颜色
"}"
"QPushButton:pressed {"
" background-color: #EDBA39;" // 按下时的背景颜色
"}"
);
//为了更灵活的控制边界,只针对搜索按钮这行,所以我们可以创建空白的widget填充到我们的布局管理器中
QWidget* space1 = new QWidget();
space1->setFixedWidth(8);
QWidget* space2 = new QWidget();
space2->setFixedWidth(8);
QWidget* space3 = new QWidget();
space3->setFixedWidth(8);
//统一进行管理布局
layout->addWidget(space1,0,0);
layout->addWidget(searchEdit,0,1);
layout->addWidget(space2,0,2);
layout->addWidget(addUserBtn,0,3);
layout->addWidget(space3,0,4);
SessionFriendArea* sessionFriendArea=new SessionFriendArea();
layout->addWidget(sessionFriendArea,1,0,1,5);//后面的为占据一行,横跨五列
}
-
首先创造一个网格布局管理器的指针
-
设置布局管理器的各项属性
-
将windowMid的布局管理器设为layout
-
创建一个新的
QLineEdit
对象,作为搜索框架,并且初始化各项参数
然后关于QLineEdit,它是 Qt 中用于输入和编辑单行文本的类。
别忘了在mainWidget中加入成员属性。
5.创建添加用户的按钮,并初始化各项外观属性
6.为了保持设置的HorizontalSpacing为0,我们手动增加各种控件。
7.添加SessionFriendArea()并且包含头文件,现在我们的目标就转移到了SessionFriendArea
使用布局管理器统一进行管理,我们画图来显示这么一部分
二,来构建sessionFriendArea吧
1.先来头文件,我们的头文件要继承自QScrollArea
class SessionFriendArea : public QScrollArea
{
//1.这是一个宏,允许该类使用Qt的信号和槽机制
Q_OBJECT
public:
explicit SessionFriendArea(QWidget *parent = nullptr);
//3.清空该区域所有item
void clear();
//4.添加一个item到该区域中(这个后面会再次改动)
void addItem(const QIcon& user, const QString& name, const QString& text);
private:
//5.往container内部的layout后续添加元素,能够触发QScrollArea滚动元素
QWidget* container;//高度会随着里面添加的元素动态增加的。
//通常container会被设置一个布局,该布局会管理其中的子空间,
//并且由于QWidget可以根据控件大小自动调整大小
//并且这个类作为一个滚动区域,它会显示container的可视区域,如果内容超过,会自动增加滚动条
//6.定义信号(待添加,用于实现逻辑)
signals:
};
注释讲解的很清楚了,我们来补充一下Q_OBJECT
2.我们来写构造函数SessionFriendArea(QWidget *parent)
SessionFriendArea::SessionFriendArea(QWidget *parent)
: QScrollArea{parent}
{
//1.设置滚动开启,并设置滚动条样式
this->setWidgetResizable(true);//开启滚动效果
this->verticalScrollBar()->setStyleSheet(
"QScrollBar:vertical {"
" background: #F0F0F0;" // 背景颜色
" width: 2px;" // 滚动条宽度
" border: none;" // 无边框
" border-radius: 1px;" // 圆角
"}"
"QScrollBar::handle:vertical {"
" background: #FFABAB;" // 滚动条颜色
" border-radius: 7px;" // 圆角
" min-height: 7px;" // 滚动条最小高度
"}"
"QScrollBar::handle:vertical:hover {"
" background: #FF6F61;" // 鼠标悬停时的颜色
"}"
"QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {"
" background: none;" // 上下箭头的背景
" height: 0px;"
"}"
"QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {"
" background: none;" // 上下空白区域的背景
"}"
);
;//垂直方向的滚动条
//设置不可见的水平滚动条
this->horizontalScrollBar()->setStyleSheet(
"QScrollBar:horizontal {"
" background: transparent;" // 背景透明
" height: 2px;" // 仍然保持高度为0,隐藏它
"}"
);
this->setStyleSheet("QWidget {border : none;}");
//2.把widget创建出来
container=new QWidget();
container->setFixedWidth(230);
this->setWidget(container);//告诉滚动区,和哪个QWidget配合,和这个宽度为230的区域
//3.给这个container widget指定布局管理器,以便后续添加元素进去
QVBoxLayout* layout=new QVBoxLayout();
layout->setContentsMargins(0,0,0,0);
layout->setSpacing(0);
layout->setAlignment(Qt::AlignTop);
container->setLayout(layout);
//4.简单测试一下UI效果
// for(int i=0;i<500;i++)
// {
// QPushButton* btn =new QPushButton();
// btn->setText("测试");
// layout->addWidget(btn);
// }
//4.测试工程展示ui效果
#if TEST_UI1
QIcon icon(":/resource/image/defaultAvatar1.png");
for(int i=0;i<50;i++)
{
this->addItem(icon,"正切"+QString::number(i),"最后一条消息"+QString::number(i));
}
#endif
}
3.写clear函数
void SessionFriendArea::clear()
{
//1.取出布局管理器
QLayout* layout = container->layout();//遍历布局管理器中的所有元素,并依次从布局管理器中删除掉
// for(int i=0;i<layout->count();++i){// takeAt 就能移除对应下标的元素
// layout->takeAt(i);
// }
//上面的是有问题的
//2.遍历布局管理器的所有元素
for(int i=layout->count()-1;i>=0;i--)
{
QLayoutItem* item=layout->takeAt(i);
//从指定的布局(layout)中移除索引为 i 的布局项(QLayoutItem),并将其返回给item
if(item->widget())
{
delete item->widget();
//移除的item是否有关联的widget,如果有删除widget,释放空间
}
}
}
知识一:QLayoutItem*
这是一个指向 QLayoutItem
类型的指针,表示布局中的一个项目
知识二:takeAt(i)
是 QLayout
类的成员函数,它接受一个整数参数 i
,表示要移除的项目的索引。这个函数会从布局中移除该索引位置的项,并返回指向该项的指针。
知识三:item->widget()
方法返回与当前布局项关联的 QWidget
指针,这个if是在检查item种是否有关联的部件
4.添加sessionFriendArea的项目的函数
//添加一个item到该区域中
void SessionFriendArea::addItem(const QIcon& user, const QString& name, const QString& text)
{
SessionFriendItem* item = new SessionFriendItem(this, user, name, text);
container->layout()->addWidget(item);
}
//包含用户头像,名字,最后一段文本
三,增加一项又一项的item
1.头文件
class SessionFriendItem : public QWidget{
Q_OBJECT;//加上这个宏
public:
SessionFriendItem(QWidget* owner,const QIcon& user,const QString& name,const QString& text);
//为了让QSS正常使用
void paintEvent(QPaintEvent* event)override;
//重写鼠标按压函数
void mousePressEvent(QMouseEvent* event)override;
//重写鼠标触但没压的函数
void enterEvent(QEnterEvent* event)override;
//重写移开鼠标的函数
void leaveEvent(QEvent* event)override;
void select();
private:
//owner指向了上述的SessionFriendArea
QWidget* owner;
bool selected =false;
//这个变量用来表示当前item是否是“选中”状态,实现后续选中效果
};
解释一下这里的owner
2.构造函数
SessionFriendItem::SessionFriendItem(QWidget* owner,const QIcon& user,const QString& name,const QString& text)
:owner(owner)
{
//1.设置样式
this->setFixedHeight(70);//高度
this->setStyleSheet("QWidget { background-color:rgb(70,70,70);}");
//2.添加网格布局管理器
QGridLayout* layout =new QGridLayout();
layout->setContentsMargins(18,0,0,0);
layout->setHorizontalSpacing(10);
layout->setVerticalSpacing(0);
this->setLayout(layout);
//3.创建头像部件
QPushButton* userbtn =new QPushButton();
userbtn->setFixedSize(60,60);
userbtn->setIconSize(QSize(60,60));
userbtn->setIcon(user);
userbtn->setStyleSheet("QPushButton {border:none;}");
userbtn->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);
//4.创建名字部件
QLabel* nameLabel =new QLabel();
nameLabel->setText(name);
nameLabel->setStyleSheet("QLabel {font-size: 20px; color: white; font-weight: 400;}");
nameLabel->setFixedHeight(35);
nameLabel->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Fixed);\
//水平方向拓展,竖直方向固定
//5.创建消息预览的label
QLabel* messageLabel =new QLabel();
messageLabel->setText(text);
messageLabel->setStyleSheet("QLabel {font-size: 15px; color: white; font-weight: 400;}");
messageLabel->setFixedHeight(35);
messageLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
//6.进行布局管理,头像处于 0,0 位置,占据 2行,占据 2列
layout->addWidget(userbtn,0,0,2,2);
layout->addWidget(nameLabel,0,2,1,1);// 名字处于 0,2 位置,占据 1行,占据 1列
layout->addWidget( messageLabel,1,2,1,1);// 消息预览处于 1,2 位置,占据 1 行,占据 1 列layout->addwidget(messageLabel,1,2,1,1);
};
会发现这样不能设置stylesheet,现在我们来进行处理
原因:在Qt种,如果是给QWidget的子类,通过QSS设置背景色默认是不生效的,除非加上官方代码上的特殊代码
方法:
我们重写一下这个函数
void SessionFriendItem::paintEvent(QPaintEvent* event)
{
(void)event;
QStyleOption opt;
opt.initFrom(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget,&opt,&p,this);
}
3.设置各种效果
void SessionFriendItem::mousePressEvent(QMouseEvent* event)
{
(void)event;
select();
}
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(100,100,100);}");
//还原背景色
}
//点击时修改背景色
this->selected =true;
this->setStyleSheet("QWidget { background-color: rgb(200,200,200);}");
}
}
//浮动到上面的情况
void SessionFriendItem::enterEvent(QEnterEvent* event)
{
(void)event;
// 当前这个 item 是选中状态,则背景色不受到该逻辑影响
if(this->selected)
{
return;
}
//设置一个更深的颜色
this->setStyleSheet("QWidget { background-color: rgb(150,150,150);}");
}
//重写移开鼠标的函数
void SessionFriendItem::leaveEvent(QEvent* event)
{
(void)event;
// 当前这个 item 是选中状态,则背景色不受到该逻辑影响
if(this->selected)
{
return;
}
// 还原背景色
this->setStyleSheet("QWidget { background-color: rgb(70,70,70);}");
}
四,封装必要的逻辑
1.实现点击的逻辑
//click函数
void SessionFriendArea::clickItem(int index)
{
if(index<0||index>=container->layout()->count())
{
LOG()<<"指定的元素超出坐标范围,index是"<<index;
return;
}
QLayoutItem* layoutItem=container->layout()->itemAt(index);
//itemAt(index): 这是 QLayout 类的成员函数,它接受一个整数参数 index,表示要获取的布局项的索引。该函数返回对应索引的 QLayoutItem 指针,如果索引超出范围,则返回 nullptr.
if(layoutItem ==nullptr || layoutItem->widget()==nullptr)
{
LOG()<<"指定的元素不存在,index="<<index;
return;
}
//选中状态
SessionFriendItem* item = dynamic_cast<SessionFriendItem*>(layoutItem->widget());
item->select();
}
2.会话item的实现
class SessionItem : public SessionFriendItem
{
Q_OBJECT;
public:
//owner,会话id,用户名字,用户头像,用户最后一条信息
SessionItem(QWidget* owner, const QString& chatSessionId,const QIcon& user,const QString& name,const QString& lastMessage);
private:
//当前会话的id
QString chatSessionId;
};
//继承自父类SessionFriendItem
SessionItem::SessionItem(QWidget* owner, const QString& chatSessionId,const QIcon& user,const QString& name,const QString& lastMessage)
{
}
这个报错的含义是:
**SessionItem**
的构造函数需要显式地初始化基类 **SessionFriendItem**
,因为它没有默认构造函数。你可以在 **SessionItem**
的构造函数初始化列表中调用 **SessionFriendItem**
的构造函数。
SessionItem::SessionItem(QWidget* owner, const QString& chatSessionId,const QIcon& user,const QString& name,const QString& lastMessage)
:SessionFriendItem{owner,user,name,lastMessage},chatSessionId{chatSessionId}
{
}
3.好友item的实现
class FriendItem : public SessionFriendItem
{
Q_OBJECT;
public:
//owner,会话id,用户名字,用户头像,用户最后一条信息
FriendItem(QWidget* owner, const QString& userId,const QIcon& user,const QString& name,const QString& Description);
private:
//好友用户的id
QString userId;
};
FriendItem::FriendItem(QWidget* owner, const QString& userId,const QIcon& user,const QString& name,const QString& Description)
:SessionFriendItem{owner,user,name,Description},userId{userId}
{
}
4.好友申请item的实现
class ApplyItem : public SessionFriendItem
{
Q_OBJECT;
public:
//owner,会话id,用户名字,用户头像,用户最后一条信息
ApplyItem(QWidget* owner, const QString& userId,const QIcon& user,const QString& name);
private:
//申请用户的id
QString userId;
};
因为这里不用text的构造所以我们对text直接传入空字符串
ApplyItem::ApplyItem(QWidget* owner, const QString& userId,const QIcon& user,const QString& name)
:SessionFriendItem{owner,user,name,""},userId{userId}
{
}
5.基于此对additem进行改造
void SessionFriendArea::addItem(ItemType itemtype,const QString id,const QIcon& user, const QString& name, const QString& text)
{
SessionFriendItem* item =nullptr;
if(itemtype==SessionItemType)
{
item=new SessionItem(this,id,user, name, text);
}
else if(itemtype==FriendItemType)
{
item=new FriendItem(this,id,user, name, text);
}
else if(itemtype==ApplyItemType)
{
item=new ApplyItem(this,id,user, name);
}
else{
LOG()<<"错误的item"<<itemtype;
return ;
}
container->layout()->addWidget(item);
}
6.对applyitem界面的调整
//abc代码
ApplyItem::ApplyItem(QWidget* owner, const QString& userId,const QIcon& user,const QString& name)
:SessionFriendItem{owner,user,name,""},userId{userId}
{
//1.移除父类的messageLabel
//将ApplyItem的布局管理器强转为QGridLayout
QGridLayout* layout =dynamic_cast<QGridLayout*>(this->layout());
layout->removeWidget(messageLabel);
//释放内存,否则会内存泄露
delete messageLabel;
//2.创建两个按钮出来并且美化字体
QPushButton* accepBtn =new QPushButton();
accepBtn->setText("同意");
accepBtn->setStyleSheet("QPushButton {font-size: 15px; color: white; font-weight: 400; font-family: 'SimSun'; }");
QPushButton* rejectBtn =new QPushButton();
rejectBtn->setText("拒绝");
rejectBtn->setStyleSheet("QPushButton {font-size: 15px; color: white; font-weight: 400; font-family: 'SimSun'; }");
//3.添加到布局管理器中
layout->addWidget(accepBtn,1,2,1,1);
layout->addWidget(rejectBtn,1,3,1,1);
}
-
强转为网格布局
-
删除掉messageLabel,会发现父类里messageLabel是构造函数的局部变量。
我们改成protected让这个成员能够被子类访问,要记得释放内存,否则会内存泄漏
c.创建两个按钮出来
这是产生的页面效果
d.很丑我们就调整名字、消息所占的列,并且美化字体
产生的效果
7.点击逻辑的准备工作
Sessionltem :点击之后,应该要在主界面右侧消息展示区,加载出对应的消息列表.
Friendltem:点击之后,要切换到会话列表,并且选中对应的会话.
Applyltem:点击之后,无事发生.主要逻辑是靠"同意""拒绝"按钮触发的.
在父类上加虚函数active()
在子类上加重写的active()
并且对鼠标按压的触发做出逻辑实现
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(70,70,70);}");
//还原背景色
}
}
//点击时修改背景色
this->selected =true;
this->setStyleSheet("QWidget { background-color: rgb(200,200,200);}");
//提供逻辑
this->active();
}
告一段落: