【问题标题】:QGraphicsScene/View Scale UnderstandingQGraphicsScene/View 比例理解
【发布时间】:2017-04-02 11:32:19
【问题描述】:

我对 QGraphicsScene/View 的缩放值一无所知。

这是我在场景中放置目标的方式。

QPointF Mainwindow::pointLocation(double bearing, double range){
    int offset = 90; //used to offset Cartesian system
    double centerX = baseSceneSize/2;//push my center location out to halfway point
    double centerY = baseSceneSize/2;
    double newX = centerX + qCos(qDegreesToRadians(bearing - offset)) * range;
    double newY = centerY + qSin(qDegreesToRadians(bearing - offset)) * range;
    QPointF newPoint = QPointF(newX, newY);
    return newPoint;

}

所以每个目标都有一个方位和范围。只要我不缩放或缩放场景,这些值就可以充分发挥作用。我的问题是我需要实现缩放。

这里出了问题:

我的目标是方位角 270,范围 10。

当应用程序运行时,我的垂直滑块的值为零,我可以在我的视图中看到这个目标。我不应该。我只需要在滑块达到 10 的值时才能看到这个目标。试想滑块上的每个位置值都等于 1 海里。因此,如果目标为 10 NM,则只有在滑块 >= 10 时才能看到它。

这是我进行缩放的方式:

void MainWindow:: on_PlotSlider_sliderMoved(int position){
    const qreal factor = 1.01;
    viewScaleValue = qPow(factor, -position);//-position to invert the scale
    QMatrix matrix;
    matrix.scale(viewScaleValue, viewScaleValue);
    view->setMatrix(matrix);
}

我尝试让 View 更大,Scene 更大,但没有任何效果。

这是我的场景设置:

view = ui->GraphicsView;
scene = new QGraphicsScene(this);
int baseSize = 355;
scene->setSceneRect(0,0,baseSize,baseSize);
baseSceneSize = scene->sceneRect().width();
view->setScene(scene);

如何获取目标的范围并将其推到场景中,使其与滑块值对齐?

