Namespaces

Variants
Actions

Please note that as of October 24, 2014, the Nokia Developer Wiki will no longer be accepting user contributions, including new entries, edits and comments, as we begin transitioning to our new home, in the Windows Phone Development Wiki. We plan to move over the majority of the existing entries over the next few weeks. Thanks for all your past and future contributions.

(Redirected from 如何自定义 Listview)

如何使用Model View delegate自定义列表

From Wiki
Jump to: navigation, search
Article Metadata

代码示例
兼容于
平台:
Symbian

文章
flycarl 在 01 Sep 2010 创建
最后由 hamishwillee 在 11 Oct 2012 编辑
  • 适用平台:S60 3rd Edition FP1, S60 3rd Edition FP2, S60 5th Edition
  • 运行设备: Qt Creator IDE & S60 Emulator
  • 关键字:Model/View Programming, delegate, model, custom listview item, Item Roles


Contents

简介

这是一个演示Model/View Programming 中自定义model,自定义delegate用法的程序。 通过自定义的model,delegate,实现自定义的列表元素。目标是构造一个列表,其中每个列表元素包含若干图片,文字, 按钮等。 要实现这样的功能,第一反应是 自己定义一个widget,把图片文字控件放在里面加上layout, 然后再用使用void QAbstractItemView::setIndexWidget,加LlistView或TableView里面。 当界面元素固定,少量的时候,这是首选, 但是要看到setIndexWidget帮助文档里面的警告,这样做是有效率代价的,如果列表有100项,那么就要加入100个widget, 很大的消耗。 使用delegate的话你可以只是paint 每个元素, 在需要操作的时候才构建真正的控件, 而画控件可以用QStyle::drawControl() 画出来,当然这样做你需要构造,自己的model,delegate, 自己做数据绑定。 这样做效率很高,因为只有当前的控件是真的,其他都是画上去的! 好了动机大概是这样,下面详细介绍如何做。

开发环境

开始

  • 关于Model/View Programming的使用,最好的文档当然是Qt自己的帮助,建议先通读一遍, 同时参考Qt中的例子:
  • StringListModel :位于doc\src\snippets\stringlistmodel, 这个例子的代码都在帮助的 Creating New Models这一章,建议阅读的时候顺手做一下,而我下面的例子是在这个例子基础上改成的。
  • SpinBoxDelegate:这个例子用以演示 如何自定义delegate, 来实现自定义的表元素,每个列表元是个spinbox, 可以编辑。这个例子解释了自定义delegate 的方方面面,但是用的model 是框架自带的
  • PixelDelegate: 这是个很漂亮的例子,演示了model view delegate的强大威力,其model 和delegate的自定义程度很高,演示了model和delegate之间的数据交互。遗憾的是这是个只读的例子,就是说并不能通过model改变data。
  • StarDelegate: 这个例子演示了如何定制delegate, 使用了自定义的数据结构,和编辑器,效果很强大,我们的自定义控件也期望这个目标。遗憾是没有定制model。
  • 我们期望自己的StarDelegatedelegate能画上图片等元素 如StarDelegate,PixelDelegate, 又能支持一些系统控件(button,checkbox...)如SpinBoxDelegate,但是上面三个强大的例子都没能完全达到这个目标,所以才写这篇文章。
  • 下面开始新建一个QT工程,选Mobile QT Application, 在ui中拖入一个ListView。

自定义model

为了说明问题,这里用最简单的数据结构,一个QStringList, model 是 数据的直接接口,有了String, 我们可以显示文字,也可以作为路径读取图片,在这个小例子中足够了。 按照帮助中 Creating New Models的顺序,首先我们做一个只读的model, 在mainWindow.cpp中 对listview 加载model:

    parserModel *model = new parserModel(strings, this);
ui->listView->setModel(model);

只读的model

只读的model 只需要实现rowCount ,data 两个方法, 这里为ListView服务,所以我们继承QAbstractListModel, 如果用TableView的话,可以像PixelDelegate那样使用QAbstractTableModel。

class parserModel : public QAbstractListModel
{
Q_OBJECT
public:
parserModel(const QStringList &strings, QObject *parent = 0);
 
//basic function for a read-only model
int rowCount ( const QModelIndex & parent = QModelIndex() ) const;
QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const ;
 
private:
//simple data source just a QStringList, if need can add other list of QString, int, bool, struct or class
QStringList stringList;
};

想要 rowCount 能返回string list 中string的个数,所以我们这样写:

int parserModel::rowCount(const QModelIndex &parent) const
{
return stringList.count();
}

接下来关键的 data 函数,这个函数是 View 或 Delegate 获取数据的主要接口,我们想要显示文字,图片,都是从model中直接获得,我们这样写:

