聊天项目(23) 侧边栏切换和搜索列表

侧边栏按钮

我们接下来实现侧边栏按钮功能,希望点击一个按钮,清空其他按钮的选中状态。而我们又希望按钮上面能在有新的通知的时候出现红点的图标,所以不能用简单的按钮,要用自定义的一个widget实现点击效果

我们自定义StateWidget ,声明如下

  1. class StateWidget : public QWidget
  2. {
  3. Q_OBJECT
  4. public:
  5. explicit StateWidget(QWidget *parent = nullptr);
  6. void SetState(QString normal="", QString hover="", QString press="",
  7. QString select="", QString select_hover="", QString select_press="");
  8. ClickLbState GetCurState();
  9. void ClearState();
  10. void SetSelected(bool bselected);
  11. void AddRedPoint();
  12. void ShowRedPoint(bool show=true);
  13. protected:
  14. void paintEvent(QPaintEvent* event);
  15. virtual void mousePressEvent(QMouseEvent *ev) override;
  16. virtual void mouseReleaseEvent(QMouseEvent *ev) override;
  17. virtual void enterEvent(QEvent* event) override;
  18. virtual void leaveEvent(QEvent* event) override;
  19. private:
  20. QString _normal;
  21. QString _normal_hover;
  22. QString _normal_press;
  23. QString _selected;
  24. QString _selected_hover;
  25. QString _selected_press;
  26. ClickLbState _curstate;
  27. QLabel * _red_point;
  28. signals:
  29. void clicked(void);
  30. signals:
  31. public slots:
  32. };

接下来实现定义

  1. StateWidget::StateWidget(QWidget *parent): QWidget(parent),_curstate(ClickLbState::Normal)
  2. {
  3. setCursor(Qt::PointingHandCursor);
  4. //添加红点
  5. AddRedPoint();
  6. }
  7. void StateWidget::SetState(QString normal, QString hover, QString press, QString select, QString select_hover, QString select_press)
  8. {
  9. _normal = normal;
  10. _normal_hover = hover;
  11. _normal_press = press;
  12. _selected = select;
  13. _selected_hover = select_hover;
  14. _selected_press = select_press;
  15. setProperty("state",normal);
  16. repolish(this);
  17. }
  18. ClickLbState StateWidget::GetCurState()
  19. {
  20. return _curstate;
  21. }
  22. void StateWidget::ClearState()
  23. {
  24. _curstate = ClickLbState::Normal;
  25. setProperty("state",_normal);
  26. repolish(this);
  27. update();
  28. }
  29. void StateWidget::SetSelected(bool bselected)
  30. {
  31. if(bselected){
  32. _curstate = ClickLbState::Selected;
  33. setProperty("state",_selected);
  34. repolish(this);
  35. update();
  36. return;
  37. }
  38. _curstate = ClickLbState::Normal;
  39. setProperty("state",_normal);
  40. repolish(this);
  41. update();
  42. return;
  43. }
  44. void StateWidget::AddRedPoint()
  45. {
  46. //添加红点示意图
  47. _red_point = new QLabel();
  48. _red_point->setObjectName("red_point");
  49. QVBoxLayout* layout2 = new QVBoxLayout;
  50. _red_point->setAlignment(Qt::AlignCenter);
  51. layout2->addWidget(_red_point);
  52. layout2->setMargin(0);
  53. this->setLayout(layout2);
  54. _red_point->setVisible(false);
  55. }
  56. void StateWidget::ShowRedPoint(bool show)
  57. {
  58. _red_point->setVisible(true);
  59. }
  60. void StateWidget::paintEvent(QPaintEvent *event)
  61. {
  62. QStyleOption opt;
  63. opt.init(this);
  64. QPainter p(this);
  65. style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
  66. return;
  67. }
  68. void StateWidget::mousePressEvent(QMouseEvent *event)
  69. {
  70. if (event->button() == Qt::LeftButton) {
  71. if(_curstate == ClickLbState::Selected){
  72. qDebug()<<"PressEvent , already to selected press: "<< _selected_press;
  73. //emit clicked();
  74. // 调用基类的mousePressEvent以保证正常的事件处理
  75. QWidget::mousePressEvent(event);
  76. return;
  77. }
  78. if(_curstate == ClickLbState::Normal){
  79. qDebug()<<"PressEvent , change to selected press: "<< _selected_press;
  80. _curstate = ClickLbState::Selected;
  81. setProperty("state",_selected_press);
  82. repolish(this);
  83. update();
  84. }
  85. return;
  86. }
  87. // 调用基类的mousePressEvent以保证正常的事件处理
  88. QWidget::mousePressEvent(event);
  89. }
  90. void StateWidget::mouseReleaseEvent(QMouseEvent *event)
  91. {
  92. if (event->button() == Qt::LeftButton) {
  93. if(_curstate == ClickLbState::Normal){
  94. //qDebug()<<"ReleaseEvent , change to normal hover: "<< _normal_hover;
  95. setProperty("state",_normal_hover);
  96. repolish(this);
  97. update();
  98. }else{
  99. //qDebug()<<"ReleaseEvent , change to select hover: "<< _selected_hover;
  100. setProperty("state",_selected_hover);
  101. repolish(this);
  102. update();
  103. }
  104. emit clicked();
  105. return;
  106. }
  107. // 调用基类的mousePressEvent以保证正常的事件处理
  108. QWidget::mousePressEvent(event);
  109. }
  110. void StateWidget::enterEvent(QEvent *event)
  111. {
  112. // 在这里处理鼠标悬停进入的逻辑
  113. if(_curstate == ClickLbState::Normal){
  114. //qDebug()<<"enter , change to normal hover: "<< _normal_hover;
  115. setProperty("state",_normal_hover);
  116. repolish(this);
  117. update();
  118. }else{
  119. //qDebug()<<"enter , change to selected hover: "<< _selected_hover;
  120. setProperty("state",_selected_hover);
  121. repolish(this);
  122. update();
  123. }
  124. QWidget::enterEvent(event);
  125. }
  126. void StateWidget::leaveEvent(QEvent *event)
  127. {
  128. // 在这里处理鼠标悬停离开的逻辑
  129. if(_curstate == ClickLbState::Normal){
  130. // qDebug()<<"leave , change to normal : "<< _normal;
  131. setProperty("state",_normal);
  132. repolish(this);
  133. update();
  134. }else{
  135. // qDebug()<<"leave , change to select normal : "<< _selected;
  136. setProperty("state",_selected);
  137. repolish(this);
  138. update();
  139. }
  140. QWidget::leaveEvent(event);
  141. }

