Qt学习笔记(六)项视图类

许多应用程序允许用户搜索、查看和编辑属于某个数据集中的一些个别项。这些数据可能保存在文件中、数据库中或者网络服务器上。处理像这样的数据集的标准方式是使用Qt的项视图类(item view class)

Smalltalk语言普及了一种非常灵活的对于大数据集的可视化方法:模型-视图-控制器(Model-View-Contorller, MVC)。在MVC方法中,Model代表数据集,它对需要查看数据的获取以及任何存储负责,每种类型的数据集都有自己的模型,但不管底层的数据集是什么样,模型提供给视图view的API是相同的。视图代表的是面向用户的那些数据。在同一时间,任何大数据集只有有限的部分是可见的,所以这个有限的部分就是视图所请求的那部分数据,控制器controller是用户和视图之间的媒介,它把用户的操作转化为浏览或者编辑数据的请求,这部分数据是根据需要由视图传送给模型的数据。

借鉴MVC的方法,Qt也提供了一种模型/视图架构,模型和视图之间使用委托联系。委托对于项的如何显示和如何编辑提供精细控制。Qt对每种类型的视图都提供了默认的委托。 故我们通常不需要注意它。通过把一个模型注册到两个或者多个视图,就可以让用户使用不同的方式查看数据以及和数据交互。Qt对于多个视图会自动的保持同步,从而使对一个视图的改变会影响到全部视图。模型/视图架构的另一个好处是:如果决定改变底层数据集的存储方式,则只需要修改模型,而视图仍然能够继续正常工作。

在很多情况下,只需要把一小部分数据显示给用户,可以使用Qt提供的那些方便的项视图类(QListWidget, QTableWidget, QTreeWidget),并且可以把它们和项直接组装起来。它们把数据存在项中(如QTableWidget中包含了一些QTableWidgetItem)。实际上在这些方便的类的内部,使用了自定义的模型,就可以让这些项在视图中变得可见。

下面示例中,使用QListWidget每个项都由一个图标、一段文本和一个唯一的ID组成。

#ifndef FLOWCHARTSYMBOLPICKER_H
#define FLOWCHARTSYMBOLPICKER_H
 
  
#include 
#include 
 
  
class QDialogButtonBox;
class QIcon;
class QListWidget;
 
  
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);
 
  
private:
    QIcon iconForSymbol(const QString &symbolName);
 
  
    QListWidget *listWidget;
    QDialogButtonBox *buttonBox;
    int id;
};
 
  
#endif
构造对话框时必须传递给它一个QMap,在它运行之后,可以通过selectedId()获得一个选中的ID(或者如果用户没有选中任何一项,返回-1)

#include 
 
  
#include "flowchartsymbolpicker.h"
 
  
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());	//调用setData()函数,并且把任意ID保存到QListWidget中。iconForSymbol()私有函数给每个给定的符号名称返回一个ICon。
QListWidgetItem有几个角色(role),每个role都以一个关联的QVariant。最常用的role是Qt::DisplayRole, Qt::EditRole, Qt::IconRole,并且这些角色都有方便的设置和获取hanshu[setText(). setIcon()].通过指定一个大于等于Qt::UserRole的值,就可以定义自定义的角色,在上例子中使用Qt::UserRole存储每个项的ID。
 
  
    }
 
  
    buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
                                     | QDialogButtonBox::Cancel);
 
  
    connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
    connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
 
  
    QVBoxLayout *mainLayout = new QVBoxLayout;
    mainLayout->addWidget(listWidget);
    mainLayout->addWidget(buttonBox);
    setLayout(mainLayout);
 
  
    setWindowTitle(tr("Flowchart Symbol Picker"));
}
 
  
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重新实现,当用户单击OK或者Cancel时,就会调用它。如果用户单击OK,就会result == QDialog::Accepted,从而获得相应的项并且使用data()函数提取ID。如果对项的文本感兴趣,则可通过调用item->data(Qt::DisplayRole).toString()或者更为方便的item->text()来提取文本。
 
  
QIcon FlowChartSymbolPicker::iconForSymbol(const QString &symbolName)
{
    QString fileName = ":/images/" + symbolName.toLower();
    fileName.replace(' ', '-');
    return QIcon(fileName);
}
 
  

