我想在
QTableWidget
中使用粗体颜色的线条分隔特定的列/行。
我可以通过在与列分隔相邻的单元格两侧画线轻松实现分隔(通过实施自定义
QItemDelegate
):
但是,我不想在单元格内部绘制,而是在它们之间的区域绘制。
此外,将行延伸到标题中将是我的应用程序的理想外观:
有没有办法实现这种外观?
我们在
paintEvent
的子类中覆盖 QTableView
(如果需要,我让你替换为 QTableWidget
)和 QHeaderView
的另一个(私有)子类。#include <QtWidgets/QHeaderView>
#include <QtWidgets/QTableView>;
#include <QtGui/QPaintEvent>
#include <QtGui/QPainter>
// Tableview that draws a rectangle between 2 columns, called split rectangle.
// The split rectangle is drawn over the right part of a chosen column.
// A big part of the code consists in tweaking some behavior to hide the fact that the column actually extends under the split rectangle. Among other things:
// The header and column width are increased to leave enough space for the rectangle.
// Nothing will happen for item selection if the click takes place over the rectangle.
class MyTableView : public QTableView {
private:
class MyHeader : public QHeaderView {
public:
MyHeader(int splitAfterColumn, int lineThickness, MyTableView* parent) :
QHeaderView(Qt::Horizontal, parent),
splitColumn(splitAfterColumn),
thickness(lineThickness)
{}
protected:
void paintSection(QPainter* painter, const QRect& rect, int logicalIndex) const override
{
if (logicalIndex + 1 == splitColumn)
QHeaderView::paintSection(painter, rect.adjusted(0, 0, -thickness, 0), logicalIndex);
else
QHeaderView::paintSection(painter, rect, logicalIndex);
}
void paintEvent(QPaintEvent* event) override
{
QHeaderView::paintEvent(event);
int x = sectionViewportPosition(3);
QPainter p(viewport());
p.setBrush(Qt::black);
if (auto maxAvailableSize = sectionSize(splitColumn-1); maxAvailableSize < thickness)
p.fillRect(x - maxAvailableSize, 0, maxAvailableSize, height(), Qt::BrushStyle::SolidPattern);
else
p.fillRect(x - thickness, 0, thickness, height(), Qt::BrushStyle::SolidPattern);
}
QSize sectionSizeFromContents(int logicalIndex) const override {
if (logicalIndex + 1 == splitColumn) {
QSize contentSize = QHeaderView::sectionSizeFromContents(logicalIndex);
if (contentSize.width() < minimumSectionSize())
contentSize.setWidth(minimumSectionSize());
return contentSize + QSize(thickness, 0);
}
else
return QHeaderView::sectionSizeFromContents(logicalIndex);
}
private:
int splitColumn, thickness;
};
// The delegate exists only to draw a correct dotted rectangle around the index that has the focus.
// A (now unused) inDelegate attribute can be leveraged
public:
MyTableView(int splitAfterColumn, int lineThickness, QWidget* parent = nullptr) :
QTableView(parent),
splitColumn(splitAfterColumn),
thickness(lineThickness)
{
setHorizontalHeader(new MyHeader(splitColumn, thickness, this));
}
QModelIndex indexAt(const QPoint& pos) const
{
QModelIndex index = QTableView::indexAt(pos);
if (index.column() + 1 == splitColumn) {
if (visualRect(index).contains(pos))
return index;
else
return QModelIndex();
}
else
return index;
}
QRect visualRect(const QModelIndex& index) const
{
QRect rect = QTableView::visualRect(index);
if (rect.isValid() && ! rect.isNull() && index.column() + 1 == splitColumn)
return rect.adjusted(0, 0, -thickness, 0);
else return rect;
}
QRegion visualRegionForSelection(const QItemSelection& selection) const
{
//Adjust the region covered by a selection.
QRegion region = QTableView::visualRegionForSelection(selection);
return region.subtracted(QRegion(separatorArea()));
}
protected:
void paintEvent(QPaintEvent* event) override
{
QTableView::paintEvent(event);
if (model() && splitColumn > 0) {
QPainter p(viewport());
p.setBrush(Qt::black);
QRect splitRect = separatorArea();
p.fillRect(splitRect, Qt::BrushStyle::SolidPattern);
QStyleOptionViewItem option;
option.initFrom(this);
const QColor gridColor = static_cast<QRgb>(style()->styleHint(QStyle::SH_Table_GridLineColor, &option, this));
p.setPen(gridColor);
p.drawLine(splitRect.topLeft() - QPoint(1, 0), splitRect.bottomLeft() - QPoint(1, 0));
}
}
int sizeHintForColumn(int column) const override
{
return QTableView::sizeHintForColumn(column) + (column + 1 == splitColumn ? thickness : 0);
}
private:
QRect separatorArea() const
{
int x = columnViewportPosition(3);
//Replace h by the commented lines below if the line should stop on the last model row.
int h = height();
//auto findHeight = [this]() -> int {
// //Dichotomic search of the table height (if smaller than its viewport)
// int hTop = 0, hBottom = height();
// while (hTop < hBottom - 1) {
// if (auto hMiddle = (hTop + hBottom) >> 1; indexAt(QPoint(0, hMiddle)).isValid())
// hTop = hMiddle;
// else
// hBottom = hMiddle;
// }
// return indexAt(QPoint(0, hBottom)).isValid() ? hBottom : hTop;
//};
//int h = findHeight();
if (auto maxAvailableSize = columnWidth(splitColumn - 1); maxAvailableSize < thickness)
return QRect(x - maxAvailableSize, 0, maxAvailableSize, h);
else
return QRect(x - thickness, 0, thickness, h);
}
int splitColumn, thickness;
};
我让您通过检测可以跳过对
MyTableView::paintEvent
的调用和/或只要模型不变就将 findHeight 的值保留在内存中的情况来优化 findHeight
。您可以使用下面的主要功能来测试类:
int main(int argc, char** arga)
{
QApplication a(argc, arga);
QStandardItemModel model;
for (int r = 1; r <= 10; ++r) {
QList<QStandardItem*> items;
for (int c = 1; c <= 10; ++c)
items.append(new QStandardItem(QString::number(r) + ',' + QString::number(c)));
model.appendRow(items);
}
MyTableView view(3, 20);
view.setHorizontalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
view.setMinimumSize(640, 480);
view.setModel(&model);
view.resizeColumnsToContents();
view.show();
return a.exec();
}
编辑:我更改了代码以解决下面的评论。
在具有焦点的索引上出现的轮廓仍然存在一个小问题。可以用proxy delegate来解决,代码还没写完
#include <QtWidgets/QStyledItemDelegate>
class MyProxyDelegate : public QStyledItemDelegate
{
public:
MyProxyDelegate (int lineThickness, QObject* parent = nullptr) :
QStyledItemDelegate(parent),
thickness(lineThickness),
inDelegate(nullptr)
{}
protected:
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QStyleOptionViewItem opt = option;
if (index.column() + 1 == 3)
opt.rect.adjust(0, 0, -thickness, 0);
if (inDelegate)
inDelegate->paint(painter, opt, index);
else
QStyledItemDelegate::paint(painter, opt, index);
}
private:
int thickness;
QAbstractItemDelegate* inDelegate;
friend class MyTableView;
};