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

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

简介

这是一个演示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
### 回答1: Qt Model/View Delegate是一个用于实现自定义视图的框架。它可以将数据模型与视图分离,从而简化了数据可视化的实现过程,并且遵循了MVC设计模式。Qt Model/View Delegate框架由三个组件组成:模型、视图和代理,它们分别负责每个领域的功能。 模型负责存储和管理数据,以及通知视图数据的修改。模型可以是一个简单的列表模型,也可以是一个具有层级结构和多个属性的复杂模型。 视图负责在屏幕上显示数据。Qt为我们提供了多种不同的视图类型,以满足不同的需求。不同类型的视图可以以许多不同的方式呈现模型数据,例如表格、树形、列表、图形化等等。 代理负责管理视图中的单元格的外观和行为。通过代理,我们可以自定义单元格的显示方式、编辑方式、数据验证和拖放等等。代理可以获得有关模型的完整信息,从而使开发人员可以轻松地为单元格提供自定义视图。 总之,使用Qt的模型视图框架可以帮助我们快速简单地实现数据可视化,将数据与界面分离,提高代码的可维护性和可重用性。在Qt中,这个框架被广泛地应用于各种领域的应用程序开发。 ### 回答2: Qt Model-View-Delegate (MVC)是一种基于模型、视图和委托的设计模式,用于实现Qt框架中的表格、列表和树形等具有良好交互效果的UI控件。其主要思想是将底层模型和数据逻辑与表现形式分离开来,使得用户可以用统一的接口访问数据,而不必了解其内部实现细节。 MVC模式中,模型(Model)是数据源,它负责存储和管理数据,可以是数据库、服务器或者本地文件等;视图(View)负责呈现模型中的数据,并与用户进行交互,用户所见到的界面就是视图呈现出来的结果;委托(Delegate)作为模型和视图之间的桥梁,负责处理鼠标、键盘事件等用户的操作,并将其反映到底层的模型中。 Qt框架中的QAbstractItemModel类提供了表格、列表和树形控件所需的模型接口,而QAbstractItemView类则提供了对应的视图接口。QItemDelegate类则是一个通用的委托类,它可以创建和管理单元格、行和列的编辑和显示,为数据提供不同的可视化效果。 使用Qt Model-View-Delegate框架可以帮助开发者简化数据管理和界面设计的任务,提高用户体验,同时增加了代码的可维护性和可扩展性。 ### 回答3: Qt Model-View-Delegate(MVD)是一个重要的框架,它是用于管理和展示大量数据的高效方式。该框架可以将数据与视图分离,从而使得用户可以更加灵活地处理数据和视图的交互。 Model是指QT提供的数据模型,View是指数据展示的视图,Delegate则是视图的代理,主要负责对视图的各种单元格进行渲染,由Delegate完成格式化和显示控件等。 代理可以被用来实现视图和数据的进一步定制,改变视图对数据的显示方式或者增加行为特性。代理可以在显示表格视图、树视图等多种类型视图的时候,改变它们的样式和行为特性。 通过使用代理,我们可以自定义复选框、按钮、进度条等控件,添加到视图中以进行用户交互。我们还可以定制排序、筛选、背景颜色等视觉效果,从而创建一个更有效的用户界面。 总之,Qt MVD框架具有高度的灵活性和可扩展性,允许用户对数据和视图进行个性化的定制,提高应用程序的用户体验和效率。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值