默认情况下,QListWidget是只读的,如果想让用户编辑这些项,则可以使用QAbstractItemView::setEditTriggers()设置这个视图的编辑触发器。例如,QAbstractItemView::AnyKeyPressed这个设置值的意思是:用户只要一开始输入就进入项的编辑状态。

item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsEditable);

setEditTriggers(QAbstractItemView::AnyKeyPressed)

下例实现一个可以编辑数据的QTableWidget。

    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会提供一个垂直表头,这个列的标签从1开始,所以不需要手工设置垂直表头的标签。 
  

默认情况下,QTableWidget允许进行编辑。用户在这个视图中所做的任何修改都会自动影响这些QTableWidgetItem,为了防止编辑,可以setEidtTriggers(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按钮的时候就会调用addRow()槽,这种方式在构造函数中也常用到。使用QTableWidget::insertRow()来插入一个新的行,然后创建两个QTableWidgetItem()项,并利用QTableWidget::insertRow()把它们添加到表中。除了该项,QTableWidget::setItem()还需要一行及一列。 
  

QTreeWidget的例子。QTreeWidget默认是只读的。

class SettingsViewer : public QDialog
{
    Q_OBJECT
 
  
public:
    SettingsViewer(QWidget *parent = 0);
 
  
private slots:
    void open();
 
  
private:
    void readSettings();
    void addChildSettings(QSettings &settings, QTreeWidgetItem *item,
                          const QString &group);
 
  
    QTreeWidget *treeWidget;
    QDialogButtonBox *buttonBox;
 
  
    QString organization;
    QString 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);//设置两列的重定义模式为stretch
    treeWidget->header()->setResizeMode(1, QHeaderView::Stretch);
 
  
	...........
	.........
 
  
    setWindowTitle(tr("Settings Viewer"));
    readSettings();
}
在构造函数最后,读取Settings

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));
}
 
  

使用预定义模型

QT提供了几种可以在视图类中使用的预定义模型:

QStringListModel //存储一个字符串列表 ; QStandardItemModel // 存储任意分层次的数据

QDirModel //封装本地文件系统; QSqlQueryModel //封装一个SQL结果集

QSqlTableModel //封装一个SQL表; QSqlRelationalTableModel //利用外键封装一个SQL表

QSortFilterProxyModel // 排序或筛选另一个模型

实例:利用QStringListModel模型和QListView来实现列表显示。

class TeamLeadersDialog : public QDialog
{
    Q_OBJECT
 
  
public:
    TeamLeadersDialog(const QStringList &leaders, QWidget *parent = 0);
 
  
    QStringList leaders() const;
 
  
private slots:
    void insert();
    void del();
 
  
private:
    QListView *listView;
    QDialogButtonBox *buttonBox;
    QStringListModel *model;
};

TeamLeadersDialog::TeamLeadersDialog(const QStringList &leaders,
                                     QWidget *parent)
    : QDialog(parent)
{
    model = new QStringListModel(this);	//创建 QListStringModel模型
    model->setStringList(leaders);	//设置数据集
 
  
    listView = new QListView;
    listView->setModel(model);		//QListView 设置模型
    listView->setEditTriggers(QAbstractItemView::AnyKeyPressed
                              | QAbstractItemView::DoubleClicked);
	................
 
  
 }
实例:利用QDirModel模型和QTreeView控件来实现文件列表

class DirectoryViewer : public QDialog
{
    Q_OBJECT
 
  
public:
    DirectoryViewer(QWidget *parent = 0);
 
  
private slots:
    void createDirectory();
    void remove();
 
  
private:
    QTreeView *treeView;
    QDirModel *model;
    QDialogButtonBox *buttonBox;
};
 
  

