使用Qt的項目視圖便利的子類通常比定義一個自己的模型更簡單,適合無需分離模型和視圖的操作。在第四章中我們使用了該技術,我們從QTableWidget和QTableWidgetItem派生子類來實現列表功能。
本節我們將展示如何使用便利的項目視圖子類來顯示項目。第一個例子展示了一個只讀的QListWidget,第二個例子展示了一個可編輯的QTableWidget,第三個例子展示了一個只讀的QtreeWidget.
我們首先來看一個簡單對話框,它允許用戶從一個列表中選取一個流程圖符號。每個項目由一個圖標、文本及唯一的ID號組成。
Figure 10.3. The Flowchart Symbol Picker application
首先來看一下頭文件的一段摘錄:
class FlowChartSymbolPicker : public QDialog
{
Q_OBJECT
public:
FlowChartSymbolPicker(const QMap<int, QString> &symbolMap,
QWidget *parent = 0);
int selectedId() const { return id; }
void done(int result);
...
};
當我們創建該對話框時,我們必須傳遞一個QMap<int, QString>,並且它執行後我們能調用selectedId()得到選擇的ID(或者如果用戶沒有選擇任何項目,返回-1)。
FlowChartSymbolPicker::FlowChartSymbolPicker(
const QMap<int, QString> &symbolMap, QWidget *parent)
: QDialog(parent)
{
id = -1;
listWidget = new QListWidget;
listWidget->setIconSize(QSize(60, 60));
QMapIterator<int, QString> i(symbolMap);
while (i.hasNext()) {
i.next();
QListWidgetItem *item = new QListWidgetItem(i.value(),
listWidget);
item->setIcon(iconForSymbol(i.value()));
item->setData(Qt::UserRole, i.key());
}
...
}
我們初始化id(最近選擇的ID)爲-1。接着我們創建了一個QListWidget對象,它是一個便利的項目視圖控件。我們對流程圖符號的map進行跌代遍歷每個項目且創建一個QListWidgetItem來表示每項。QListWidgetItem構造函數接收一個QString參數來表示要顯示的文本,後面接着是父親QListWidget。
接下來我們設置項目的圖標,接着在QListWidgetItem中我們調用setData()來保存我們任意的ID號。iconForSymbol()私有函數返回一個指定符號名的QIcon對象。
QListWidgetItem有多種角色,每種角色有一個相關的QVariant。最常用的角色是Qt::DisplayRole, Qt::EditRole, 及 Qt::IconRole,對於這些常用角色,有便利的設置及獲取函數(setText(), setIcon()),但還有一些其它的角色。我們也可以通過指定Qt::UserRole的一個數字值來定義自己的角色。在此例中,我們使用了Qt::UserRole來保存每個項目的ID。
構造函數的省略部分是關於創建按鈕、排列控件及設置窗體標題。
void FlowChartSymbolPicker::done(int result)
{
id = -1;
if (result == QDialog::Accepted) {
QListWidgetItem *item = listWidget->currentItem();
if (item)
id = item->data(Qt::UserRole).toInt();
}
QDialog::done(result);
}
done()函數是對QDialog的done()函數的重載。當用戶點擊OK或Cancel時調用該函數。如果點擊的OK,我們獲得相關項目並使用data()函數提取ID。如果我們對項目的文本感興趣,我們可以調用item->data(Qt::DisplayRole).toString() 或更方便地調用item->text()來獲得。
默認,QListWidget是隻讀的。如果我們想讓用戶編輯項目,可以使用QAbstractItemView::setEditTriggers()來設置視圖的編輯觸發器。例如,QAbstractItemView::AnyKeyPressed設置表示用戶僅通過開始輸入就能夠開始編輯一個項目。同樣,提供一個Edit按鈕(也許是Add 和 Delete按鈕)並把它們與槽連接以便我們能通過程序來處理編輯操作。
既然我們已經明白瞭如何使用一個便利的項目視圖類來瀏覽和選擇數據,我們將看一個能編輯數據的例子。我們再次使用一個對話框,這次對話框表示一組用戶能編輯的(x, y)座標。
Figure 10.4. The Coordinate Setter application
從構造函數開始,我們先看看項目視圖相關的代碼。
CoordinateSetter::CoordinateSetter(QList<QPointF> *coords,
QWidget *parent)
: QDialog(parent)
{
coordinates = coords;
tableWidget = new QTableWidget(0, 2);
tableWidget->setHorizontalHeaderLabels(
QStringList() << tr("X") << tr("Y"));
for (int row = 0; row < coordinates->count(); ++row) {
QPointF point = coordinates->at(row);
addRow();
tableWidget->item(row, 0)->setText(QString::number(point.x()));
tableWidget->item(row, 1)->setText(QString::number(point.y()));
}
...
}
QTableWidget構造函數接收要顯示的表的初始行列數。QTableWidget中的每一項用QTableWidgetItem表示,包含水平和垂直頭部項目。函數setHorizontalHeaderLabels()對每個水平的表控件項目將文本設置爲函數所傳遞的串列表中的文本。默認時,QTableWidget提供了一個帶有從1開始標號的行的垂直頭部,這正是我們所要的,因此我們不必手工設置垂直的頭標籤。
一旦我們創建並進入列標籤,我們遍歷所傳進的座標數據。對每個(x, y)對,我們創建與x和y座標相關的兩個QTableWidgetItems。使用QTableWidget::setItem()將這兩項加入到表中,該函數除了項外還接收一個行一個列。
默認,QTableWidget允許編輯。用戶通過瀏覽時按F2或只是錄入就可以編輯表中的任何單元格。視圖中用戶所做的改變會自動反應到QTableWidgetItem中。爲了阻止編輯,我們可以調用setEditTriggers(QAbstractItemView:: NoEditTriggers)。
void CoordinateSetter::addRow()
{
int row = tableWidget->rowCount();
tableWidget->insertRow(row);
QTableWidgetItem *item0 = new QTableWidgetItem;
item0->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
tableWidget->setItem(row, 0, item0);
QTableWidgetItem *item1 = new QTableWidgetItem;
item1->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
tableWidget->setItem(row, 1, item1);
tableWidget->setCurrentItem(item0);
}
當用戶點擊Add Row按鈕時會調用addRow()槽。我們使用insertRow()來追加一個新行。如果用戶想編輯該新行的一個單元格,QTableWidget會自動創建一個新的QTableWidgetItem。
void CoordinateSetter::done(int result)
{
if (result == QDialog::Accepted) {
coordinates->clear();
for (int row = 0; row < tableWidget->rowCount(); ++row) {
double x = tableWidget->item(row, 0)->text().toDouble();
double y = tableWidget->item(row, 1)->text().toDouble();
coordinates->append(QPointF(x, y));
}
}
QDialog::done(result);
}
最後,當用戶點擊OK時,我們清除傳入對話框中的所有座標,然後根據QTableWidget的項中的座標創建一個新的座標集。
QtreeWidget默認情況是隻讀的。
Figure 10.5. The Settings Viewer application
SettingsViewer::SettingsViewer(QWidget *parent)
: QDialog(parent)
{
organization = "Trolltech";
application = "Designer";
treeWidget = new QTreeWidget;
treeWidget->setColumnCount(2);
treeWidget->setHeaderLabels(
QStringList() << tr("Key") << tr("Value"));
treeWidget->header()->setResizeMode(0, QHeaderView::Stretch);
treeWidget->header()->setResizeMode(1, QHeaderView::Stretch);
...
setWindowTitle(tr("Settings Viewer"));
readSettings();
}
爲了訪問應用程序的設置,必須創建一個帶有組織的名字及程序的名字作爲參數的QSettings對象。我們設置默認的名字("Designer" by "Trolltech")然後創建一個新的QtreeWidget。最後調用readSettings()函數。
void SettingsViewer::readSettings()
{
QSettings settings(organization, application);
treeWidget->clear();
addChildSettings(settings, 0, "");
treeWidget->sortByColumn(0);
treeWidget->setFocus();
setWindowTitle(tr("Settings Viewer - %1 by %2")
.arg(application).arg(organization));
}
應用程序的設置被保存在一個層次結構的鍵值裏。私有函數addChildSettings()接收一個settings對象,一個父親QtreeWidgetItem和當前"group"。組是一個等同於文件系統目錄的QSettings。函數addChildSettings()遞歸調用自己來遍歷一個隨機樹結構。從readSettings()函數最初的調用傳遞0作爲父項目表示根。
void SettingsViewer::addChildSettings(QSettings &settings,
QTreeWidgetItem *parent, const QString &group)
{
QTreeWidgetItem *item;
settings.beginGroup(group);
foreach (QString key, settings.childKeys()) {
if (parent) {
item = new QTreeWidgetItem(parent);
} else {
item = new QTreeWidgetItem(treeWidget);
}
item->setText(0, key);
item->setText(1, settings.value(key).toString());
}
foreach (QString group, settings.childGroups()) {
if (parent) {
item = new QTreeWidgetItem(parent);
} else {
item = new QTreeWidgetItem(treeWidget);
}
item->setText(0, group);
addChildSettings(settings, item, group);
}
settings.endGroup();
}
addChildSettings()函數用於創建所有的QtreeWidgetItem。它遍歷在settings結構中當前級別的所有的鍵,並且爲每個鍵創建一個QTableWidgetItem。如果傳遞0作爲父項目,我們就創建該項目作爲QtreeWidget自己的孩(使其成爲頂級項);否則我們就創建該項目作爲它父親的孩。第一列被設爲鍵名,第二列設爲對應的值。
接着,此函數遍歷當前級別的每個組。對每個組,創建一個新的QtreeWidgetItem,第一列設爲組名。接着該函數使用組項作爲父親遞歸調用自己來創建帶組的孩項的QTReeWidget。
這節所說的項目視圖控件允許我們使用一種類似於早期版本所用的編程風格:把整個數據集讀進一個項目視圖控件,使用項目對象來表示數據元素,(如果項目是可編輯的)然後寫回數據源。在後面的小節中,我們將充分利用Qt的模型/視圖架構。