業務需求:
基於網盤客戶端的實現,原有網盤的設置面板無論從界面顯示還是從業務需求都不能滿足我們的正常需求。當前的要求是,模擬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系統設置面板的功能,開始我對這一塊如何實現,使用什麼控件完全不知道,共花了不到一天的事件查資料、試驗才找到合適的方法。在這個過程中深刻地體會到解決問題最關鍵的地方在於思路,如果有了一個思路,哪怕別人告訴你應該朝着哪個方向走,後面的工作其實都是水到渠成很簡單的了。慢慢享受這個過程,一個問題由完全不知道如何解決,到有思路,到真正解決。