DirectoryViewer::DirectoryViewer(QWidget *parent)
    : QDialog(parent)
{
    model = new QDirModel;
    model->setReadOnly(false);
    model->setSorting(QDir::DirsFirst | QDir::IgnoreCase | QDir::Name);
 
  
    treeView = new QTreeView;
    treeView->setModel(model);
    treeView->header()->setStretchLastSection(true);
    treeView->header()->setSortIndicator(0, Qt::AscendingOrder);
    treeView->header()->setSortIndicatorShown(true);
    treeView->header()->setClickable(true);
 
  
    QModelIndex index = model->index(QDir::currentPath());
    treeView->expand(index);
    treeView->scrollTo(index);
    treeView->resizeColumnToContents(0);
 
  
	..........................
}

void DirectoryViewer::createDirectory()
{
    QModelIndex index = treeView->currentIndex();
    if (!index.isValid())
        return;
 
  
    QString dirName = QInputDialog::getText(this,
                              tr("Create Directory"),
                              tr("Directory name"));
    if (!dirName.isEmpty()) {
        if (!model->mkdir(index, dirName).isValid())
            QMessageBox::information(this, tr("Create Directory"),
                    tr("Failed to create the directory"));
    }
}

void DirectoryViewer::remove()
{
    QModelIndex index = treeView->currentIndex();
    if (!index.isValid())
        return;
 
  
    bool ok;
    if (model->fileInfo(index).isDir()) {
        ok = model->rmdir(index);
    } else {
        ok = model->remove(index);
    }
    if (!ok)
        QMessageBox::information(this, tr("Remove"),
                tr("Failed to remove %1").arg(model->fileName(index)));
}

实现自定义模型

Qt 默认的数据模型(model)提供一系列方便的方法来处理和观察数据。但是这些默认的数据模型还不足以有效地处理某些比较特殊的数据源(data sources)。对于这些特殊的情况,根据底层数据建立自定义的数据模型是非常有必要的。

 

在开始创建自定义的数据模型之前,首先回顾在Qt model/view 架构中的关键性的概念。每个数据模型中的数据单元(data element)有一个模型索引(model index)和一系列的属性(attributes/roles)。其中,数据单元的属性可以具有任意的数据类型。其中较为常见的属性包括:Qt::DisplayRole 和 Qt::EditRole;其它的属性用于辅助类(supplementary)数据,包括:Qt::ToolTipRole,Qt::StatusTipRole 和 Qt::WhatsThisRole;还有一些用于控制基本的显示属性:Qt::FontRole,Qt::TextAlignmentRole,Qt::TextColorRole 和 Qt::BackgroundColorRole 等等。

 

对于一个列表数据模型(list model),其模型索引是行号(row number),这个索引值可以由 QModelIndex::row( ) 得到,有效的索引是非负的;对于一个表格模型(table model),其模型索引是行号(row number)和列号(column number)两个,可以由 QModelIndex::row( ) 和 QModelIndex::column( ) 得到。不论是列表数据模型还是表格数据模型,它们的数据单元的父类都是根(root)。根由一个非法的 QModelIndex 索引。下面是 Qt 中的数据模型的示意图:

 

 

第一个例子介绍一个只读性的表格数据模型。这张表格可以显示对应的汇率值,程序效果如下:

 

 

这个程序使用一个简单的表格控件(QTableWidget)就可以实现,但是我们希望使用自定义的数据模型,以便利用好数据之间的某些特点减小数据内存使用量。正常情况下下,如果我们需要保存表格中目前正在交易的162个汇率值,那么需要保存 162X162=26244 个值,然而使用自定义的数据模型 CurrencyModel ,我们只需要保存 162 个数值即可(每种汇率相对美元的值)。

 

类 CurrencyModel 当与 QTableView 类搭配使用,模型中的数据由 QMap 这个数据结构保存;Map 中的每个 key 值是一种汇率的代号,而每个 value 则是用美元表达的汇率值。下面的代码片段说明 Map 如何组织数据,以及模型的使用方法。

 

