前言
QComboBox是个系统提供的带有下拉框的控件,样式比较单一,默认的下拉框是以列表形式,如下图:
这是一般情况,但是在某些特殊的场景之下我们需要下拉框显示表格,刚好QComboBox给我们提供了方便
api文档
我们看一下QComboBox的文档:
void QComboBox::setView(QAbstractItemView * itemView)
Sets the view to be used in the combobox popup to the given itemView. The combobox takes ownership of the view.
Note: If you want to use the convenience views (like QListWidget, QTableWidget or QTreeWidget), make sure to call setModel() on the combobox with the convenience widgets model before calling this function.
QComboBox的这个方法可以设置下拉框显示的控件,它的参数是QAbstractItemView,我们都知道QTableView、QTreeView、QListView都是从这个类继承出来的,那么也就是说,这三个都可以用于QComboBox类的下拉框了。
注意,使用这个方法之前必须先调用QComboBox::setModel方法
QComboBox::setModel():
void QComboBox::setModel(QAbstractItemModel * model)
Sets the model to be model. model must not be 0. If you want to clear the contents of a model, call clear().
定义model/View的方式跟使用普通的表格控件差不多,不同的只是,原先我们将model交给view,现在将两个都交给QComboBox。
代码
既然上面所说model的调用必须在view前面,所以简单的用了一下建造者模式,下面具体上代码:
首先是自定义ComboBoxTableView的代码:
#include <QComboBox>
#include "constructorexception.h"
class ComboBoxTableView : public QComboBox
{
Q_OBJECT
private:
explicit ComboBoxTableView(QWidget *parent = 0);
signals:
void resize_signal(QRect );
public slots:
public:
void resizeEvent(QResizeEvent *);
// void ressizeView(QRect r);
//内部类,用来建造ComboBoxTableView
class ComboBoxBuild
{
public:
ComboBoxBuild(QWidget *parent) :
parent(parent),
comboBox(NULL),
view(NULL),
model(NULL)
{
}
void setView(QAbstractItemView *itemView)
{
this->view = itemView;
}
void setModel(QAbstractItemModel *model)
{
this->model = model;
}
ComboBoxTableView *build() throw(ConstructorException)
{
if( !model || !view)
throw ConstructorException("model或这个View不能为空!");
comboBox = new ComboBoxTableView(parent);
comboBox->setModel(model);
comboBox->setView(view);
comboBox->view = view;
comboBox->model = model;
return comboBox;
}
private:
QWidget *parent;
ComboBoxTableView *comboBox;
QAbstractItemView *view;
QAbstractItemModel *model;
};
private:
QAbstractItemView *view;
QAbstractItemModel *model;
};
comboBoxtableView.cpp文件的内容:
#include "comboboxtableview.h"
#include <QDebug>
#include <QTableView>
ComboBoxTableView::ComboBoxTableView(QWidget *parent) :
QComboBox(parent)
{
}
void ComboBoxTableView::resizeEvent(QResizeEvent *e)
{
qDebug() << "geometry:" << geometry();
int row = model->rowCount();
int column = model->columnCount();
int width = geometry().width();
int cell = width / column;
QTableView *view = static_cast<QTableView *>(this->view);
for(int i = 0; i < column; i++)
{
view->setColumnWidth(i, cell);
}
emit resize_signal(geometry());
}
上面的代码比较简单,没什么需要说的,先将构造ComboBoxTableView所需要的model/view先交给ComboBoxBuild,真正构造ComboBoxTableView的代码在build里面,这样的话就不会出现View在model之前被调用了。
TableModel的定义:
#include <QAbstractItemModel>
typedef QList<QString> StringList;
/**
* @brief The TableModel class
* 表格model,自定义model继承QAbstractItemModel
*/
class TableModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit TableModel(QObject *parent = 0);
QModelIndex index(int row, int column, const QModelIndex &parent) const;
QModelIndex parent(const QModelIndex &child) const;
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
void setList(StringList *list, int row, int column);
signals:
public slots:
private:
StringList *list;
int row, column; //多少行、多少列
};
TableModel.cpp文件:
#include "tablemodel.h"
#include <QDebug>
#include <QColor>
#include <QSize>
TableModel::TableModel(QObject *parent) :
QAbstractItemModel(parent)
{
}
//list的setter方法
void TableModel::setList(StringList *list, int row, int column)
{
this->list = list;
this->column = column;
this->row = row;
}
//建立索引
QModelIndex TableModel::index(int row, int column, const QModelIndex &parent) const
{
int i = row * this->column + column;
return createIndex(row, column);
}
QModelIndex TableModel::parent(const QModelIndex &child) const
{
return QModelIndex();
}
int TableModel::rowCount(const QModelIndex &parent) const
{
return row;
}
int TableModel::columnCount(const QModelIndex &parent) const
{
return column;
}
QVariant TableModel::data(const QModelIndex &index, int role) const
{
if( role == Qt::CheckStateRole)
return QVariant();
if( role == Qt::TextAlignmentRole)
return (Qt::AlignVCenter);
if( role == Qt::ForegroundRole)
return QColor(250, 150, 150);
int row = index.row();
int column = index.column();
int i = row * this->column + column;
return list->at(i);
}
上面是自定义model的代码,关于这方面的学习可以参考《qt学习之路2》
https://www.devbean.net/2013/05/qt-study-road-2-bool-tree-model/
现在看看我使用的代码:
QWidget *dictWidget = new QWidget;
QVBoxLayout *dicLayout = new QVBoxLayout(dictWidget);
dictWidget->setLayout(dicLayout);
QTableView *view = new QTableView(dictWidget);
//准备数据
StringList *list = new QStringList;
list->append("汉-->英");
list->append("英-->汉");
list->append("汉-->日");
list->append("日-->汉");
list->append("汉-->韩");
list->append("韩-->汉");
list->append("汉-->法");
list->append("法-->汉");
list->append("汉-->俄");
list->append("俄-->汉");
list->append("汉-->西");
list->append("西-->汉");
TableModel *model = new TableModel;
model->setList(list, 3, 4);
//隐藏表头
view->horizontalHeader()->setVisible(false);
view->verticalHeader()->setVisible(false);
view->setAlternatingRowColors(true);
ComboBoxTableView::ComboBoxBuild *build = new ComboBoxTableView::ComboBoxBuild(dictWidget);
build->setModel(model);
build->setView(view);
comboBox = build->build();
dicLayout->addWidget(comboBox);
效果
这个是效果,使用上面的代码,可以完美的显示一个表格,至于显示的颜色、对齐、等等其他设置可以在自定义的TableModel::data()函数里面根据角色返回不同的值,就可以达到设置的目的
使用过程中遇到的问题
在使用QTableView的过程中,本来设置每一个item的宽度,以达到自动填充整个下拉框的,在网上有搜到两种解决办法:
一种是重写QAbstractItemModel::data(QModelIndex, int role)方法,当role等于Qt::sizeHintRole时返回大小,但是我将所有的role都打印出来都没有Qt::sizeHintRole,所以这个方法失效,(注意,当QTableView改成QListView时这种方式有效),第二种是重写QStyledItemDelegate的sizeHint()方法返回宽高,这个方法也没有调用,关于sizeHint()方法的作用:
总之,这种常规的两种方式都失效了,后来又看到一种方法:
总结
看样子QTableView和QListView、QTreeView有点不一样,它设置列宽不是通过角色设置的,而是直接调用setColumnWidth()方法设置的