Qt中派生QAbatractProxyModel
Qt是非常强大的,功能很全,它有个特点:如果你顺着它的特性做需求,那就会非常舒服顺畅。如果你要越过它的特性做一些特别的事:
VERY PAINFULL
以下记录本人的一个非常痛苦的经历,希望可以让后来人少走弯路。
首先遇到的需求是,做一个可以根据外部筛选规则来自定义显示的tableview,外界可以选择显示数据集合中的某一段,此时tableview表现就好像整个数据集合只有这些数据一样。另外,原数据是一维数据,显示出来将是一个有行列的二维数据。这个规则不能用Qt的QSortFilterProxyModel来做,因为Filter只能针对DisplayRole做一些filter操作,说白了,是为查找功能而生的。当然绝大多数软件都只会有常规查找功能,不会有我这样奇葩的需求。
当然,也可以通过Role来进行筛选,不过作为一个程序员,不能满足于使用这种类似“HACK”的方法实现。既然我们的需求和QSortFilterProxyModel的设计初衷不一样了,自然,我们应该写一个新的QAbstractProxyModel。
一般来说,从QAbstractProxyModel直接继承需要写很多代码,其中包括connect大量原数据集合的各种数据更改signal。这是非常痛苦的。所以,我们不应该直接从QAbstractProxyModel,这也是Qt所推荐的:Qt文档里指出,应该选择最接近的行为的ProxyModel进行继承。于是,我选择了存在感最低的QIdentityProxyModel,事实上也没什么更好选择,总共就两个,QIdentityProxyModel和QSortFilterProxyModel,后者所做的特化太多了。
继承了它之后,重写几个必要函数:
QModelIndex mapToSource(const QModelIndex &proxy_index) const Q_DECL_OVERRIDE; QModelIndex mapFromSource(const QModelIndex &source_index) const Q_DECL_OVERRIDE; QModelIndex sibling(int row, int column, const QModelIndex &idx) const Q_DECL_OVERRIDE; QModelIndex parent(const QModelIndex & proxy_child) const Q_DECL_OVERRIDE; QModelIndex index(int row,int column,const QModelIndex &proxy_index) const Q_DECL_OVERRIDE; QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; int rowCount(const QModelIndex &proxy_parent) const Q_DECL_OVERRIDE; int columnCount(const QModelIndex &proxy_parent) const Q_DECL_OVERRIDE;
片刻的编译之后,程序如愿地进行了filter。
然而这并不是完美结局,很快发现一个奇怪的问题:除了以原数据0为开始的filter形式,其他形式的选择功能全部表现得非常奇怪,具体地说,不管怎样进行矩形范围选择,selection永远都是从原数据第0个,到所选择的第一个项。然而如果直接使用原数据集合,那当然是没有问题。
这就非常奇怪了,因为我正确地实现了mapToSource和mapFromSource。难道proxy不应该是透明了吗?为什么会产生这样的变化?由于Qt在mac上不太好调试,我无法跟踪到Qt代码中,进行了各种log输出,最奇怪的在于:当调用selectionModel->selectedIndex()时,结果是错误的,但在selectionChanged的信号中,selected和unselected是正确的。这就更奇怪了,我查看源代码,这两处应该是一样的处理方式。
最后,我把目光放在QIdentityProxyModel上,检查发现,它重写了sibling函数。我重写这个函数,直接调用QAbstractProxyModel的函数,问题解决。
原因在于,selection在导出index的时候,使用了sibling,从top到bottom,一路用sibling来创建index。而QIdentityProxyModel中,sibling是直接转发了原始数据集合的,而原始数据集合只有一列,导致sibling全部是-1,-1,导致了最后selectedIndex返回的错误。
结论1:Qt做东西,最好顺着它做。
结论2:继承一个并不了解的类做事情,如果出现了问题,首先考虑这个类本身的行为是不是影响了预期目标