首先在 main 函数中,创建一个 QMap 作为数据源。

 

QMap currencyMap;
currencyMap.insert("AUD", 1.3259);
currencyMap.insert("CHF", 1.2970);
...
currencyMap.insert("SGD", 1.6901);
currencyMap.insert("USD", 1.0000);

CurrencyModel currencyModel;
currencyModel.setCurrencyMap(currencyMap);

QTableView tableView;
tableView.setModel(¤cyModel);
tableView.setAlternatingRowColors(true);

 

现在看一下数据模型的实现,首先是它的头文件:

 

class CurrencyModel : public QAbstractTableModel
{
public:
CurrencyModel(QObject *parent = 0);

void setCurrencyMap(const QMap &map);
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;


private:
QString currencyAt(int offset) const;
QMap currencyMap;
};

 

由于 QAbstractTableModel 最接近我们的数据源,所以选择由这个类的派生类作为数据模型。Qt 提供了许多的模型基类,包括:QAbstractListModel,QAbstractTableModel 和 QAbstractItemModel 等等。在处理一维,二维数据集合时,前两种数据模型可以提供方便,下面是数据模型的类继承图:

 

 

作为只读型的表格数据模型,必须重载三个函数:rowCount( ),columnCount( ) 和 data( ) 。在这个例子中,我们也重载了 headerData( ) 函数,并提供了一个函数用于初始化数据:setCurrencyMap( ) 。这些函数重载好后,将数据模型给入 view ,则 view 会自动地加载模型中的数据并将数据显示出来。

 

CurrencyModel::CurrencyModel(QObject *parent)
: QAbstractTableModel(parent)
{
}

 

构造函数中无需多事,仅仅传递一个 parent 指针给父类。

 

int CurrencyModel::rowCount(const QModelIndex & /* parent */) const
{
return currencyMap.count();
}


int CurrencyModel::columnCount(const QModelIndex & /* parent */) const
{
return currencyMap.count();
}

 

表格的行数或者列数,就是 map 中的汇率数目。

 

QVariant CurrencyModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();

if (role == Qt::TextAlignmentRole) {
return int(Qt::AlignRight | Qt::AlignVCenter);
} else if (role == Qt::DisplayRole) {
QString rowCurrency = currencyAt(index.row());
QString columnCurrency = currencyAt(index.column());

if (currencyMap.value(rowCurrency) == 0.0)
return "####";

double amount = currencyMap.value(columnCurrency)
/ currencyMap.value(rowCurrency);

return QString("%1").arg(amount, 0, 'f', 4);
}
return QVariant();
}

 

data( ) 函数返回某个数据单元的众多属性中的某一个属性的值。数据单元由 QModelIndex 索引。对于表格数据模型,QModelIndex 的有用的部分是数据单元的行号与列号,这些值可以由 row( ) 和 column( ) 取得。特别这里的 arg 用法是:

 

QString QString::arg ( double a, int fieldWidth = 0, char format = 'g', int precision = -1, const QChar & fillChar = QLatin1Char( ' ' ) ) const

 

首先,a 是要显示的数字;fieldWidth 指明了a 填入的最小空间,同时空余空间将由 fillChar 填充;format 是数字的显示样式,如“E” 是以科学计数法显示,“f” 是以实数方式显式等等;precision 则是指保留小数点后数字的位数。

 

如果是 Qt::TextAlignmentRole,将返回一个适合数据单元的布局(Alignment);如果属性是 Qt::DisplayRole,那么查找每个汇率的值并计算其转换率。

 

我们也可以将返回值类型写成 double,但是后面我们就没有办法决定数字需要显示的小数点后数字的位数(除非使用自定义的 delegate)。所以,这里我们返回一个字符串,以便控制其显示。

 

QVariant CurrencyModel::headerData(int section,
                                   Qt::Orientation /* orientation */,
                                   int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();
    return currencyAt(section);
}

 