为了让按钮好看一点,我们修改下qss文件

  1. #chat_user_name {
  2. color:rgb(153,153,153);
  3. font-size: 14px;
  4. font-family: "Microsoft YaHei";
  5. }
  6. #side_chat_lb[state='normal']{
  7. border-image: url(:/res/chat_icon.png);
  8. }
  9. #side_chat_lb[state='hover']{
  10. border-image: url(:/res/chat_icon_hover.png);
  11. }
  12. #side_chat_lb[state='pressed']{
  13. border-image: url(:/res/chat_icon_press.png);
  14. }
  15. #side_chat_lb[state='selected_normal']{
  16. border-image: url(:/res/chat_icon_press.png);
  17. }
  18. #side_chat_lb[state='selected_hover']{
  19. border-image: url(:/res/chat_icon_press.png);
  20. }
  21. #side_chat_lb[state='selected_pressed']{
  22. border-image: url(:/res/chat_icon_press.png);
  23. }
  24. #side_contact_lb[state='normal']{
  25. border-image: url(:/res/contact_list.png);
  26. }
  27. #side_contact_lb[state='hover']{
  28. border-image: url(:/res/contact_list_hover.png);
  29. }
  30. #side_contact_lb[state='pressed']{
  31. border-image: url(:/res/contact_list_press.png);
  32. }
  33. #side_contact_lb[state='selected_normal']{
  34. border-image: url(:/res/contact_list_press.png);
  35. }
  36. #side_contact_lb[state='selected_hover']{
  37. border-image: url(:/res/contact_list_press.png);
  38. }
  39. #side_contact_lb[state='selected_pressed']{
  40. border-image: url(:/res/contact_list_press.png);
  41. }