QVariant parserModel::data(const QModelIndex &index, int role) const
{
if ( (!index.isValid()) || (index.row() >= stringList.size()))
return QVariant();
 
if(role == Qt::UserRole)
{
return stringList.at(index.row());
}
else if( role == Qt::UserRole+1)
{
QPixmap pixmap;
pixmap.load(stringList.at(index.row()));
return pixmap.scaled(80, 80,Qt::KeepAspectRatioByExpanding, Qt::FastTransformation);
}
else if( role == Qt::UserRole+2)
{
QPixmap pixmap;
pixmap.load(stringList.at(index.row()));
return pixmap.scaled(20, 20,Qt::KeepAspectRatio, Qt::FastTransformation);
}
 
return QVariant();
}

这里默认是返回QVariant(), 关键是看中间返回的东西,必须结合后面的Delegate实现来一起看。 当delegate 或 view 询问数据的时候会传给data方法两个参数, 就是问model要 位于 index这个地方的具有role数据,enum Qt::ItemDataRole 中常见的role是

  • Qt::DisplayRole 返回 QString 文字
  • Qt::DecorationRole 返回 QPixmap 图片

但是如果你想要返回另一幅图的话,就要用其他的role了,所以这里我全用 Qt::UserRole,来解释这个问题。 一个解释Qt::ItemDataRole 的例子是 Color Editor Factory,在这个例子中列表显示颜色和颜色名, 都是从颜色名字符串中读数据,但是显示的时候一个是颜色方块,和名字,这就是同一个数据的两种表现形式,两个role。 更进一步的,你可以在model中再引用一个struct 或者class Data, 当被询问不同的role的时候返回 Data.str1,或 Data.int2, 这样同一个index可以返回不同的数据,不同的数据类型。也可以是SQL查询的结果,在选择语句中传入不同的查询参数,这取决于你的数据结构,总之data被model 包装隔离了。 不清楚没关系,下面我们看delegate如何数据。

自定义delegate

QT 4.6以后推荐自定义delegate 继承自QStyledItemDelegate,使用styleSheet来显示。不失一般性,这里先用QItemDelegate来说明问题。 在mainWindow.cpp中 对listview 加载delegate:

    MyDelegate *delegate = new MyDelegate(this);
ui->listView->setItemDelegate(delegate);

只读的delegate

先做只读的功能,需要重写 paint,和sizeHint 这个函数

class MyDelegate : public QItemDelegate
{
Q_OBJECT
public:
explicit MyDelegate(QObject *parent = 0);
 
//basic function for a read-only delegate, you can draw anything with the painter
void paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const;
QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const;
};

sizeHint用于指定绘制每个列表项的大小,关键是paint方法, 有了paint,我们就可以任意定制我们的列表

void MyDelegate::paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
QStyleOptionViewItemV4 opt = setOptions(index, option);
 
// prepare
painter->save();
 
// get the data and the rectangles
const QPixmap& pixmap = qvariant_cast<QPixmap>(index.data(Qt::UserRole+1));
QRect decorationRect = QRect(opt.rect.topLeft(), QSize(80,80));
 
const QString& text = index.data(Qt::UserRole).toString();
QFontMetrics fm(painter->font());
QRect displayRect = QRect(decorationRect.topRight()+QPoint(20,30),QSize(fm.width(text),fm.height()));
 
const QPixmap& pixmapSmall = qvariant_cast<QPixmap>(index.data(Qt::UserRole+2));
QRect smallIconRect = QRect(opt.rect.topRight()-QPoint(100,-20), QSize(20,20));
 
 
drawBackground(painter, opt, index);
 
painter->drawPixmap(decorationRect, pixmap);
painter->drawText(displayRect, text);
painter->drawPixmap(smallIconRect, pixmapSmall);
 
drawFocus(painter, opt, displayRect);
 
// done
painter->restore();
}

参见最后的效果图,这里每一个列表项有两幅图,一行文字,图片和文字都是用 qvariant_cast<QPixmap>(index.data(Qt::UserRole+1)); index.data(Qt::UserRole).toString(); 取得的,注意qvariant_cast的用法。

index.data(Qt::UserRole+1) 会调用model中的 QVariant data ( const QModelIndex & index, int role ) const 方法。 delegate 的role和 model 的role对应。 通过修改model中data的实现,可以使得delegate显示不同的东西,而delegate的代码不需变动,实现了数据的隔离。 到目前为止一个只读的自定义列表就完成了,在paint方法中加入下面的代码,可以画出一个pushButton, 但是点击没有反应,这需要在下面的编辑功能中实现。

        QStyleOptionButton opt;
