【问题标题】:Drawing QGraphicsLineItem on a QTableView在 QTableView 上绘制 QGraphicsLineItem
【发布时间】:2020-07-27 16:51:36
【问题描述】:

我有一个应用程序,我需要在 QTableView 上画线以显示帧的范围。我有一个QGraphicsViewQGraphicsScene,其中包含一个QTableView 如下图:

标准

  • 行的跨度应保持相对于列的位置,即第一行应始终保持在 3 到 7 之间,即使滚动出视图也是如此。向后滚动时应该再次可见

  • 我想在 QModelIndex 中检索行的起始索引和结束索引。

  • 行必须始终保持行宽的中心

我特此创建了一个 MVCE

主窗口.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

MainWindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "timelineview.h"

MainWindow::MainWindow(QWidget* parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    TimelineView* graphicsView = new TimelineView(this);
    setCentralWidget(graphicsView);
}

MainWindow::~MainWindow()
{
    delete ui;
}

时间线视图.cpp

// qt
#include <QHeaderView>

// local
#include "timelineview.h"

TimelineView::TimelineView(QWidget* parent) :
    QGraphicsView(parent),
    m_scene{new TimelineScene}
{
    setScene(m_scene);

    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

    m_table = new QTableView;

    // settings for table view
    m_table->setSelectionMode(QAbstractItemView::NoSelection);
    m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
    m_table->verticalHeader()->hide();
    m_table->horizontalHeader()->setHighlightSections(false);

    m_tableModel = new QStandardItemModel(10, 100, m_table);
    m_table->setModel(m_tableModel);

    m_scene->addWidget(m_table);

    setMouseTracking(true);
}


void TimelineView::resizeEvent(QResizeEvent* event)
{
    m_scene->setSceneRect(0, 0, width(), height());
    m_table->setGeometry(m_scene->sceneRect().toRect());
    fitInView(m_scene->sceneRect(), Qt::KeepAspectRatioByExpanding);
    QGraphicsView::resizeEvent(event);
}

时间线视图.h

#ifndef TIMELINEVIEW_H
#define TIMELINEVIEW_H

// qt
#include <QGraphicsView>
#include <QTableView>
#include <QStandardItemModel>

// local
#include "timelinescene.h"

class TimelineView : public QGraphicsView
{
    public:

        explicit TimelineView(QWidget* parent = nullptr);

    protected:

        virtual void resizeEvent(QResizeEvent* event) override;

    private:

        QTableView* m_table;
        QStandardItemModel* m_tableModel;
        TimelineScene* m_scene;
};

#endif // TIMELINEVIEW_H

TimelineScene.cpp

#include "timelinescene.h"

TimelineScene::TimelineScene(QObject* parent) :
    QGraphicsScene(parent)
    , m_lineItem{nullptr}
    , m_isPressed{false}
{

}



void TimelineScene::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
    if(event->button() == Qt::LeftButton)
    {
        m_origPoint = event->scenePos();
        m_isPressed = true;
    }

    QGraphicsScene::mousePressEvent(event);
}



void TimelineScene::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
    if(m_isPressed)
    {
        if(m_lineItem == nullptr)
        {
            // create a pen for the line to be drawn
            QPen pen;
            pen.setStyle(Qt::SolidLine);
            pen.setBrush(QColor(255, 102, 0));
            pen.setWidth(8);

            m_lineItem = new QGraphicsLineItem(m_origPoint.x(), m_origPoint.y(), event->scenePos().x(), event->scenePos().y());

            // set the pen
            m_lineItem->setPen(pen);

            // add the item to the scene
            addItem(m_lineItem);
        }

        m_lineItem->setLine(m_origPoint.x(), m_origPoint.y(), event->scenePos().x(), m_origPoint.y());

        update();
    }

    QGraphicsScene::mouseMoveEvent(event);
}