回到ChatDialog.ui中,将side_chat_lb改为StateWidget,side_contact_lb改为StateWidget。

https://cdn.llfc.club/1719028635439.jpg

接下来回到ChatDialog.cpp中构造函数中添加

  1. QPixmap pixmap(":/res/head_1.jpg");
  2. ui->side_head_lb->setPixmap(pixmap); // 将图片设置到QLabel上
  3. QPixmap scaledPixmap = pixmap.scaled( ui->side_head_lb->size(), Qt::KeepAspectRatio); // 将图片缩放到label的大小
  4. ui->side_head_lb->setPixmap(scaledPixmap); // 将缩放后的图片设置到QLabel上
  5. ui->side_head_lb->setScaledContents(true); // 设置QLabel自动缩放图片内容以适应大小
  6. ui->side_chat_lb->setProperty("state","normal");
  7. ui->side_chat_lb->SetState("normal","hover","pressed","selected_normal","selected_hover","selected_pressed");
  8. ui->side_contact_lb->SetState("normal","hover","pressed","selected_normal","selected_hover","selected_pressed");
  9. AddLBGroup(ui->side_chat_lb);
  10. AddLBGroup(ui->side_contact_lb);
  11. connect(ui->side_chat_lb, &StateWidget::clicked, this, &ChatDialog::slot_side_chat);
  12. connect(ui->side_contact_lb, &StateWidget::clicked, this, &ChatDialog::slot_side_contact);

切换函数中实现如下

  1. void ChatDialog::slot_side_chat()
  2. {
  3. qDebug()<< "receive side chat clicked";
  4. ClearLabelState(ui->side_chat_lb);
  5. ui->stackedWidget->setCurrentWidget(ui->chat_page);
  6. _state = ChatUIMode::ChatMode;
  7. ShowSearch(false);
  8. }

上述函数我们实现了清楚其他标签选中状态,只将被点击的标签设置为选中的效果,核心功能是下面

  1. void ChatDialog::ClearLabelState(StateWidget *lb)
  2. {
  3. for(auto & ele: _lb_list){
  4. if(ele == lb){
  5. continue;
  6. }
  7. ele->ClearState();
  8. }
  9. }

我们在构造函数里将要管理的标签通过AddGroup函数加入_lb_list实现管理

  1. void ChatDialog::AddLBGroup(StateWidget *lb)
  2. {
  3. _lb_list.push_back(lb);
  4. }

搜索列表类

在pro中添加我们自定义一个搜索列表类

  1. class SearchList: public QListWidget
  2. {
  3. Q_OBJECT
  4. public:
  5. SearchList(QWidget *parent = nullptr);
  6. void CloseFindDlg();
  7. void SetSearchEdit(QWidget* edit);
  8. protected:
  9. bool eventFilter(QObject *watched, QEvent *event) override {
  10. // 检查事件是否是鼠标悬浮进入或离开
  11. if (watched == this->viewport()) {
  12. if (event->type() == QEvent::Enter) {
  13. // 鼠标悬浮,显示滚动条
  14. this->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
  15. } else if (event->type() == QEvent::Leave) {
  16. // 鼠标离开,隐藏滚动条
  17. this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  18. }
  19. }
  20. // 检查事件是否是鼠标滚轮事件
  21. if (watched == this->viewport() && event->type() == QEvent::Wheel) {
  22. QWheelEvent *wheelEvent = static_cast<QWheelEvent*>(event);
  23. int numDegrees = wheelEvent->angleDelta().y() / 8;
  24. int numSteps = numDegrees / 15; // 计算滚动步数
  25. // 设置滚动幅度
  26. this->verticalScrollBar()->setValue(this->verticalScrollBar()->value() - numSteps);
  27. return true; // 停止事件传递
  28. }
  29. return QListWidget::eventFilter(watched, event);
  30. }
  31. private:
  32. void waitPending(bool pending = true);
  33. bool _send_pending;
  34. void addTipItem();
  35. std::shared_ptr<QDialog> _find_dlg;
  36. QWidget* _search_edit;
  37. LoadingDlg * _loadingDialog;
  38. private slots:
  39. void slot_item_clicked(QListWidgetItem *item);
  40. void slot_user_search(std::shared_ptr<SearchInfo> si);
  41. signals:
  42. };

