最近在用QT写的一个程序中,需要一个对大量参数进行设置的功能。过去用Delphi的经验是,尽量不要使用QListView和TGrid来实现,因为涉及到控件的对齐,界面的重绘等,反而使简单问题变得复杂。因此这次用QT写程序时也想当然,用控件数组的方式来解决。很快程序写好了,界面如下:
显示出的效果还不错,但是遇到了一个严重的问题,那就是速度。由于QT的机制是自动调整控件比例来适应各种系统和窗体大小,而这个调整的功能其实涉及到复杂的运算。另外,QT的控件创建也不如真正控件的创建那么快。因此当控件数量一大,构建整个界面就变得其慢无比了。在我的机器上,像上图这样的界面打开需要1-2秒时间,而且即使到了性能较好的计算机上也没有什么大的改善。这样的效果就连自己都无法忍受,更别说使用者了。
于是考虑改进,参考QT的example中的spinboxdelegate例子,使用QTableView来解决。具体方法是:
1)在控件设计器中放置一个QTableView,并调整好其与其它控件的位置关系。生成ui文件,在程序中制作相应的界面窗体类。
2)从QItemDelegate中继承出一个新的类,它将用来配合上面的QTableView,用来告诉其是否可以针对某个Cell进入编辑模式,以及在编辑该Cell时,使用什么控件,相互的值如何传递等。
具体来说,只要继承QItemDelegate中以下的几个虚函数就可以了:
class enstCatheterNodeInfoDelegate : public QItemDelegate
{
Q_OBJECT
public:
enstCatheterNodeInfoDelegate(QObject *parent, enstCatheterInfo *pCatheterInfo);
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;
};
createEditor()函数,在QTableView中某个Cell被DoubleClick时触发,这时可以用其index参数,获得该Cell的行与列,如果确定该Cell是可以被编辑的,那么就生成一个对应的编辑控件,如:
QComboBox *cbo = new QComboBox(parent);
cbo->addItem(" 1 ");
cbo->addItem(" 2 ");
cbo->addItem(" 3 ");
cbo->installEventFilter(const_cast<enstCatheterNodeInfoDelegate*>(this));
return cbo;
这时,一个ComboBox就会被自动替换到该Cell的位置上了。以上cbo->installEventFilter(),是为了让QItemDelegate能够获得用户在该控件上的Enter,Escape等事件,以便做出相应处理。
如果该Cell不能被编辑,此时返回NULL就可以了。
setEditorData()函数,用来对生成的控件中的值进行初始化,一般来说,这个函数在以上的createEditor()函数结束后会被自动调用,如下:
QComboBox *cbo = static_cast<QComboBox*>(editor);
cbo->setCurrentIndex(0);
editor是QWidget类型的参数,它代表createEditor()函数中生成的那个控件,这里必须将其进行类型转换,才可以变成自己实际生成的那个控件类型。如果在createEditor()中根据index.column()不同,生成了不同的编辑控件,那么这里也需要判断index.column(),来将editor参数转换成相应的控件。
接着setModelData(),它会在用户的离开当前编辑中的Cell时被调用,可以在此将编辑控件中被修改了的数据写回到QTableView中,如下:
QString tmpstr;
QComboBox *cbo = static_cast<QComboBox*>(editor);
tmpstr.setNum(cbo->currentIndex());
model->setData(index, tmpstr);
最后,updateEditorGeometry(),这个函数是用来控制编辑控件的位置的,一般只需要写成:
editor->setGeometry(option.rect);
编辑控件就会自动被调整到Cell的大小了。
3)在实际的窗体类中,使用如下的代码将以上这个类与QTableView关联起来:
enstCatheterNodeInfoDelegate *delegate = new enstCatheterNodeInfoDelegate(this);
tbl1->setItemDelegate(delegate);
4)光这样还不行,由于QTableView实在太底层了,它还需要知道以怎样的方式显示,即指定一个ItemModel,可以选用的类包括:
QDirModel:可以以树状方式显示某个目录下的所有子目录,以及其相关信息。
QProxyModel:用来将旧的Model类型过渡到新类型上的。
QStandardItemModel:最简单的Grid方式的显示Model。
另外,还可以自己从QAbstractListModel, QAbstractProxyModel, QAbstractTableModel来继承出符合自己要求的model。
偷个懒,我就直接使用QStandardItemModel了:
mItemModel = new QStandradItemMode(this, 3, 10);
tbl1->setModel(mItemModel);
5)基本完成,最后再加上初始化QTableView中所有文字,以及最终将设置好了的内容保存出来的代码,就可以了,完成的界面显示如下:
但是,仔细看看,还有不少不满意的地方,TableView的ColumnHeader居然自动填上了数字,而左边则多出了一条垂直Header,另外,所有Cell的宽度变成一样的了,多少有点不爽。。经过努力,总算摸索出了解决方法,不过不清楚真正的解决方法是什么,我这个解决方法应该并不好:
首先从QStandardItemModel中继承出自己的类,暂且名叫enstCatheterNodeItemModel了:
class enstCatheterNodeItemModel : public QStandardItemModel
{
Q_OBJECT
public:
enstCatheterNodeItemModel(QObject *parent = 0);
};
在其构造函数中,使用:
enstCatheterNodeItemModel::enstCatheterNodeItemModel(QObject *parent)
: QStandardItemModel(MAX_CATHETERNODE_COUNT, 3, parent)
{
setHeaderData(0, Qt::Horizontal, tr("Input"));
setHeaderData(1, Qt::Horizontal, tr("Signal"));
setHeaderData(2, Qt::Horizontal, tr("Locate"));
}
强制其只能有三列,并指定每个列的标题。
在窗体的构造函数中,进一步调整QTableView:
QHeaderView *header;
header = tbl1->horizontalHeader();
header->resizeSection(0, 260);
header->resizeSection(1, 260);
header->resizeSection(2, 50);
header = tbl1->verticalHeader();
header->hide();
设置其各个列的宽度,同时将左边的垂直Header隐藏。
总算大功告成,加载延时已经基本感觉不到了。看看最后的效果:
以及另一组编辑器,其中的颜色选择ComboBox并不是QT自带的控件,而是自己实现的,照样可以被做成编辑控件:
完。