更改 QTableWidget 中选择性列/行分隔线的外观

问题描述 投票:0回答:1

我想在

QTableWidget
中使用粗体颜色的线条分隔特定的列/行。

我可以通过在与列分隔相邻的单元格两侧画线轻松实现分隔(通过实施自定义

QItemDelegate
):

但是,我不想在单元格内部绘制,而是在它们之间的区域绘制。

此外,将行延伸到标题中将是我的应用程序的理想外观:

有没有办法实现这种外观?

qt qtablewidget qpainter
1个回答
2
投票

我们在

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;
};

© www.soinside.com 2019 - 2024. All rights reserved.