然后在构造函数中初始化条目列表

  1. SearchList::SearchList(QWidget *parent):QListWidget(parent),_find_dlg(nullptr), _search_edit(nullptr), _send_pending(false)
  2. {
  3. Q_UNUSED(parent);
  4. this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  5. this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  6. // 安装事件过滤器
  7. this->viewport()->installEventFilter(this);
  8. //连接点击的信号和槽
  9. connect(this, &QListWidget::itemClicked, this, &SearchList::slot_item_clicked);
  10. //添加条目
  11. addTipItem();
  12. //连接搜索条目
  13. connect(TcpMgr::GetInstance().get(), &TcpMgr::sig_user_search, this, &SearchList::slot_user_search);
  14. }

addTipItem是用来添加一个一个条目的

  1. void SearchList::addTipItem()
  2. {
  3. auto *invalid_item = new QWidget();
  4. QListWidgetItem *item_tmp = new QListWidgetItem;
  5. //qDebug()<<"chat_user_wid sizeHint is " << chat_user_wid->sizeHint();
  6. item_tmp->setSizeHint(QSize(250,10));
  7. this->addItem(item_tmp);
  8. invalid_item->setObjectName("invalid_item");
  9. this->setItemWidget(item_tmp, invalid_item);
  10. item_tmp->setFlags(item_tmp->flags() & ~Qt::ItemIsSelectable);
  11. auto *add_user_item = new AddUserItem();
  12. QListWidgetItem *item = new QListWidgetItem;
  13. //qDebug()<<"chat_user_wid sizeHint is " << chat_user_wid->sizeHint();
  14. item->setSizeHint(add_user_item->sizeHint());
  15. this->addItem(item);
  16. this->setItemWidget(item, add_user_item);
  17. }

sig_user_search可以先在TcpMgr中声明信号

  1. void sig_user_search(std::shared_ptr<SearchInfo>);

SearchInfo定义在userdata.h中

  1. class SearchInfo {
  2. public:
  3. SearchInfo(int uid, QString name, QString nick, QString desc, int sex);
  4. int _uid;
  5. QString _name;
  6. QString _nick;
  7. QString _desc;
  8. int _sex;
  9. };

接下来实现我们自定义的AddUserItem, 在pro中添加qt设计师界面类AddUserItem

  1. class AddUserItem : public ListItemBase
  2. {
  3. Q_OBJECT
  4. public:
  5. explicit AddUserItem(QWidget *parent = nullptr);
  6. ~AddUserItem();
  7. QSize sizeHint() const override {
  8. return QSize(250, 70); // 返回自定义的尺寸
  9. }
  10. protected:
  11. private:
  12. Ui::AddUserItem *ui;
  13. };

实现

  1. AddUserItem::AddUserItem(QWidget *parent) :
  2. ListItemBase(parent),
  3. ui(new Ui::AddUserItem)
  4. {
  5. ui->setupUi(this);
  6. SetItemType(ListItemType::ADD_USER_TIP_ITEM);
  7. }
  8. AddUserItem::~AddUserItem()
  9. {
  10. delete ui;
  11. }

我们将ChatDialog.ui中将search_list升级为SearchList类型

美化界面

我们用qss美化界面

  1. #search_edit {
  2. border: 2px solid #f1f1f1;
  3. }
  4. /* 搜索框列表*/
  5. #search_list {
  6. background-color: rgb(247,247,248);
  7. border: none;
  8. }
  9. #search_list::item:selected {
  10. background-color: #d3d7d4;
  11. border: none;
  12. outline: none;
  13. }
  14. #search_list::item:hover {
  15. background-color: rgb(206,207,208);
  16. border: none;
  17. outline: none;
  18. }
  19. #search_list::focus {
  20. border: none;
  21. outline: none;
  22. }
  23. #invalid_item {
  24. background-color: #eaeaea;
  25. border: none;
  26. }
  27. #add_tip {
  28. border-image: url(:/res/addtip.png);
  29. }
  30. #right_tip{
  31. border-image: url(:/res/right_tip.png);
  32. }
  33. #message_tip{
  34. text-align: center;
  35. font-family: "Microsoft YaHei";
  36. font-size: 12pt;
  37. }