void TimelineScene::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
    m_lineItem = nullptr;
    m_isPressed = false;

    QGraphicsScene::mouseReleaseEvent(event);
}

TimelineScene.h

#ifndef TIMELINESCENE_H
#define TIMELINESCENE_H

// qt
#include <QGraphicsScene>
#include <QObject>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsLineItem>
#include <QPointF>



class TimelineScene : public QGraphicsScene
{
    public:

        explicit TimelineScene(QObject* parent = nullptr);

    protected:

        virtual void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
        virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
        virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;

    private:

        QGraphicsLineItem* m_lineItem;
        QPointF m_origPoint;
        bool m_isPressed;
};

#endif // TIMELINESCENE_H

问题:

  • 我无法获取单元格的索引,即用 QModelIndex 表示和结束行的点

  • 我什至可以在QHeaderViewQScrollbar 上画线。我知道这样做的原因是因为整个画布是一个场景,我可以在任何地方绘制。但我想将绘图限制在表格的单元格中

我知道如果我能找到一种方法来检索有关底层QTableView 的信息但我不确定如何处理所有这些问题。

【问题讨论】:

  • 您是否考虑过这样做?没有图形视图,但有一个自定义项目委托,它将在单元格内绘制线条的各个部分?在此处查看该概念的基本示例:doc.qt.io/qt-5/qtwidgets-itemviews-stardelegate-example.html
  • 是否可以通过 LMB+Drag 使用项目代表来绘制线条?很抱歉,我以前从未使用过样式化的项目代表。不过我会研究这个例子。
  • 您必须通过在代表中重载 mousepressevent、mousereleaseevent、mousemoveevent 来模拟该行为;同时也使其他单元格更新。它可能会再次变得丑陋。
  • @ypnos 是的,你是对的!但将来我计划为线条添加水平调整大小、选择和删除功能。您认为项目代表仍然是一个不错的计划吗?
  • 这是一个棘手的问题。你会遇到表格改变屏幕布局的情况吗?通过调整列宽或由于自动换行而增加行?然后你必须相应地手动移动线条,我假设。如果表格是相当静态且可预测的,则图形项方法可能会少用。

标签: c++ qt qgraphicsview


【解决方案1】:

除了我们在 cmets 中关于代表的讨论,回答您的两个问题:

1。从场景坐标中查找模型索引

2。限制物品移动

您可以覆盖QGraphicsItem::itemChange() 以操纵项目的任何移动,即使在用户拖动时也是如此。它看起来像这样:

QVariant QGraphicsItem::itemChange(GraphicsItemChange change,
                                              const QVariant &value)
{
    if (change == ItemPositionChange) {
        // restr.Position() returns a QPointF limited by the allowed are
        return restrictPosition(value.toPointF());
    }
    …
}