headerData( ) 这个函数由 view 调用,以为它的水平和垂直表头赋值。这里的 section 参数是列号或者行号(取决于方向)。因为行和列有相同的汇率代号,所以这里不考虑方向(Orientation),而直接返回给定 section 号对应的汇率代号。

 

void CurrencyModel::setCurrencyMap(const QMap &map)
{
    currencyMap = map;
    reset();
}

 

调用者可以通过 setCurrencyMap( ) 来改变汇率 map 的对象,QAbstractItemModel::reset( ) 函数将通知所有正在使用模型的 views,模型中的数据单元已失效,views 必须更新显示出来的数据单元。

 

QString CurrencyModel::currencyAt(int offset) const
{
    return (currencyMap.begin() + offset).key();
}

currencyAt( ) 函数返回汇率 map 中给定偏移量位置处的 key 值,也就是汇率代号。这里使用了 STL 型的遍历器遍历 map 查找数据单元,并调用这个数据单元的 key( ) 。其中,QMap::begin( ) 返回一个指向 map 中第一个数据单元的 STL 型的遍历器。

 

自定义委托

我们知道,在经典的 MVC 模型中,view用于向用户展示 model 的数据。但是,Qt提供的不是 MVC 三层架构,而是一个 model/view 设计。这种设计并没有包含一个完整而独立的组件用于管理用户的交互。一般来说,view仅仅是用作对model数据的展示和对用户输入的处理,而不应该去做其他的工作。在这种结构中,为了获得对用户输入控制的灵活性,这种交互工作交给了delegate,也就是“委托”,去完成。简单来说,就像它们的名字一样,view 将用户输入委托给 delegate 处理,而自己不去处理这种输入。这些组件提供一种输入能力,并且能够在某些 view 中提供这种交互情形下的渲染,比如在 table 中通过双击单元格即可编辑内容等。对这种控制委托的标准接口被定义在 QAbstractItemDelegate 类中。
委托用来渲染和编辑视图中的不同的项。在大多数情况下,视图中默认的委托已经足够了。如果想要更好的控制有关项的显示,通常可以通过使用自定义模型很简单的实现我们想要的。在data()重新实现中,我们可以处理Qt::FontRole, Qt::TextAlignmentRole, Qt::TextColorRole和Qt::BackgroundColorRole,并且他们会被默认的委托使用。
如果想要更多的控制,就可以创建自己的委托类并且把它设置成我们想要使用它的视图中。 
delegate 可以用于渲染内容,这是通过 paint() 和 sizeHint() 函数来完成的。但是,对于一些简单的基于组件的delegate,可以通过继承 QItemDelegate 或者 QStyledItemDelegate 来实现。这样就可以避免要完全重写 QAbstractItemDelegate 中所需要的所有函数。对于一些相对比较通用的函数,在这两个类中已经有了一个默认的实现。
 
Qt提供的标准组件使用 QItemDelegate 提供编辑功能的支持。这种默认的实现被用在 QListView,QTableView 和 QTreeView 之中。view 实用的delegate可以通过 itemDelegate() 函数获得。setItemDelegate() 函数则可以为一个标准组件设置自定义的 delegate。
 
Qt 4.4版本之后提供了两个可以被继承的delegate类:QItemDelegate 和 QStyledItemDelegate。默认的delegate是 QStyledItemDelegate。这两个类可以被相互替代,用于给view 组件提供绘制和编辑的功能。它们之间的主要区别在于,QStyledItemDelegate 使用当前的风格(style)去绘制组件。所以,在自定义delegate或者需要使用 Qt style sheets 时,建议使用 QStyledItemDelegate 作为父类。使用这两个类的代码通常是一样的,除了需要使用style进行绘制的部份。如果你希望为view item自定义绘制函数,最好实现一个自定义的style。这个你可以通过QStyle类来实现。
 