【问题讨论】:

  • 我认为“基本”场景大小的想法是不必要的。使用任何有意义的单位,例如海里,作为您的场景单位,并假设您的船只的位置位于坐标系的原点。然后将目标放置在QPoint(range*cos(bearing), range*sin(bearing) 的坐标处。缩放选择场景和视图之间的映射,以使给定的英里数适合视图。场景会处理剩下的事情。
  • 基本场景大小是为了确保在偏移范围和方位转换之前将对象放置在视图的中心。另外,它是一个测试元素,用于尝试查看会改变场景单位测量的因素。如何“使用任何单位作为我的场景单位”?
  • 澄清一下,355 baseSize 来自我的 UI 中 QGraphicsView 的实际尺寸。如果我不 centerX = baseSceneSize/2;然后根据视图的左上角而不是中心创建项目。
  • 场景和视图是完全不同的野兽。当您向其中添加项目时,应将场景设计为方便您。对于船舶雷达样式的图像,我认为 0,0 的船舶最容易,单位是海里。要在视图上显示 10NM 的范围,请将场景在视图中居中,并在 min(viewHeight/20.0, viewWidth/20.0) 处缩放视图/场景,20.0 是 10NM 左/右/上/下。所有这一切都非常非常简单——否则你会让事情复杂化。
  • 我知道我的事情可能过于复杂,因此为什么我从“我迷路了”开始我明白“(viewHeight / 20.0,viewWidth / 20.0)”如何有意义,但我不明白如何实现它我所拥有的。

标签: c++ qt


【解决方案1】:

所以正如 Kuba 建议的那样,我有点过于复杂了。在他的帮助下,这最终让我得到了我需要的结果。不是 100% 确定其中一些,但现在它正在按照我需要的方式工作。

view = ui->GraphicsView;
scene = new QGraphicsScene(this);
int baseSize = 1000; // MAGIC value that works, anything other than this, not so much
view->setSceneRect(0,0,baseSize,baseSize);
baseViewSize = view->sceneRect().width();
view->setScene(scene);

我的 drawPoint 方法运行良好,无需更改。

最后,这是我的滑块

void MainWindow:: on_PlotSlider_sliderMoved(int position){
    const qreal factor = 1.01;
    viewScaleValue = qPow(factor, -position);//-position to invert the scale
    QMatrix matrix;
     // below is the update, again 6 is a MAGIC number, no clue why 6 works...
    matrix.scale((baseViewSize/6 / position, baseViewSize/6 / position);
    view->setMatrix(matrix);

}

虽然我的问题解决了,但我希望能对我的 2 个 MAGIC 数字进行一些解释。

为什么只有 baseSize 为 1000 才有效?

为什么只有将 BaseViewSize 除以 6 才能正确缩放?

【讨论】:

  • 很难说它是如何工作的,因为我们不知道位置是什么,你在场景中使用了什么单位等等。你的代码的其他部分丢失了。设置场景矩形是没有意义的——场景已经完成并且正确地完成了。假设您想在场景坐标中适应原点周围的 10NM 范围 - 这是一个 20NM 宽/高的区域,从 -10NM,-10NM 开始。您需要做的就是致电view->fitInView(-10.,-10.,20.,20.)。而已。该视图负责确定要设置的缩放比例。当然,每次视图更改大小时都需要调用它。
【解决方案2】:

QGraphicsView::fitInView 是您选择显示范围并使视图居中所需的一切。

您可以这样做。这是一个完整的例子。

// https://github.com/KubaO/stackoverflown/tree/master/questions/scene-radar-40680065
#include <QtWidgets>
#include <random>

首先,让我们获取随机目标位置。场景按比例缩放,例如海里:因此场景中的任何坐标都应采用这些单位。这只是一个约定:否则场景不关心,视图也不关心。参考点位于 0,0:所有范围/方位均相对于原点。

QPointF randomPosition() {
    static std::random_device dev;
    static std::default_random_engine eng(dev());
    static std::uniform_real_distribution<double> posDis(-100., 100.); // NM
    return {posDis(eng), posDis(eng)};
}

然后,为了帮助打开和关闭场景项目组(例如格线),为它们设置一个空的父项目会有所帮助:

class EmptyItem : public QGraphicsItem {
public:
    QRectF boundingRect() const override { return QRectF(); }
    void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override {}
};

场景管理器设置显示。空项目充当项目集合,它们可以很容易地隐藏/可见,而无需修改子项目。他们还强制执行子代的相对 Z 顺序。

class SceneManager : public QObject {
    Q_OBJECT
    Q_PROPERTY(bool microGraticuleVisible READ microGraticuleVisible WRITE setMicroGraticuleVisible)
    QGraphicsScene m_scene;
    QPen m_targetPen{Qt::green, 1};
    EmptyItem m_target, m_center, m_macroGraticule, m_microGraticule;

可以在视图上安装一个事件过滤器,以便在调整视图大小时发出信号。这可用于在调整大小的情况下保持视图居中:

    bool eventFilter(QObject *watched, QEvent *event) override {
        if (event->type() == QEvent::Resize 
                && qobject_cast<QGraphicsView*>(watched))
            emit viewResized();
        return QObject::eventFilter(watched, event);
    }

场景具有以下 Z 顺序:中心十字、宏观和微观格线,然后目标在顶部。

public:
    SceneManager() {
        m_scene.addItem(&m_center);
        m_scene.addItem(&m_macroGraticule);
        m_scene.addItem(&m_microGraticule);
        m_scene.addItem(&m_target);
        m_targetPen.setCosmetic(true);
        addGraticules();
    }

我们可以监视图形视图以调整大小;我们还暴露了微标线的可见性。

    void monitor(QGraphicsView *view) { view->installEventFilter(this); }
    QGraphicsScene * scene() { return &m_scene; }
    Q_SLOT void setMicroGraticuleVisible(bool vis) { m_microGraticule.setVisible(vis); }
    bool microGraticuleVisible() const { return m_microGraticule.isVisible(); }
    Q_SIGNAL void viewResized();

目标可以随机生成。目标在视图坐标中具有固定大小。但是,它的位置会受到任何场景到视图的转换。

目标和标线的笔是装饰笔:它们的宽度以视图设备单位(像素)而不是场景单位给出。

    void newTargets(int count = 200) {
        qDeleteAll(m_target.childItems());
        for (int i = 0; i < count; ++i) {
            auto target = new QGraphicsEllipseItem(-1.5, -1.5, 3., 3., &m_target);
            target->setPos(randomPosition());
            target->setPen(m_targetPen);
            target->setBrush(m_targetPen.color());
            target->setFlags(QGraphicsItem::ItemIgnoresTransformations);
        }
    }

经纬网是以原点(范围参考点)为中心的同心圆和原点处的十字。原点十字在视图单位中具有固定大小 - 这由 ItemIgnoresTransformations 标志指示。

   void addGraticules() {
        QPen pen{Qt::white, 1};
        pen.setCosmetic(true);
        auto center = {QLineF{-5.,0.,5.,0.}, QLineF{0.,-5.,0.,5.}};
        for (auto l : center) {
            auto c = new QGraphicsLineItem{l, &m_center};
            c->setFlags(QGraphicsItem::ItemIgnoresTransformations);
            c->setPen(pen);
        }
        for (auto range = 10.; range < 101.; range += 10.) {
            auto circle = new QGraphicsEllipseItem(0.-range, 0.-range, 2.*range, 2.*range, &m_macroGraticule);
            circle->setPen(pen);
        }
        pen = QPen{Qt::white, 1, Qt::DashLine};
        pen.setCosmetic(true);
        for (auto range = 2.5; range < 9.9; range += 2.5) {
            auto circle = new QGraphicsEllipseItem(0.-range, 0.-range, 2.*range, 2.*range, &m_microGraticule);
            circle->setPen(pen);
        }
    }
};

场景单元和视图之间的映射保持如下:

  1. 每次更改视图范围(例如从组合框)时,都会调用 QGraphicsView::fitInView 方法,并以场景单位(海里)为单位使用矩形。这会处理所有缩放、居中等。例如。要选择 10NM 的范围,我们会调用 view.fitInView(QRect{-10.,-10.,20.,20.), Qt::KeepAspectRatio)

  2. 可以根据给定范围禁用/启用标线以使视图整洁。

    int main(int argc, char ** argv) {
        QApplication app{argc, argv};
        SceneManager mgr;
        mgr.newTargets();
    
        QWidget w;
        QGridLayout layout{&w};
        QGraphicsView view;
        QComboBox combo;
        QPushButton newTargets{"New Targets"};
        layout.addWidget(&view, 0, 0, 1, 2);
        layout.addWidget(&combo, 1, 0);
        layout.addWidget(&newTargets, 1, 1);
    
        view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        view.setBackgroundBrush(Qt::black);
        view.setScene(mgr.scene());
        view.setRenderHint(QPainter::Antialiasing);
        mgr.monitor(&view);
    
        combo.addItems({"10", "25", "50", "100"});
        auto const recenterView = [&]{
            auto range = combo.currentText().toDouble();
            view.fitInView(-range, -range, 2.*range, 2.*range, Qt::KeepAspectRatio);
            mgr.setMicroGraticuleVisible(range <= 20.);
        };
        QObject::connect(&combo, &QComboBox::currentTextChanged, recenterView);
        QObject::connect(&mgr, &SceneManager::viewResized, recenterView);
        QObject::connect(&newTargets, &QPushButton::clicked, [&]{ mgr.newTargets(); });
        w.show();
        return app.exec();
    }
    
    #include "main.moc"
    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多