目的
仿Qt写一个在鼠标move过程中, 会有一个矩形的Elastic Band(可以清晰的表示当前的鼠标移动过程中的所可以选中范围区域)。 那么下面将直接进入主题了。
说明
之所以写着一篇博客, 主要是来源于我发现QListView有时候会有elastic band出现, 有时候没有。而QTreeView是直接没有。查看源码发现QListView::setViewMode这个导致, 默认情况下ListMode没有, IconMode 有。
void QListView::setViewMode(ViewMode mode)
{
Q_D(QListView);
if (d->commonListView && d->viewMode == mode)
return;
d->viewMode = mode;
delete d->commonListView;
if (mode == ListMode) {
d->commonListView = new QListModeViewBase(this, d);
if (!(d->modeProperties & QListViewPrivate::Wrap))
d->setWrapping(false);
if (!(d->modeProperties & QListViewPrivate::Spacing))
d->setSpacing(0);
if (!(d->modeProperties & QListViewPrivate::GridSize))
d->setGridSize(QSize());
if (!(d->modeProperties & QListViewPrivate::Flow))
d->flow = TopToBottom;
if (!(d->modeProperties & QListViewPrivate::Movement))
d->movement = Static;
if (!(d->modeProperties & QListViewPrivate::ResizeMode))
d->resizeMode = Fixed;
if (!(d->modeProperties & QListViewPrivate::SelectionRectVisible))
d->showElasticBand = false;
} else {
d->commonListView = new QIconModeViewBase(this, d);
if (!(d->modeProperties & QListViewPrivate::Wrap))
d->setWrapping(true);
if (!(d->modeProperties & QListViewPrivate::Spacing))
d->setSpacing(0);
if (!(d->modeProperties & QListViewPrivate::GridSize))
d->setGridSize(QSize());
if (!(d->modeProperties & QListViewPrivate::Flow))
d->flow = LeftToRight;
if (!(d->modeProperties & QListViewPrivate::Movement))
d->movement = Free;
if (!(d->modeProperties & QListViewPrivate::ResizeMode))
d->resizeMode = Fixed;
if (!(d->modeProperties & QListViewPrivate::SelectionRectVisible))
d->showElasticBand = true;
}
#if QT_CONFIG(draganddrop)
bool movable = (d->movement != Static);
setDragEnabled(movable);
setAcceptDrops(movable);
#endif
d->clear();
d->doDelayedItemsLayout();
}
核心在于
if (!(d->modeProperties & QListViewPrivate::SelectionRectVisible))
d->showElasticBand = true;
selectionRectVisible : bool, 可以通过这setSelectionRectVisible个接口设置显隐, 其他view是没有的。
参考对应源码, 你可以仿真写一个类似的, 以QTreeWidget为基类
void FileTableWidget::mousePressEvent(QMouseEvent *event)
{
QTreeWidget::mousePressEvent(event);
m_press_pos = event->pos();
//......do something
}
void FileTableWidget::mouseMoveEvent(QMouseEvent *event)
{
if (QLineF(event->pos(), m_press_pos).length() >= QApplication::startDragDistance())
{
auto buttons = event->buttons();
if (buttons & Qt::LeftButton) {
QRect rect(m_press_pos, event->pos());
rect = rect.normalized();
this->viewport()->update(rect.united(m_elasticBand)); //viewport
m_elasticBand = rect;
m_showElasticBand = true;
}
}
//......do something
QTreeWidget::mouseMoveEvent(event);
}
void FileTableWidget::mouseReleaseEvent(QMouseEvent *event)
{
//......do something
QTreeWidget::mouseReleaseEvent(event);
if(m_showElasticBand){
this->viewport()->update(m_elasticBand);
m_showElasticBand = false;
m_elasticBand = QRect();
}
}
//核心
void FileTableWidget::paintEvent(QPaintEvent *event)
{
QTreeWidget::paintEvent(event);
QPainter painter(this->viewport());
if (m_elasticBand.isValid() && m_showElasticBand) {
QStyleOptionRubberBand opt;
opt.initFrom(this);
opt.shape = QRubberBand::Rectangle;
opt.opaque = false;
opt.rect = this->m_elasticBand.intersected(this->viewport()->rect().adjusted(-16, -16, 16, 16));
painter.save();
style()->drawControl(QStyle::CE_RubberBand, &opt, &painter);
painter.restore();
}
}
主要核心 就是通过QPainter 绘制 QRubberBand::Rectangle, QPainter 是在viewport上, 以及对move过程中矩形区域进行normalized(), 取united,获取移动过程中最大的矩形区域。 所有的矩形区域操作都是基于viewport的。