如果delegate没有支持为你的数据类型进行绘制,或者你希望自己绘制item,那么就可以继承 QStyledItemDelegate 类,并且重写 paint() 或者还需要重写 sizeHint() 函数。paint() 函数会被每一个item独立调用,而sizeHint()函数则可以定义每一个item 的大小。在重写 paint() 函数的时候,通常需要用 if 语句找到你需要进行渲染的数据类型并进行绘制,其他的数据类型需要调用父类的实现进行绘制。
 
一个自定义的delegate也可以直接提供一个编辑器,而不是使用内置的编辑器工厂(editor item factory)。如果你需要这种功能,那么需要实现一下几个函数:
  • createEditor(): 返回修改数据的组件;
  • setEditorData(): 为editor提供编辑的原始数据;
  • updateEditorGeometry(): 保证editor显示在 item view 的合适位置以及大小;
  • setModelData(): 根据editor 的数据更新model的数据。
好了,这就是一个自定义delegate的实现了。下面来看一个例子。
 
这是一个歌曲及其时间的例子。使用的是QTableWidget,一共有两列,第一列是歌曲名字,第二列是歌曲持续的时间。为了表示这个数据,我们建立一个Track类:
 
track.h
#ifndef TRACK_H 
#define TRACK_H 
 
#include  
 
class Track 

public
        Track( const QString &title = "",  int duration = 0); 
 
        QString title; 
         int duration; 
}; 
 
#endif  // TRACK_H
 
track.cpp
#include  "track.h" 
 
Track::Track( const QString &title,  int duration) 
        : title(title), duration(duration) 

}
 
这个类的构造函数没有做任何操作,只是把title和duration这两个参数通过构造函数初始化列表赋值给内部的成员变量。注意,现在这两个成员变量都是public的,在正式的程序中应该声明为private的才对。然后来看TrackDelegate类:
 
trackdelegate.h
#ifndef TRACKDELEGATE_H 
#define TRACKDELEGATE_H 
 
#include  
 
class TrackDelegate :  public QStyledItemDelegate 

        Q_OBJECT 
 
public
        TrackDelegate( int durationColumn, QObject *parent = 0); 
 
         void paint(QPainter *painter,  const QStyleOptionViewItem &option,  const QModelIndex &index)  const
        QWidget *createEditor(QWidget *parent,  const QStyleOptionViewItem &option,  const QModelIndex &index)  const
         void setEditorData(QWidget *editor,  const QModelIndex &index)  const
         void setModelData(QWidget *editor, QAbstractItemModel *model,  const QModelIndex &index)  const
 
private slots: 
         void commitAndCloseEditor(); 
 
private
         int durationColumn; 
}; 
 
 
#endif  // TRACKDELEGATE_H 
 
trackdelegate.cpp
#include  "trackdelegate.h" 
 
TrackDelegate::TrackDelegate( int durationColumn, QObject *parent) 
        : QStyledItemDelegate(parent) 

         this->durationColumn = durationColumn; 

 
void TrackDelegate::paint(QPainter *painter,  const QStyleOptionViewItem &option,  const QModelIndex &index)  const 

         if (index.column() == durationColumn) { 
                 int secs = index.model()->data(index, Qt::DisplayRole).toInt(); 
                QString text = QString( "%1:%2").arg(secs / 60, 2, 10, QChar('0')).arg(secs % 60, 2, 10, QChar('0')); 
                QTextOption o(Qt::AlignRight | Qt::AlignVCenter); 
                painter->drawText(option.rect, text, o); 
        }  else { 
                QStyledItemDelegate::paint(painter, option, index); 
        } 

 
QWidget *TrackDelegate::createEditor(QWidget *parent,  const QStyleOptionViewItem &option,  const QModelIndex &index)  const 

         if (index.column() == durationColumn) { 
                QTimeEdit *timeEdit =  new QTimeEdit(parent); 
                timeEdit->setDisplayFormat( "mm:ss"); 
                connect(timeEdit, SIGNAL(editingFinished()),  this, SLOT(commitAndCloseEditor())); 
                 return timeEdit; 
        }  else { 
                 return QStyledItemDelegate::createEditor(parent, option, index); 
        } 

 