我们在ChatDialog的构造函数中添加

  1. //链接搜索框输入变化
  2. connect(ui->search_edit, &QLineEdit::textChanged, this, &ChatDialog::slot_text_changed);

slot_text_changed槽函数中实现

  1. void ChatDialog::slot_text_changed(const QString &str)
  2. {
  3. //qDebug()<< "receive slot text changed str is " << str;
  4. if (!str.isEmpty()) {
  5. ShowSearch(true);
  6. }
  7. }

源码和视频

再次启动后在输入框输入文字,就会显示搜索框

https://cdn.llfc.club/1719113143252.jpg

视频

https://www.bilibili.com/video/BV1uM4m1U7MP/?spm_id_from=333.999.0.0&vd_source=8be9e83424c2ed2c9b2a3ed1d01385e9

源码链接

https://gitee.com/secondtonone1/llfcchat

热门评论

热门文章

  1. slice介绍和使用

    喜欢(521) 浏览(1927)
  2. 解密定时器的实现细节

    喜欢(566) 浏览(2262)
  3. C++ 类的继承封装和多态

    喜欢(588) 浏览(3175)
  4. Linux环境搭建和编码

    喜欢(594) 浏览(6835)
  5. windows环境搭建和vscode配置

    喜欢(587) 浏览(1850)

最新评论

  1. interface应用 secondtonone1:interface是万能类型,但是使用时要转换为实际类型来使用。interface丰富了go的多态特性,也降低了传统面向对象语言的耦合性。
  2. 堆排序 secondtonone1:堆排序非常实用,定时器就是这个原理制作的。
  3. asio多线程模式IOThreadPool secondtonone1:这么优秀吗
  4. 互斥与死锁 Vstronzw://仅提供一份参考代码给同样初学者的我们[脱单doge] /* 定义了如下栈, 对于多线程访问时判断栈是否为空, 此后两个线程同时出栈,可能会造成崩溃 */ #include <iostream> #include <string> #include <thread> #include <vector> #include <mutex> #include <stack> #include <exception> using namespace std; struct empty_stack : public std::exception { public: const char* what()const throw() //函数后面必须跟throw(),括号里面不能有任务参数,表示不抛出任务异常 //因为这个已经是一个异常处理信息了,不能再抛异常。 { return "empty_stack"; } }; //struct empty_stack : std::exception //{ // const char* what() const throw(); //}; template<typename T> class threadsafe_stack { private: std::stack<T> data; mutable std::mutex m; public: threadsafe_stack() {} threadsafe_stack(const threadsafe_stack& other) { std::lock_guard<std::mutex> lock(other.m); //①在构造函数的函数体(constructor body)内进行复制操作 data = other.data; } threadsafe_stack& operator=(const threadsafe_stack&) = delete; void push(T new_value) { std::lock_guard<std::mutex> lock(m); data.push(std::move(new_value)); } T pop() { std::lock_guard<std::mutex> lock(m); if (data.empty()) throw empty_stack(); auto element = data.top(); data.pop(); return element; } bool empty() const { std::lock_guard<std::mutex> lock(m); return data.empty(); } }; void test_threadsafe_stack() { threadsafe_stack<int> safe_stack; safe_stack.push(1); std::thread t1([&safe_stack]() { if (!safe_stack.empty()) { std::this_thread::sleep_for(std::chrono::seconds(1)); try { safe_stack.pop(); } catch (empty_stack &e) { cout << e.what() << endl; } } }); std::thread t2([&safe_stack]() { if (!safe_stack.empty()) { std::this_thread::sleep_for(std::chrono::seconds(1)); try { safe_stack.pop(); } catch (empty_stack &e) { cout << e.what() << endl; } } }); t1.join(); t2.join(); } int main() { test_threadsafe_stack(); return 0; } /* empty_stack */
  5. 线程基础 mzx2023:新手好奇问一下,这是什么原因呢?

个人公众号

个人微信