opt.state |= QStyle::State_Enabled;
opt.rect = option.rect.adjusted(1, 1, -10, -10);
opt.text = trUtf8("Button text");
QApplication::style()->drawControl(QStyle::CE_PushButton, &opt, painter, 0);

item中显示不同的字体

这里是painter的使用技巧,要在同一个列表项中的显示的不同的字体,需要在新的的 painter->save,painter->restore段中写, 如下的代码段实现了最后的效果图

void MyDelegate::paint ( QPainter * painter, const QStyleOptionViewItem & opt, const QModelIndex & index ) const
{
painter->save();
// get the data and the rectangles
const QString& text = index.data(Qt::UserRole).toString();
QRect displayRect;//set the paint rect
painter->setPen(QColor(255,127,127));
painter->drawText(displayRect, text);
 
painter->restore();
 
painter->save();
// get the data and the rectangles
const QString& text2 = index.data(Qt::UserRole+1).toString();
QRect displayRect2;//set the paint rect
painter->setPen(QColor(255,122,0));
painter->drawText(displayRect2, text2);
 
painter->restore();
}

增加编辑功能

可编辑的model

要使model可以修改data,我们需要重写flags,setData , 修改后的model变成:

class parserModel : public QAbstractListModel
{
Q_OBJECT
public:
parserModel(const QStringList &strings, QObject *parent = 0);
 
//basic function for a read-only model
int rowCount ( const QModelIndex & parent = QModelIndex() ) const;
QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const ;
 
//for a editable model
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value, int role);
 
private:
//simple data source just a QStringList, if need can add other list of QString, int, bool, struct or class
QStringList stringList;
};

其中 flags方法非常重要,需要根据具体的user case 来设定,这个例子处理的简单草率。 来看setData

bool parserModel::setData(const QModelIndex &index,
const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
 
stringList.replace(index.row(), value.toString());
emit dataChanged(index, index);
return true;
}
return false;
}

由于我们使用的是简单的QListView, 要使用标准的Qt::EditRole来传递编辑数据。数据修改结束发射signal 提示listview 改变显示。 emit dataChanged(index, index);

可编辑的delegate

要使delegate可以修改data,我们需要重写createEditor ,setEditorData ,setModelData ,updateEditorGeometry , 修改后的delegate变成:

class MyDelegate : public QItemDelegate
{
Q_OBJECT
public:
explicit MyDelegate(QObject *parent = 0);
 
//basic function for a read-only delegate, you can draw anything with the painter
void paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const;
QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const;
 
//for a editable delegate
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 ;
void updateEditorGeometry ( QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index ) const ;
};

由于我们这里的数据只是QString, 于是用了个QLineEdit,如果还有其他数据需要编辑,可以做一个widget,在里面加入 QLineEdit ,QSpinBox,checkbox等editor,或自定义的editor如StarDelegate。然后在setModelData 中响应。

QWidget * MyDelegate::createEditor ( QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
QLineEdit *editor = new QLineEdit(parent);
return editor;
}
void MyDelegate::setModelData ( QWidget * editor, QAbstractItemModel * model, const QModelIndex & index ) const
{
QLineEdit *textEdit = qobject_cast<QLineEdit *>(editor) ;
model->setData(index, qVariantFromValue(textEdit->text()));
}

setEditorData需要读取Model数据

void MyDelegate::setEditorData ( QWidget * editor, const QModelIndex & index ) const
{
const QString& text = index.data(Qt::UserRole).toString();
QLineEdit *textEdit = qobject_cast<QLineEdit *>(editor) ;
textEdit->setText(text);
}

列表的元素被选中进入编辑状态后,描绘就不通过paint了,这个时候要重做一遍,好在只需做一次特殊处理,用updateEditorGeometry

void MyDelegate::updateEditorGeometry ( QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
 
QRect decorationRect = QRect(option.rect.topLeft(), QSize(80,80));
QRect displayRect = QRect(decorationRect.topRight()+QPoint(20,30),QSize(150,25));//QRect(decorationRect.topRight()+QPoint(20,30),QSize(50,50));
editor->setGeometry(displayRect);
}

至此一个可编辑的自定义listview就做出来了, 通过修改字符串指向已存在的图像的路径,可以改变列表中的图片。 限于篇幅自定义的按钮留待以后实现。

使用StyleSheet定制listview的外观

参见

未来的工作

selection 在这里没有讨论, 卷动listview时候的动画效果没有讨论, 自定义的按钮可以通过在model中增加状态数据,在delegate增加事件,在view中增加事件过滤来支持,以后讨论

屏幕截图

CustomListview1.PNG
图:图标图片来自:潘斯特

下载源码和例子程序

参考链接

This page was last modified on 11 October 2012, at 01:18.
822 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×