【讨论】:

    【解决方案2】:

    如前所述,使用委托是正确的做法。

    如果由于某种原因无法实现,我强烈建议您不要以这种方式将模型视图与图形场景混合。

    如果绝对不可能使用委托,我会考虑在QTableView 上画线——它有通过像素坐标访问模型索引的 API,它确实知道它的滚动位置,并且它确实有处理所有必要的用户输入。因此,您所要做的就是将QLine 的集合存储在派生自QTableView 的类中,并通过QPainter 绘制适合当前视口的线条。 恐怕即使@9​​87654329@ 的“覆盖”现在看起来是最简单的解决方案,但实际上它存在大量与用户输入处理/路由相关的问题。而且它绝对不是 Qt 方式。

    UPD:基于委托的示例:

    这个想法很简单:

    • 整行是段的集合——每个单元格是一个 段;
    • 对于每个单元格,将线 startend x 坐标映射到 范围 [0.0;1.0];
    • 将映射坐标存储在模型本身中。

    然后您可以按如下方式绘制自定义委托:

    void LineDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
    {
        QStyledItemDelegate::paint(painter, option, index);
    
        const QVariant &lineData = index.data(Line::DataRole);
        if (lineData.isValid() && lineData.canConvert<Line>()) {
            const Line &line = lineData.value<Line>();
            const QLineF &lineF = line.toQLine(option.rect);
            painter->save();
    
            painter->setPen(m_linePen);
            painter->drawLine(lineF);
    
            painter->restore();
        }
    }
    

    鼠标处理有点棘手:

    bool LineDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
                                   const QModelIndex &index)
    {
        switch (event->type()) {
        case QEvent::MouseButtonPress:
            handleMousePress(static_cast<QMouseEvent *>(event), model, option, index);
            break;
        case QEvent::MouseMove:
            handleMouseMove(static_cast<QMouseEvent *>(event), model, option, index);
            break;
        case QEvent::MouseButtonRelease:
            handleMouseRelease(static_cast<QMouseEvent *>(event), model, option, index);
            break;
        default:
            break;
        }
    
        return QStyledItemDelegate::editorEvent(event, model, option, index);
    }
    
    void LineDelegate::handleMousePress(QMouseEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
                                        const QModelIndex &index)
    {
        Q_UNUSED(event);
        Q_UNUSED(model);
    
        if (index.isValid()) {
            m_startIndex = index;
            m_startPoint = { event->x() - option.rect.x(), option.rect.center().y() };
        }
    }
    
    void LineDelegate::handleMouseMove(QMouseEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
                                       const QModelIndex &index)
    {
        const QPoint endPoint = { event->x() - option.rect.x(), option.rect.center().y() };
        if (m_startIndex != index) {
            m_startIndex = index;
            m_startPoint = endPoint;
        }
    
        const Line &line = Line::fromQLine({ m_startPoint, endPoint }, option.rect);
    
        model->setData(index, QVariant::fromValue(line), Line::DataRole);
    }
    
    void LineDelegate::handleMouseRelease(QMouseEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
                                          const QModelIndex &index)
    {
        Q_UNUSED(event);
        Q_UNUSED(model);
        Q_UNUSED(option);
        Q_UNUSED(index);
    
        m_startIndex = QModelIndex();
        m_startPoint = { -1, -1 };
    }
    

    这是它的样子:

    以上示例的完整代码可在github获取

    请注意——这只是一个简短的原型。不支持鼠标从右向左移动,也不限制仅当前行等。但我希望这应该足以让您开始。

    【讨论】:

    • 如果真的可以调整画线的大小、选择和删除,我会选择代理方式。如果您认为这是可能的,能否请您发布一个小的 sn-p 我如何使用画家进行绘制,然后可以访问有关绘制线的信息?
    • 这是一款制作精良的产品。我现在开始了解项目代表。我测试了代码,我意识到如果我使用这种方法,会有很多事情很难处理。例如,移动鼠标太快,线条变得不稳定,来回移动鼠标会弄乱线条,重新访问绘制的线条并处理它们上的鼠标事件等等。我觉得使用QGraphicsView 方法可以更好地控制订单项。
    • 我没有实现很多东西,因为我不知道要求,或者因为它只是一个 PoC ?当然,我不是坚持使用代表的地方。如果您更喜欢使用场景,我只能添加到@ypnos 答案:为了避免在标题/滚动条上绘图,您可以在 TimelineScene::mouseMoveEvent 中限制鼠标坐标,请参阅 QTableView::horizo​​ntal/verticalHeader() 和 QAbstractScrollArea水平/垂直滚动条()和视口()。但是,如果您对代表没有足够的经验 - 请随时与我联系以寻求帮助 ?
    • 我知道我的问题在这里仍然不完整。那是因为我不知道会有这么多并发症。我创建了一个类似的线程forum.qt.io/topic/113795/… 来获得一些建议。可能会让您对我的要求有所了解。再次感谢您的时间和帮助。如果我被卡住了,我肯定会联系你。也许是电子邮件之类的?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多