Model:列表的数据集合,每一条Item是一个数据表,默认的key值是Qt::DisplayRole,用户可扩展以Qt::UserRole起始的自定义字段;数据提供者;
Delegate:Item的样式实现,使用Model中的数据来创建Item的UI;数据使用者;
View:List UI的载体,带有默认的Model和Delegate,也可设置自定义的Model及Delegate来实现自定义的ListView;
void ProjectInfoListView::init(QListView::ViewMode viewMode)
{
//layout;
auto layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
this->setLayout(layout);
//listview;
m_listView = new QListView(this);
layout->addWidget(m_listView);
//model
m_model = new ProjectInfoModel(this);
m_listView->setModel(m_model);
//list delegate;
m_delegate = new ProjectInfoDelegate(this);
m_listView->setItemDelegate(m_delegate);
}
分别创建QListView,ProjectInfoModel和ProjectInfoDelegate的对象,然后把Model和Delegate对象设置给ListView。
void ProjectInfoListView::setViewMode(QListView::ViewMode viewMode)
{
m_viewMode = viewMode;
if(viewMode == QListView::IconMode)
{
m_listView->setItemDelegate(m_iconDelegate);
m_listView->setViewMode(QListView::IconMode);
m_listView->setProperty("isListMode", false);
m_listView->setSelectionBehavior(QAbstractItemView::SelectItems);
m_listView->setResizeMode(QListView::Adjust); //ListView Resize时是否根据新的宽高重新排列;
m_listView->setWrapping(true); //一行多个Item,直到占满一行;
m_listView->setSpacing(10);
m_listView->verticalScrollBar()->setSingleStep(100);
}
else
{
m_listView->setItemDelegate(m_listDelegate);
m_listView->setViewMode(QListView::ListMode);
m_listView->setProperty("isListMode", true);
m_listView->setSelectionBehavior(QAbstractItemView::SelectRows);
m_listView->setWrapping(false); //一行多个Item,直到占满一行;
m_listView->setSpacing(0);
m_listView->verticalScrollBar()->setSingleStep(1);
}
m_listView->scrollToTop();
style()->unpolish(m_listView);
style()->polish(m_listView);
}
创建m_listDelegate和m_iconDelegate两个不同类型的Delegate对象,分别代表list mode和icon mode。
namespace fly
{
struct ProjectDetailInfo
{
bool checked = false;
QString name = "";
QDateTime modifyTime;
QString size = "";
int hoverPosX= 0;
ProjectDetailInfoKey clickedRole = PROJECT_NAME;
QString iconPath = ":/icon/default.png";
};
}
class ProjectInfoModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit ProjectInfoModel(QObject* parent=nullptr);
explicit ProjectInfoModel(const QList& itemList, QObject*parent=nullptr);
virtual ~ProjectInfoModel();
void appendList(const QList& itemList);
void append(const fly::ProjectDetailInfo& item);
void insert(int row, const fly::ProjectDetailInfo& item);
void remove(int row);
private:
QList m_list;
};
用户数据实际上存储在m_list中,我们自定义了ProjectDetailInfod数据结构,增加了append,insert,remove等接口来操作m_list,实现Model的更新。
Item的数据读写需要通过固定的接口来实现,分别是data()和setData(),这两个函数会在Delegate被使用到,我们需要对其进行重写,使它们支持自定义的数据结构ProjectDetailInfo。
QVariant ProjectInfoModel::data(const QModelIndex &index, int role) const
{
QVariant var;
do
{
if(!index.isValid())
break;
if(index.row()>= m_list.size() || index.row()< 0)
break;
const auto& info = m_list.at(index.row());
switch(role)
{
case fly::CHECKED_STATE:
var = info.checked;
break;
case fly::PROJECT_NAME:
var = info.name;
break;
case fly::MODIFY_TIME:
var = info.modifyTime;
break;
case fly::PROJECT_SIZE:
var = info.size;
break;
case fly::HOVER_POS_X:
var = info.hoverPosX;
break;
case fly::ICON_PATH:
var = info.iconPath;
break;
case fly::CLICKED_ROLE:
var = info.clickedRole;
break;
case Qt::DisplayRole:
var = info.name;
break;
}
}while(0);
return var;
}
index代表一条item数据,role则代表一条item数据下的一个字段(一条item可以有多个字段)。
bool ProjectInfoModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
int row = index.row();
if(row>= m_list.size() || row< 0)
return false;
auto info = m_list.at(row);
switch(role)
{
case fly::CHECKED_STATE:
info.checked = value.toBool();
break;
case fly::PROJECT_NAME:
info.name = value.toString();
break;
case fly::MODIFY_TIME:
info.modifyTime = value.toDateTime();
break;
case fly::PROJECT_SIZE:
info.size = value.toString();
break;
case fly::HOVER_POS_X:
info.hoverPosX = value.toInt();
break;
case fly::ICON_PATH:
info.iconPath = value.toString();
break;
case fly::CLICKED_ROLE:
info.clickedRole = (fly::ProjectDetailInfoKey)value.toInt();
break;
case Qt::DisplayRole:
info.name = value.toString();
break;
}
m_list.replace(row, info);
return true;
}
对应于data()访问数据函数,setData()函数则是用于通过key值更新数据,同样传入index定位到item,再通过role找到对应的字段。
int ProjectInfoModel::rowCount(const QModelIndex &parent) const
{
(void)parent;
return m_itemCount;
}
bool ProjectInfoModel::canFetchMore(const QModelIndex &parent) const
{
(void)parent;
if(m_itemCount< m_list.size())
return true;
else
return false;
}
void ProjectInfoModel::fetchMore(const QModelIndex &parent)
{
(void)parent;
int remainder = m_list.size()- m_itemCount;
int itemToFetch = qMin(m_onceFetchNum, remainder);
if(itemToFetch< 0)
return;
beginInsertRows(QModelIndex(), m_itemCount, m_itemCount+ itemToFetch- 1);
m_itemCount += itemToFetch;
endInsertRows();
}
通过重写基类的rowCount(),canFetchMore(),fetchMore()三个函数实现item的动态加载,这三个函数默认由ListView底层去调用,我们只需关注实现。
m_itemCount记录的是ListView当前加载的item数量,ListView会根据这个值去计算滚动条的位置。
当滚动条到达底部时,底层会调用canFetchMore()函数,若返回true,则下一步调用fetchMore(),fetchMore()里面实现了item的加载。
void ProjectInfoDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
fly::ProjectDetailInfo info;
info.init(index);
QRect rect = option.rect;
QRect checkedRect;
QRect iconRect;
QRect nameRect;
QRect timeRect;
QRect sizeRect;
QRect moreRect;
getRects(rect, checkedRect, iconRect, nameRect, timeRect, sizeRect, moreRect);
//checked button;
if(info.checked)
{
QPixmap pix(":/icon/checked.png");
painter->drawPixmap(checkedRect, pix);
}
else
{
QPixmap pix(":/icon/unchecked.png");
painter->drawPixmap(checkedRect, pix);
}
//icon
QPixmap iconPix(info.iconPath);
painter->drawPixmap(iconRect, iconPix);
{
QFont font;
font.setFamily("Microsoft YaHei");
font.setPixelSize(18);
painter->setFont(font);
painter->drawText(nameRect, Qt::AlignLeft, info.name);
painter->drawText(timeRect, Qt::AlignLeft, info.modifyTime.toString());
painter->drawText(sizeRect, Qt::AlignLeft, info.size);
}
//more button;
QPixmap morePix(":/icon/more.png");
painter->drawPixmap(moreRect, morePix);
}
ProjectInfoDelegate继承于QStyledItemDelegate,重写它的paint()函数,通过传入的index所带的数据来绘制item。所有的item都会通过paint()函数来创建,所以过程是统一的,差异在于每条index带的数据会不一样。
bool ProjectInfoDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
ProjectInfoModel* conModel = static_cast(model);
QRect rect = option.rect;
QRect checkedRect;
QRect iconRect;
QRect nameRect;
QRect timeRect;
QRect sizeRect;
QRect moreRect;
getRects(rect, checkedRect, iconRect, nameRect, timeRect, sizeRect, moreRect);
QMouseEvent* msEvent = static_cast(event);
if(event->type() == QEvent::MouseButtonPress)
{
if(checkedRect.contains(msEvent->pos()))
{
conModel->setData(index, fly::CHECKED_STATE, fly::CLICKED_ROLE);
emit conModel->clicked(index);
conModel->setData(index, false, fly::CHECKED_STATE);
}
else if(moreRect.contains(msEvent->pos()))
{
emit conModel->moreBtnClick(index, QPoint(rect.x()+ moreRect.x()+ moreRect.width(), rect.y()+ moreRect.height()));
}
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
editorEvent()函数会传入鼠标的Pressed事件,以及当前index和整个Model,我们在判断到MouseButtonPress事件时,可以根据鼠标当前所在的位置来判断属于哪个控件的区域,然后更新对应item的CLICKED_ROLE字段的值,该字段记录了鼠标事件属于哪个控件的。通过在Model中的自定义信号clicked发出信号,该信号可以在Delegate对象持有者中进行处理。
bool ProjectInfoListView::eventFilter(QObject *watched, QEvent *event)
{
//捕获ListView的MouseMove事件,并把Mouse的position值记录在鼠标当前所在的item的数据里,用于实现item的多控件hover响应;
if(watched == m_listView->viewport() && event->type() == QMouseEvent::MouseMove)
{
QMouseEvent* msEvent = static_cast(event);
m_model->setData(m_listView->indexAt(msEvent->pos()), msEvent->x(), fly::HOVER_POS_X);
update();
}
return QWidget::eventFilter(watched, event);
}
void ProjectInfoDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QRect rect = option.rect;
QRect checkedRect;
QRect iconRect;
QRect nameRect;
QRect timeRect;
QRect sizeRect;
QRect moreRect;
getRects(rect, checkedRect, iconRect, nameRect, timeRect, sizeRect, moreRect);
QPoint msPos = QPoint(index.data(fly::HOVER_POS_X).toInt(), rect.y());
if(option.state & QStyle::State_MouseOver)
{
//more button hover
if(moreRect.contains(msPos))
{
painter->fillRect(moreRect, QColor(100, 100, 100, 100));
}
else if(nameRect.contains(msPos))
{
painter->fillRect(nameRect, QColor(100, 100, 100, 100));
}
}
}
item中的鼠标hover事件需要由QListView的viewport的的MouseMove来实现,Delegate的paint()函数中虽然有QStyle::State_MouseOver事件,但这个事件只能实现item的hover,无法实现item里面具体的控件的hover。结合QListView的viewport中捕获的Mouse 的位置就可以具体到哪个控件,同样也是通过把Mouse的位置记录到item的自定义的HOVER_POS_X字段中,再在paint()中去读取这个值。
重写createEidtor(),setEditorData(),setModelData()和updateEditorGeometry()这四个函数可以实现在item上动态创建各种类型的控件,比如QLineEidt,QSlider,甚至自绘的控件都可以。
1. QListView可以通过setEditTriggers()函数设置触发item进入Eidt模式的方式,分别有鼠标双击,选中单击等选项。假如设置了双击触发,那么在双击item时QListView底层会调用createEidtor()来获取要创建的控件对象,当我们在子类重写了createEditor(),使它返回我们自己创建的控件,那么动态创建控件的目的就达到了。
2. 底层在调用完createEditor()后,会接着调用setEidtorData()来初始化控件的内容,比如说把item的内容设置在控件上。
3. createEidtor()创建出来的控件默认位置是当前item的(0, 0) 位置,要想控件显示在不同的位置可以重写updateEditorGeometry()把控件移到希望显示的位置,同样这个函数也是由QListView底层调用。
4. setModelData()的作用是在item推出edit状态后更新Model的,也是由QListView底层调用,比如输入框输入完成后,把输入框的内容更新到Model中。
【QListViewDemo】https://download.csdn.net/download/JellyLi2091/88068274