首页 > 代码库 > 【大话QT之八】模拟QQ系统设置面板实现功能
【大话QT之八】模拟QQ系统设置面板实现功能
业务需求:
基于网盘客户端的实现,原有网盘的设置面板无论从界面显示还是从业务需求都不能满足我们的正常需求。当前的要求是,模拟QQ系统设置的面板实现当前我们网盘中的基本配置功能。在完成这篇文章时已将基本功能实现完成,虽未整合进网盘客户端中,但基本技术预演已经实现。
QQ系统设置面板分析:
QQ系统设置面板效果图:
QQ系统设置面板功能描述:
由于存在较多的配置,如果每个模块的配置项都设计到一个窗体中,则会存在很多的窗体,不太符合用户的使用体验,且程序编写也比较麻烦。QQ系统设置面板中的实现是,所有的配置项均列在右侧区域中,当移动右侧的滚动条的过程中,如果对应的面板出现则在左边的导航中对应的标题也显示点击的效果。同时,单独点击左侧的导航,右面的区域也显示对应导航的配置项。
实现思路:
左侧:
左侧导航的实现很简单,使用QListWidget完全可以满足我们的需求,至于当点击左侧时右侧显示对应模块的配置项则需要我们添加处理代码完成。
右侧:
由于右侧存在导航栏,因此右侧的区域需要使用QScrollArea控件,这里需要注意两点:1> 在QScrollArea控件中每个模块的配置需要放在一个单独的QWidget中,只 有这样我们在移动滚动条经过某个某块的配置项时才可以识别到。2> 在实现滚动条效果时,看到网上有很多文章说滚动条效果无法实现出来,其实我的做法很简单,在ui窗体中将QScrollArea拖入窗体中,然后设置改窗体的属性:widgetResizable为不选中,即false状态。如果你的QScrollArea是在代码中new出来的,则它默认的widgetResizable是true,必须在代码中将它设置为false才行。
为了直观地说明第一个注意点,在我测试的UI窗体中QScrollArea区域中防止了多个QWidget(每个模块配置对应一个QWidget),贴图如下:
到这里我们面临的问题就是:
1. 当拖动滚动条时如何判断经过某个QWidget,从而显示左侧的对应项?
首先,需要绑定垂直滚动条的valueChanged事件,这样我们才能随时的监控它的移动变化;其次,利用QWidget的visibleRegion()方法,官方关于这个函数的解释是“当paint事件出现的时候返回它清晰的范围,对与可见的widget,它是一个没有被其它widget覆盖的近似的范围;反之返回一个空的范围”,通过调用QWidget的visibleRegion().isEmpty() 就可以确定出当前滑动过的区域。
2. 当点击左侧的导航时,右侧区域如何定位到对应的配置模块?
首先,需要绑定QListWidget的itemClicked事件,这样我们才能监控到点击事件;其次利用QScrollBar的setSliderPosition()方法设置滚动调到特定模块的位置。
3. 由于绑定了滚动条的valueChanged事件,又在itemClicked事件中设置了滚动条的问题,那么在设置位置的同时不也同样触发valueChanged事件吗?
需要一个变量来标记,本次valueChanged事件是由于setSliderPosition方法引起的。
关键代码段:
1. 绑定QScrollArea的valueChanged事件和QListWidget的itemClicked事件
void LHTSettingsBoard::SetupUi() { m_scroll = qFindChild<QScrollArea *>(m_wgtMain, "scrollArea"); m_widget_login = qFindChild<QWidget *>(m_wgtMain, "widget_login"); m_widget_register = qFindChild<QWidget *>(m_wgtMain, "widget_register"); m_widget_network = qFindChild<QWidget *>(m_wgtMain, "widget_network"); m_widget_password = qFindChild<QWidget *>(m_wgtMain, "widget_password"); m_listWidget = qFindChild<QListWidget *>(m_wgtMain, "left_navigation"); connect((const QObject*)m_scroll->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(valueChanged(int))); connect(m_listWidget, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(itemClicked(QListWidgetItem*))); QListWidgetItem *loginItem = m_listWidget->item(0); loginItem->setSelected(true); m_wgtMain->show(); }
2. 响应valueChanged事件的槽,完成移动滚动条时,当某个面板出现时触发左侧QListWidget中item的选中事件
void LHTSettingsBoard::valueChanged(int value) { QListWidgetItem *loginItem = m_listWidget->item(0); QListWidgetItem *registerItem = m_listWidget->item(1); QListWidgetItem *networkItem = m_listWidget->item(2); QListWidgetItem *passwordItem = m_listWidget->item(3); if (!m_sign) { if (!m_widget_login->visibleRegion().isEmpty()) { loginItem->setSelected(true); return; } else { loginItem->setSelected(false); } if (!m_widget_register->visibleRegion().isEmpty()) { registerItem->setSelected(true); return; } else { registerItem->setSelected(false); } if (!m_widget_network->visibleRegion().isEmpty()) { networkItem->setSelected(true); return ; } else { networkItem->setSelected(false); } if (!m_widget_password->visibleRegion().isEmpty()) { passwordItem->setSelected(true); return ; } else { passwordItem->setSelected(false); } } m_sign = false ; }
3. 响应itemClicked事件的槽,完成点击QListWidget中的item时,QScrollArea中的滚动条移动到相应配置项的位置
void LHTSettingsBoard::itemClicked(QListWidgetItem *item) { QString itemText = item->text(); qDebug() << itemText; QPoint pos ; m_sign = true ; if (itemText.compare("Login") == 0) { pos = m_widget_login->pos(); m_scroll->verticalScrollBar()->setSliderPosition(pos.y()); } else if (itemText.compare("Register") == 0) { pos = m_widget_register->pos(); m_scroll->verticalScrollBar()->setSliderPosition(pos.y()); } else if (itemText.compare("Network") == 0) { pos = m_widget_network->pos(); m_scroll->verticalScrollBar()->setSliderPosition(pos.y()); } else if (itemText.compare("ChangePassword") == 0) { pos = m_widget_password->pos(); m_scroll->verticalScrollBar()->setSliderPosition(pos.y()); } }总结:
通过以上就可以实现类似QQ系统设置面板的功能,开始我对这一块如何实现,使用什么控件完全不知道,共花了不到一天的事件查资料、试验才找到合适的方法。在这个过程中深刻地体会到解决问题最关键的地方在于思路,如果有了一个思路,哪怕别人告诉你应该朝着哪个方向走,后面的工作其实都是水到渠成很简单的了。慢慢享受这个过程,一个问题由完全不知道如何解决,到有思路,到真正解决。