void TrackDelegate::commitAndCloseEditor() 

        QTimeEdit *editor = qobject_cast(sender()); 
        emit commitData(editor); 
        emit closeEditor(editor); 

 
void TrackDelegate::setEditorData(QWidget *editor,  const QModelIndex &index)  const 

         if (index.column() == durationColumn) { 
                 int secs = index.model()->data(index, Qt::DisplayRole).toInt(); 
                QTimeEdit *timeEdit = qobject_cast(editor); 
                timeEdit->setTime(QTime(0, secs / 60, secs % 60)); 
        }  else { 
                QStyledItemDelegate::setEditorData(editor, index); 
        } 

 
void TrackDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,  const QModelIndex &index)  const 

         if (index.column() == durationColumn) { 
                QTimeEdit *timeEdit = qobject_cast(editor); 
                QTime time = timeEdit->time(); 
                 int secs = (time.minute() * 60) + time.second(); 
                model->setData(index, secs); 
        }  else { 
                QStyledItemDelegate::setModelData(editor, model, index); 
        } 

 
正如前面所说的,这个类继承了QStyledItemDelegate,覆盖了其中的四个函数。通过前面的讲解,我们已经了解到这些函数的作用。至于实现,我们前面也说过,需要通过QModelIndex选择我们需要进行渲染的列,然后剩下的数据类型仍然需要显式地调用父类的相应函数。由于我们在Track里面存储的是歌曲的秒数,所以在paint()里面需要用除法计算出分钟数,用%60计算秒数。其他的函数都比较清楚,请注意代码。
 
最后写一个使用的类:
 
trackeditor.h
#ifndef TRACKEDITOR_H 
#define TRACKEDITOR_H 
 
#include  
#include  "track.h" 
 
class TrackEditor :  public QDialog 

        Q_OBJECT 
 
public
        TrackEditor(QList *tracks, QWidget *parent); 
 
private
        QList *tracks; 
        QTableWidget *tableWidget; 
}; 
 
#endif  // TRACKEDITOR_H
 
trackeditor.cpp
#include  "trackeditor.h" 
#include  "trackdelegate.h" 
 
TrackEditor::TrackEditor(QList *tracks, QWidget *parent) 
        : QDialog(parent) 

         this->tracks = tracks; 
 
        tableWidget =  new QTableWidget(tracks->count(), 2); 
        tableWidget->setItemDelegate( new TrackDelegate(1)); 
        tableWidget->setHorizontalHeaderLabels(QStringList() << tr( "Track") << tr( "Duration")); 
 
         for ( int row = 0; row < tracks->count(); ++row) { 
                Track track = tracks->at(row); 
 
                QTableWidgetItem *item0 =  new QTableWidgetItem(track.title); 
                tableWidget->setItem(row, 0, item0); 
 
                QTableWidgetItem *item1 =  new QTableWidgetItem(QString::number(track.duration)); 
                item1->setTextAlignment(Qt::AlignRight); 
                tableWidget->setItem(row, 1, item1); 
        } 
 
        QVBoxLayout *mainLayout =  new QVBoxLayout; 
        mainLayout->addWidget(tableWidget); 
         this->setLayout(mainLayout); 

 
其实也并没有很大的不同,只是我们使用setItemDelegate()函数设置了一下delegate。然后写main()函数:
 
#include  
#include  "trackeditor.h" 
 
int main( int argc,  char *argv[]) 

        QApplication a(argc, argv); 
        QList tracks; 
        Track t1( "Song 1", 200); 
        Track t2( "Song 2", 150); 
        Track t3( "Song 3", 120); 
        Track t4( "Song 4", 210); 
        tracks << t1 << t2 << t3 << t4; 
        TrackEditor te(&tracks, NULL); 
        te.show(); 
         return a.exec(); 

 
好了,运行一下看看效果吧!
 



你可能感兴趣的:(Qt)