【问题标题】:How to align a primitive checkbox with the center of QHeaderView's column header?如何将原始复选框与 QHeaderView 的列标题的中心对齐?
【发布时间】:2019-03-08 06:40:03
【问题描述】:

我有一个自定义的QTableView 模型和一个自定义的QHeaderView,以便呈现一个用于对表格内容执行“全选”功能的复选框。

在我的标头重载的paintSection() 函数中,我成功渲染了用于全选的复选框:

QStyleOptionButton option;
option.rect = QRect(3,10,16,16);
option.state = QStyle::State_Enabled | QStyle::State_Active;
if (isChecked_)
    option.state |= QStyle::State_On;
else
    option.state |= QStyle::State_Off;
style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &option, painter);

不幸的是,复选框没有居中呈现,而是左对齐。这与每个表条目的列中正确居中的QStyledItemDelegate 复选框完全冲突。

我知道我可以更改QRect 的前两个参数来更改绘制图元的原点,但这不会响应列宽的变化。虽然,固定列宽并不是最糟糕的解决方案。

如何在列标题中正确居中复选框?

辅助问题:可以通过单击单元格中的 anywhere 来切换标题中的复选框,而不仅仅是在框本身上(与通过代表在表格中呈现的那些不同)。有没有办法解决这个问题?

【问题讨论】:

  • 你的第二个问题我也有解决办法,但是按照一帖一问的规定,建议你分开问。您可以发布指向此问题的链接以涵盖常见细节。重要的是解释你如何设置/重置isChecked_。
  • @Answoquest 我的解决方案对你有用吗?
  • @Answoquest 好的,但请始终记住提供反馈,以便您解决任何问题。 :-)
  • @Answoquest,我认为是时候接受eyllanesc的回答了。您不妨考虑投票。

标签: c++ qt qt5 qheaderview


【解决方案1】:

The solution of @scopchanov 对我不起作用,因为复选框涵盖了标题的整个项目

一个可能的解决方案是绘制带有样式的 CheckBox,但除此之外,您必须记住该元素是否被选中,为此我们使用QMap<>,第一个元素是logicalIndex,因为即使移动列并且第二个元素是状态,它也不会改变。

#include <QApplication>
#include <QHeaderView>
#include <QMouseEvent>
#include <QPainter>
#include <QStandardItemModel>
#include <QTableView>

class CheckedHeaderView : public QHeaderView
{
    Q_OBJECT
public:
    using QHeaderView::QHeaderView;
protected:
    void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override
    {
        painter->save();
        QHeaderView::paintSection(painter, rect, logicalIndex);
        painter->restore();
        QStyleOptionButton opt;

        QRect checkbox_rect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &opt);
        checkbox_rect.moveCenter(rect.center());
        opt.rect = checkbox_rect;
        opt.state = QStyle::State_Enabled | QStyle::State_Active;
        if(logicalIndex == columnDown)
            opt.state |= QStyle::State_Sunken;
        if (states[logicalIndex])
            opt.state |= QStyle::State_On;
        else
            opt.state |= QStyle::State_Off;
        style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &opt, painter);
    }
    void mousePressEvent(QMouseEvent *event) override
    {
        QHeaderView::mousePressEvent(event);
        int li = logicalIndexAt(event->pos());
        if(li == -1) return;
        columnDown = li;
        updateSection(li);
    }
    void mouseReleaseEvent(QMouseEvent *event) override
    {
        QHeaderView::mouseReleaseEvent(event);
        int li = logicalIndexAt(event->pos());
        if(li == -1) return;
        states[li] = !states[li];
        Q_EMIT checked(li, states[li]);
        columnDown = -1;
        updateSection(li);
    }
Q_SIGNALS:
    void checked(int logicalIndex, bool state);
private:
    QMap<int, bool> states;
    int columnDown = -1;
};

class TableView : public QTableView
{
    Q_OBJECT
public:
    TableView(QWidget *parent = nullptr):
        QTableView(parent)
    {
        CheckedHeaderView *header = new CheckedHeaderView(Qt::Horizontal, this);
        setHorizontalHeader(header);
        connect(header, &CheckedHeaderView::checked, this, &TableView::on_checked);
    }
private Q_SLOTS:
    void on_checked(int logicalIndex, bool state){
        QItemSelectionModel::SelectionFlags command = state? QItemSelectionModel::Select : QItemSelectionModel::Deselect;
        for(int r=0; r < model()->rowCount(); r++){
            QModelIndex ix = model()->index(r, logicalIndex);
            selectionModel()->select(ix, command);
        }
    }
};


int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    TableView w;
    QStandardItemModel *model = new QStandardItemModel(8, 6, &w);
    w.setModel(model);
    w.show();
    return a.exec();
}

【讨论】:

  • 是的,QStyle::subElementRect 比整个rect 更好,传递给QHeaderView::paintSection。
【解决方案2】:

解决方案

QHeaderView::paintSection 将QRect &amp;rect 作为参数。设置QStyleOption::rect等于rect,即替换

option.rect = QRect(3,10,16,16);

与

option.rect = rect;

注意:此解决方案可响应列宽的变化。

示例

使用您对paintSection 的实现,我创建了一个最小示例,以演示如何实现建议的解决方案。代码在GitHub 上提供。

结果

提供的示例在 Windows 7 上产生以下结果:

Windows 10 上的结果:

注意:此解决方案不适用于 Linux,因为它会产生以下结果(Ubuntu 17):

【讨论】:

  • 感谢您的详细回答。不幸的是,此解决方案扩展了复选框以填充整个标题单元格:i.imgur.com/uq776aQ.png
  • @Answoquest,恐怕我不得不不同意你的观点。你检查了我在 Github 上提供的最小示例吗?结果如图所示,复选框绘制正确。为什么它在您的项目中不起作用是一个单独的问题,所以我再次建议您将问题标记为已接受(您不妨考虑对其进行投票),因为它回答了通用案例中标题中的问题(这是对未来的读者很重要)并发布一个新问题,为什么此解决方案在您的特定情况下不起作用,提供足够的详细信息 (mcve) 来识别问题。
  • @Answoquest 我非常乐意寻找解决方案。我也会看看你的其他问题。
  • 你的 GitHub 代码结果是一样的:i.imgur.com/tZorpal.png。在您的屏幕截图中,您使用的是旧版本的 Windows。
  • @scopchanov 我在 Arch Linux 中尝试过,我得到了不恰当的行为,因为我的答案中显示了图像
猜你喜欢
  • 2021-02-25
  • 2014-11-11
  • 2015-03-10
  • 2018-04-16
  • 2023-01-24
  • 2019-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多