Илья
ИльяMarch 14, 2017, 8:15 p.m.

Проблема при перерисовке сцены - "скакание" элементов сцены по вертикали

QGraphicsView, QGraphicsScene, QWidget

Здравствуйте. Использую Qt 5.6.1.
В общем, имеется клиентское приложение, которое связывается с сервером, получает от него данные - координаты узлов сети системы управления и данные с них, и отрисовывает местоположение узлов на QGraphicsView. На сцене рисую координатную сетку, которое можно "зуммировать" колесиком мыши, "скролить" правой кнопкой мыши. Возникает интересная вещь - при скроллинге по горизонтали все отлично, при скроллинге по вертикали начинают скакать горизонтальные линии координатной сетки, подписи к оси Х. Такое ощущение, что выключена какая нибудь "вертикальная привязка" или еще что-то в этом роде. Ничего похожего в интернете не нашел.
Выкладываю 2 класса - главного окна (myclient.h, myclient.cpp) и виджета, на котором происходит рисование (mygraphicview.cpp, mygraphicview.h).
Также для лицезрения сего чуда прикрепляю папку с проектом - сначала сделайте Zoom колесиком мыши и попробуйте правой кнопкой мыши поскроллить. Увидите, что горизонтальная линия зеленого цвета начнем прыгать то в одну сторону, то в другую (в зависимости от направления скролла).
p.s. При компиляции укажите путь к БД (database.cpp).

myclient.h

#ifndef MYCLIENT_H
#define MYCLIENT_H

#include <QMainWindow>
#include <QTcpSocket>
#include <QGraphicsScene>
#include <QLabel>
#include <QStatusBar>
#include <database.h>
#include <stdlib.h>
#include <QSqlTableModel>
#include <QMessageBox>
#include <mypoint.h>
#include "mygraphicview.h"
#include <QKeyEvent>
#include <QProgressBar>

namespace Ui {
class MyClient;
}
class MyClient : public QMainWindow
{
    Q_OBJECT

public:
    explicit MyClient(QWidget *parent = 0);
    ~MyClient();
    Ui::MyClient *ui;

private:
    QProgressBar *pBar;
    MyPoint *point;
    QTcpSocket* pTcpSocket;             // Объявляем сокет
    bool Connect = false;               // Объявляем переменную, отвечающую за наличие соединения
    QGraphicsScene  *scene;             // Объявляем графическую сцену
    MyGraphicView   *myPicture;         // Наш кастомный виджет
    DataBase        *db;                // Объявляем переменную для доступа к БД
    QSqlTableModel  *model;             // Объект для взаимодействия с моделью представления таблицы БД
    // Объявяем метод для парсинга входящего сообщения
    void parsingRecMsg(const QString &RecMsg);
    // Метод для формирования модели представления таблицы БД
    void setupModel(const QString &tableName, const QStringList &headers);
    // Метод для отрисовки медели представления таблицы БД
    void createModel();
    //QLabel *mouseTracker;

protected:
    //void mouseMoveEvent(QMouseEvent* pe);
    QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const;

signals:
    void signalRepaint();

private slots:
    void slotReadyRead();
    void slotError(QAbstractSocket::SocketError);
    void slotConnected();
    void slotDisconnected();
    void on_Button_Connect_clicked();
    void on_Button_Send_clicked();


    void on_Button_AddNode_clicked();

public slots:
    void slotSelectRow(int idRow);
};

#endif // MYCLIENT_H

myclient.cpp
#include "myclient.h"
#include "ui_myclient.h"

// Конструктор:
MyClient::MyClient(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MyClient)
{
    qDebug()<<"startMakeUI";
    ui->setupUi(this);
    this->resize(732,694);                                                  // Задаем размеры окна
    this->setFixedSize(732,694);                                            // Фиксируем размеры окна
    ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);   // Отключаем скроллбар по вертикали
    ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Отключаем скроллбар по горизонтали
    this->setWindowTitle("Monitoring control system");
    // Подключаемся к БД:
    db = new DataBase();
    db->connectToDataBase();
    //this->setMouseTracking(true);
    // Инициализируем виджет с графикой
    myPicture = new MyGraphicView();
    // и добавляем его на слой
    scene = new QGraphicsScene();                                           // Инициализируем графическую сцену
    ui->graphicsView->setScene(scene);                                      // Устанавливаем графическую сцену в graphicsView
    scene->addWidget(myPicture);
    /*/ Для отображения координат указателя мыши в StatusBar
    mouseTracker = new QLabel(this);
    statusBar()->addWidget(mouseTracker);*/
    connect(this,&MyClient::signalRepaint, myPicture, &MyGraphicView::slotAlarmTimer);
    connect(myPicture,&MyGraphicView::signalSelectRow,this,&MyClient::slotSelectRow);
    // Инициализируем модель для представления данных с заданием названий колонок
    this->setupModel(TABLE, QStringList() << trUtf8("NodeID")
                                          << trUtf8("Х")
                                          << trUtf8("Y")
                                          << trUtf8("Z")
                                          << trUtf8("Data 1")
                                          << trUtf8("Data 2")
                                          << trUtf8("Data 3")
                                          << trUtf8("State"));
    this->createModel();
}

// Диструктор:
MyClient::~MyClient()
{
    qDebug()<<"startDeleteUI";
    delete ui;
}

// Обработчик нажатия кнопки Connect:
void MyClient::on_Button_Connect_clicked()
{
    qDebug()<<"on_Button_Connect_clicked()";
    if (Connect==false){ // если соединения нет
        qDebug()<<"on_Button_Connect_clicked(): connecting...";
        bool PortNumber_ok;
        const QString IP = this->ui->LEdit_IPAdress->text();
        const ushort port = this->ui->LEdit_PortNumber->text().toUShort(&PortNumber_ok);
        if (!PortNumber_ok)
        {
            QMessageBox msgBox;
            msgBox.setText("The port is not valid.");
            msgBox.exec();
            return;
        }
        pTcpSocket = new QTcpSocket(this);
        pTcpSocket->connectToHost(IP, port);
        connect(pTcpSocket, SIGNAL(connected()), SLOT(slotConnected()));
        connect(pTcpSocket, SIGNAL(disconnected()), SLOT(slotDisconnected()));
        connect(pTcpSocket, SIGNAL(readyRead()), SLOT(slotReadyRead()));
        connect(pTcpSocket, SIGNAL(error(QAbstractSocket::SocketError)),
                this,         SLOT(slotError(QAbstractSocket::SocketError))
               );
    }
    else { // если соединения есть
        qDebug()<<"on_Button_Connect_clicked(): disconnecting...";
        pTcpSocket->disconnectFromHost();
    }
}

// Слот успешного соединения клиента с сервером:
void MyClient::slotConnected()
{
    qDebug()<<"slotConnected(): connected";
    Connect=true;
    ui->TEdit_RMsg->append("State: Connected");
    ui->LEdit_IPAdress->setEnabled(false);
    ui->LEdit_PortNumber->setEnabled(false);
    ui->Button_Connect->setText("Disconnect");
    ui->Button_Send->setEnabled(true);
}

// Слот отсоединения клиента с сервером:
void MyClient::slotDisconnected()
{
    qDebug()<<"slotDisconnected(): disconnected";
    Connect = false;
    ui->TEdit_RMsg->append("State: Disconnected");
    ui->LEdit_IPAdress->setEnabled(true);
    ui->LEdit_PortNumber->setEnabled(true);
    ui->Button_Connect->setText("Connect");
    ui->Button_Send->setEnabled(false);
}

// Слот приема данных от сервера:
void MyClient::slotReadyRead()
{
    qDebug()<<"slotReadyRead(): StartReceiveData";
    QString line = "";
    // Определяем размер входящего пакета:
    int bytesAvail = pTcpSocket->bytesAvailable();
    qDebug()<<"waitForInput(): Receive " << bytesAvail << "bytes";
    int cnt = 0;
    bool endOfStream = false;
    while (cnt < bytesAvail && (!endOfStream)) {
        char ch;
        int bytesRead = pTcpSocket->read(&ch, sizeof(ch));
        if (bytesRead == sizeof(ch)) {
            cnt++;
            line.append( ch );
        }
        else {
            endOfStream = true;
        }
    }
    ui->TEdit_RMsg->append("Receive: "+line);
    parsingRecMsg(line);

}

// Слот обработки ошибок в соединении клиента с сервером:
void MyClient::slotError(QAbstractSocket::SocketError err)
{
    qDebug()<<"slotError";
    QString strError =
        "Error: " + (err == QAbstractSocket::HostNotFoundError ?
                     "The host was not found." :
                     err == QAbstractSocket::RemoteHostClosedError ?
                     "The remote host is closed." :
                     err == QAbstractSocket::ConnectionRefusedError ?
                     "The connection was refused." :
                     QString(pTcpSocket->errorString())
                    );
    ui->TEdit_RMsg->append(strError);
}

// Обработчик нажатия кнопки Send:
void MyClient::on_Button_Send_clicked()
{
    qDebug()<<"on_Button_Send_clicked(): send...";
    QByteArray  arrBlock;
    arrBlock.append(QString(ui->LEdit_TMsg->text()));
    pTcpSocket->write(arrBlock);
    ui->TEdit_RMsg->append("Send: "+QString(arrBlock));
}

/*void MyClient::mouseMoveEvent(QMouseEvent* pe)
{
    QPoint bsc = myPicture->pos();
    int x = pe->x() - bsc.x();
    int y = pe->y() - bsc.y();
    if((x < 0)||(x > scene->width())||(y < 0)||(y > scene->height()))
    {
        mouseTracker->setText("out of scene");
        return;
    }
    mouseTracker->setText(QString::number(x) + "  " + QString::number(y));
}*/

// Метод для парсинга входящего сообщения:
void MyClient::parsingRecMsg(const QString &RecMsg){
    qDebug()<<"parsingRecMsg()";
    if (RecMsg.length()==22){
        int id = strtol(RecMsg.mid(0,2).toUtf8().data(),NULL,16);
        int x = strtol(RecMsg.mid(2,4).toUtf8().data(),NULL,16);
        int y = strtol(RecMsg.mid(6,4).toUtf8().data(),NULL,16);
        int z = strtol(RecMsg.mid(10,4).toUtf8().data(),NULL,16);
        int data1 = strtol(RecMsg.mid(14,2).toUtf8().data(),NULL,16);
        int data2 = strtol(RecMsg.mid(16,2).toUtf8().data(),NULL,16);
        int data3 = strtol(RecMsg.mid(18,2).toUtf8().data(),NULL,16);
        int state = strtol(RecMsg.mid(20,2).toUtf8().data(),NULL,16);
        QString state_s;
        if(state == 1) state_s = "work_ON";
        else state_s = "work_OFF";
        QVariantList data;
        data.append(id);
        data.append(x);
        data.append(y);
        data.append(z);
        data.append(data1);
        data.append(data2);
        data.append(data3);
        data.append(state_s);
        QSqlQuery query;
        query.prepare("SELECT * FROM TableSystNode WHERE NodeID=:NodeID");
        query.bindValue(":NodeID", id);
        query.exec();
        if(query.next()==false){
            // Вставляем данные в БД
            db->inserIntoTable(data);
            model->select();
            emit signalRepaint();
        }
        else{
            // Изменяем данные в БД
            db->updateTable(data);
            model->select();
            emit signalRepaint();
        }
    }
    else{
        qDebug()<<"parsingRecMsg(): bad RecMsg: "<<RecMsg;
    }

}

// Метод для инициализации модели представления таблицы БД:
void MyClient::setupModel(const QString &tableName, const QStringList &headers)
{
    /* Производим инициализацию модели представления данных
     * с установкой имени таблицы в базе данных, по которому
     * будет производится обращение в таблице
     * */
    model = new QSqlTableModel(this);
    model->setTable(tableName);
    // Устанавливаем названия колонок в таблице с сортировкой данных
    for(int i = 0, j = 0; i < model->columnCount(); i++, j++){
        model->setHeaderData(i,Qt::Horizontal,headers[j]);

    }
    // Устанавливаем сортировку по возрастанию данных по нулевой колонке
    model->setSort(0,Qt::AscendingOrder);

}

// Метод для отрисовки модели представления таблицы БД:
void MyClient::createModel()
{
    ui->tableView->setModel(model);             // Устанавливаем модель на TableView
    //ui->tableView->setColumnHidden(0, true);    // Скрываем колонку с id записей
    ui->tableView->verticalHeader()->hide();

    // Разрешаем выделение строк
    ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
    // Устанавливаем режим выделения лишь одно строки в таблице
    ui->tableView->setSelectionMode(QAbstractItemView::SingleSelection);
    // Устанавливаем размер колонок по содержимому
    ui->tableView->resizeColumnsToContents();
    ui->tableView->setColumnWidth(1,50);
    ui->tableView->setColumnWidth(2,50);
    ui->tableView->setColumnWidth(3,50);
    ui->tableView->setColumnWidth(4,50);
    ui->tableView->setColumnWidth(5,50);
    ui->tableView->setColumnWidth(6,50);
    ui->tableView->setUpdatesEnabled(true);
    ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);

    ui->tableView->horizontalHeader()->setStretchLastSection(true);
    model->select(); // Делаем выборку данных из таблицы
}

// Слот для выделения строки в таблице БД:
void MyClient::slotSelectRow(int idRow)
{
    // Выделяем нужную строку
    ui->tableView->selectRow(idRow);
    // Имитируем нажатие кнопки Tab, чтобы выделить строку
    QKeyEvent* pe = new QKeyEvent(QEvent::KeyPress,
            Qt::Key_Tab,Qt::NoModifier, "Tab");
    QApplication::sendEvent(this, pe);
}


void MyClient::on_Button_AddNode_clicked()
{
    pBar = new QProgressBar();
    pBar->setRange(0,0);
    pBar->setTextVisible(true);
    pBar->setFormat("Loading...");
    pBar->setAlignment(Qt::AlignCenter);
    pBar->show();
}


mygraphicview.h
#ifndef MYGRAPHICVIEW_H
#define MYGRAPHICVIEW_H

#include <QWidget>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QWheelEvent>
#include <qmath.h>
#include <math.h>
#include <QDebug>
#include <QRect>
#include <QScrollBar>
#include <QApplication>
#include <mypoint.h>
#include <QMessageBox>
#include <QtSql>
#include <database.h>
#include <QObject>

class MyGraphicView : public QGraphicsView
{
    Q_OBJECT
public:
    explicit MyGraphicView(QWidget *parent = 0);
       ~MyGraphicView();
    double CurZoom=1.0;
    bool FirstStart = true;

private:
    QGraphicsScene      *scene;     // Объявляем сцену для отрисовки
    QGraphicsItemGroup  *group_1;   // Объявляем первую группу элементов
    QGraphicsItemGroup  *group_2;   // Объявляем вторую группу элементов
    MyPoint *point;                 // Объявляем переменную для создания объектов на сцене
    DataBase *db;                   // Объявляем переменную для доступа к БД
    double minX = -250.0;              // Минимальное значение по оси Х
    double maxX = 250.0;            // Максимальное значение по оси Х
    int numXTicks = 10;             // Количество рисок по оси Х
    double minY = -250.0;              // Минимальное значение по оси Y
    double maxY = 250;              // Максимальное значение по оси Y
    int numYTicks = 10;             // Количество рисок по оси Y
    double spanX() const { return fabs(maxX - minX); }  // Разница по между макс и мин X
    double spanY() const { return fabs(maxY - minY); }  // Разница по между макс и мин Y
    double currentX;                // Текущее значение Х
    double currentY;                // Текущее значение Y
    bool StartDraw = false;         // Переменная для разрешения перерисовки сцены
    double pXmin;                   // Временная переменная для хранение мин значения оси Х
    double pYmax;                   // Временная переменная для хранение мин значения оси Y
    void clearScene();              // Метод для удаления всех элементов со сцены
    void adjustAxis(double &min, double &max, int &numTicks);// Метод для выбора оптимальных значений осей и рисок сцены

protected:
    void wheelEvent(QWheelEvent* event);
    void mouseMoveEvent(QMouseEvent *event);
    void mousePressEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);

private slots:
    void slotFromPoint(int numberPoint, int pX, int pY);       // Слот для обработки сигнала из точки

public slots:
    void slotAlarmTimer();      // Cлот для обработчика переполнения таймера (в нем происходит рисование сцены)
    //void slotSelectedItem();

signals:
    void signalSelectRow(int numRow);
};

#endif // MYGRAPHICVIEW_H

mygraphicview.cpp
#include "mygraphicview.h"

MyGraphicView::MyGraphicView(QWidget *parent)
    : QGraphicsView(parent)
{
    qDebug()<<"MyGraphicView(): start";
    // Настраиваем отображение виджета и его содержимого:
    this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);             // Отключим скроллбар по горизонтали
    this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);               // Отключим скроллбар по вертикали
    this->setAlignment(Qt::AlignCenter);                                    // Делаем привязку содержимого к центру
    this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);    // Растягиваем содержимое по виджету
    this->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);         // В качестве якоря используется точка под курсором мышки.
    this->setRenderHint(QPainter::Antialiasing);                            // Устанавливаем сглаживание
    //this->setFocusPolicy(Qt::StrongFocus);
    scene = new QGraphicsScene();                                           // Инициализируем сцену для отрисовки
    scene->setItemIndexMethod(QGraphicsScene::NoIndex);                     // Линейный поиск элементов
    this->setScene(scene);                                                  // Устанавливаем сцену в виджет
    slotAlarmTimer();


    //timer = new QTimer();                   // Инициализируем Таймер
    // Подключаем СЛОТ для отрисовки к таймеру
    //connect(timer, SIGNAL(timeout()), this, SLOT(slotAlarmTimer()));
    //timer->start(50);                   // Стартуем таймер на 50 миллисекунд
    FirstStart = false;
}

MyGraphicView::~MyGraphicView()
{

}

// Метод отвечающий за перерисовку сцены
void MyGraphicView::slotAlarmTimer()
{
    // Запрещаем перерисование сцены
    StartDraw = true;
    qDebug()<<"slotAlarmTimer(): start";
    /* Удаляем все элементы со сцены,
     * если они есть перед новой отрисовкой
     * */
    clearScene();
    /* Устанавливаем размер сцены по размеру виджета
     * Первая координата - это левый верхний угол,
     * а Вторая - это правый нижний угол
     * */
    scene->setSceneRect(-250,-250,500,500);
    // Создаем кисти для рисования на сцене
    QPen penGreen(Qt::green);       // Задаём чёрную кисть
    QPen penRed(Qt::red);           // Задаём красную кисть
    QPen penGray(Qt::lightGray);         // Задаем серую кисть
    QPen penDarkGray(Qt::green); // Задаем темно-серую кисть
    penGray.setCosmetic(true);
    penDarkGray.setCosmetic(true);
    // Получаем текущее значение минимальных значений осей координат
    QPolygon pView = mapFromScene(QRect(0, 0, this->viewport()->width(), this->viewport()->height()));
    if (CurZoom < 2.0) {
        pXmin = 0;
        pYmax = 0;
    }
    else
    {
        pXmin = -pView.at(0).x()/CurZoom;
        pYmax = 500 / CurZoom + minY;
    }

    // Рисуем координатную ось Х:
    QRect rect(minX,minY,spanX(),spanY());
    for (int i = 0; i <= numXTicks; ++i) {
        int x = rect.left() + (i * (rect.width() - 1)/numXTicks);
        double label = minX + (i * spanX()/numXTicks);
        if (label == 0) scene->addLine(x+1,rect.top(),x+1,rect.bottom(),penDarkGray);
        else scene->addLine(x+1,rect.top(),x+1,rect.bottom(),penGray);
        //qDebug()<<"add line X:"<<x<<" "<<rect.top()<<" "<<x<< " "<< rect.bottom();
        QGraphicsSimpleTextItem *name = NULL;
        name = scene->addSimpleText(QString::number(label));
        name->setBrush(Qt::gray);
        name->setScale(1/CurZoom);
        int kname = spanX()/numXTicks;
        name->setPos(minX+i*kname+3/CurZoom, pYmax-14/CurZoom);
    }
    // Рисуем координатную ось Y:
    for (int j = 0; j <= numYTicks; ++j) {
        int y = rect.bottom() - (j * (rect.height() - 1)/numYTicks);
        double label = -(minY + (j * spanY()/numYTicks));
        if ((unsigned int)label == 0)
        {
            scene->addLine(rect.left(), y, rect.right(), y,penDarkGray);
            //qDebug()<<"LABEL:"<<label;
        } else scene->addLine(rect.left(), y, rect.right(), y,penGray);
        //qDebug()<<"add line Y:"<<rect.left()<<" "<<y<<" "<<rect.right()<< " "<< y;
        QGraphicsSimpleTextItem *name = NULL;
        name = scene->addSimpleText(QString::number(label));
        name->setBrush(Qt::gray);
        int kname = spanY()/numYTicks;
        name->setPos(pXmin+3/CurZoom, minY+j*kname-0.7*CurZoom);
        name->setScale(1/CurZoom);
    }
    scene->addRect(rect,penGray);

    // Рисуем местоположение абонентов системы:
    QSqlQuery query;
    db->selectTable(query);
    while (query.next()) {
         int numberP = query.value(0).toInt();
         int pX = query.value(1).toInt();
         int pY = query.value(2).toInt();
         qDebug()<<"slotAlarmTimer(): new point with cord X = "<<pX<<" Y = "<<pY;
         point = new MyPoint(pX,-pY,5.0,5.0,Qt::green,numberP);
         scene->addItem(point);
         point->setPos(pX,-pY);
         // Подключаем сигнал из точки к СЛОТу в главном классе
         connect(point,&MyPoint::signal1, this, &MyGraphicView::slotFromPoint);

    }
    // Разрешаем перерисование сцены
    StartDraw = false; 
}

/* // Методом перехватываем событие изменения размера виджет
void MyGraphicView::resizeEvent(QResizeEvent *event)
{
    timer->start(50);   // Как только событие произошло стартуем таймер для отрисовки
    QGraphicsView::resizeEvent(event);  // Запускаем событие родителького класса
}
*/

// Метод для удаления всех элементов со сцены:
void MyGraphicView::clearScene()
{
    // Перебираем все элементы сцены и удаляем их
    QList<QGraphicsItem*> all = items();
    for (int i = 0; i < all.size(); i++)
    {
        QGraphicsItem *gi = all[i];
        if(gi->parentItem()==NULL) {
            delete gi;
        }
    }
}

// Метод вращения колесика мыши:
void MyGraphicView::wheelEvent(QWheelEvent *event)
{
    // Scale the view / do the zoom
    const double scaleFactor = 1.15;
    if(event->delta() > 0)
    {
        // Zoom in
        if(CurZoom < 6.0){
            CurZoom = CurZoom*scaleFactor;
            qDebug()<<"wheelEvent(): CurZoom = " << CurZoom;
            scale(scaleFactor, scaleFactor);
            centerOn(mapToScene(event->pos()));
            QPolygon pView = mapFromScene(QRect(0, 0, this->viewport()->width(), this->viewport()->height()));
            qDebug()<<pView;
            minX = -pView.at(0).x()/CurZoom;
            maxX = 500 / CurZoom + minX;
            minY = -pView.at(0).y()/CurZoom;
            maxY = 500 / CurZoom + minY;
            adjustAxis(minX, maxX, numXTicks);
            adjustAxis(minY, maxY, numYTicks);
            qDebug()<<"numXTicks = "<<numXTicks;
            qDebug()<<"numYTicks = "<<numYTicks;
            qDebug()<<"minX = "<< minX << "; minY = " << minY;
            qDebug()<<"maxX = "<< maxX << "; maxY = " << maxY;
            slotAlarmTimer();
        } else qDebug()<<"wheelEvent(): posX = "<<event->pos().x()/CurZoom<< "posY = "<<event->pos().y()/CurZoom;
    }
    else
    {
        // Zooming out
        if (CurZoom > 1.10) {
            CurZoom = CurZoom/scaleFactor;
            qDebug()<<"wheelEvent(): CurZoom = " << CurZoom;
            scale(1.0 / scaleFactor, 1.0 / scaleFactor);
            centerOn(mapToScene(event->pos()));
            QPolygon pView = mapFromScene(QRect(0, 0, this->viewport()->width(), this->viewport()->height()));
            qDebug()<<pView;
            minX = -pView.at(0).x()/CurZoom;
            maxX = 500 / CurZoom + minX;
            minY = -pView.at(0).y()/CurZoom;
            maxY = 500 / CurZoom + minY;
            adjustAxis(minX, maxX, numXTicks);
            adjustAxis(minY, maxY, numYTicks);
            qDebug()<<"numXTicks = "<<numXTicks;
            qDebug()<<"numYTicks = "<<numYTicks;
            qDebug()<<"minX = "<< minX << "; minY = " << minY;
            qDebug()<<"maxX = "<< maxX << "; maxY = " << maxY;
            slotAlarmTimer();
        } else {
            minX = -250;
            maxX = 250;
            minY = -250;
            maxY = 250;
            numXTicks = 10;
            numYTicks = 10;
            qDebug()<<"wheelEvent(): posX = "<<event->pos().x()/CurZoom<< "posY = "<<event->pos().y()/CurZoom;
            slotAlarmTimer();
        }
    }
}

// Метод для определения оптимыльных значений рисок координатных осей:
void MyGraphicView::adjustAxis(double &min, double &max, int &numTicks)
{
    const int MinTicks = 5;
    double grossStep = (max - min) / MinTicks;
    double step = pow(10, floor(log10(grossStep)));
    qDebug()<<"Step = "<<step;
    if (5 * step < grossStep) step *= 5;
    else if (4 * step < grossStep) step *= 4;
    else if (3 * step < grossStep) step *= 3;
    else if (2 * step < grossStep) step *= 2;
    numTicks = (int)(ceil(max / step) - floor(min / step));
    if(CurZoom<1.10) numTicks = 10;
    if (CurZoom > 6.0) numTicks = 10;
    min = floor(min / step) * step;
    max = ceil(max / step) * step;
}

// Метод нажатия кнопки мыши:
void MyGraphicView::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::RightButton){
        QApplication::setOverrideCursor(QCursor(Qt::ClosedHandCursor));
        //this->viewport()->setCursor(Qt::ClosedHandCursor);
        // Store original position.
        currentX = event->x();
        currentY = event->y();
        //event->accept();
        qDebug()<<"mousePressEvent(): originX = "<<currentX<<"; originY = "<<currentY;
        return;
    }
    if (event->button() == Qt::LeftButton){
        slotAlarmTimer();
    }
    //event->ignore();
}

// Метод перемещения указателя мыши:
void MyGraphicView::mouseMoveEvent(QMouseEvent *event)
{
    if (event->buttons() & Qt::RightButton) {
        this->horizontalScrollBar()->setValue(horizontalScrollBar()->value() - (event->x() - currentX));
        this->verticalScrollBar()->setValue(verticalScrollBar()->value() - (event->y() - currentY));
        currentX = event->x();
        currentY = event->y();
        qDebug()<<"mouseMoveEvent(): originX = "<<currentX<<"; originY = "<<currentY;
        event->accept();
        if(StartDraw == false && CurZoom != 1.0){
            QPolygon pView = mapFromScene(QRect(0, 0, this->viewport()->width(), this->viewport()->height()));
            qDebug()<<pView;
            minX = -pView.at(0).x()/CurZoom;
            maxX = 500 / CurZoom + minX;
            minY = -pView.at(0).y()/CurZoom;
            maxY = 500 / CurZoom + minY;
            qDebug()<<"minX = "<< minX << "; minY = " << minY;
            qDebug()<<"maxX = "<< maxX << "; maxY = " << maxY;
            adjustAxis(minX, maxX, numXTicks);
            adjustAxis(minY, maxY, numYTicks);
            qDebug()<<"numXTicks = "<<numXTicks;
            qDebug()<<"numYTicks = "<<numYTicks;
            qDebug()<<"minX = "<< minX << "; minY = " << minY;
            qDebug()<<"maxX = "<< maxX << "; maxY = " << maxY;
            slotAlarmTimer();
        }
        return;
    }
    event->ignore();
}

// Метод отпускания кнопки мыши:
void MyGraphicView::mouseReleaseEvent(QMouseEvent *event)
{
    if(event->button()==Qt::RightButton)
    {
        qDebug()<<"mouseReleaseEvent(): RightButton was released";
        QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
        event->accept();
        if(StartDraw == false && CurZoom != 1.0){
            QPolygon pView = mapFromScene(QRect(0, 0, this->viewport()->width(), this->viewport()->height()));
            qDebug()<<pView;
            minX = -pView.at(0).x()/CurZoom;
            maxX = 500 / CurZoom + minX;
            minY = -pView.at(0).y()/CurZoom;
            maxY = 500 / CurZoom + minY;
            adjustAxis(minX, maxX, numXTicks);
            adjustAxis(minY, maxY, numYTicks);
            qDebug()<<"numXTicks = "<<numXTicks;
            qDebug()<<"numYTicks = "<<numYTicks;
            qDebug()<<"minX = "<< minX << "; minY = " << minY;
            qDebug()<<"maxX = "<< maxX << "; maxY = " << maxY;
            slotAlarmTimer();
        }
    }
    event->ignore();
}

void MyGraphicView::slotFromPoint(int numberPoint, int pX, int pY)
{
    qDebug()<<"slotFromPoint(): click "<<numberPoint<<" node";
    QList<QGraphicsItem *> itemsSelected = scene->items(QPolygonF()
                                                        << QPointF(pX, pY)
                                                        << QPointF(pX, pY)
                                                        << QPointF(pX, pY)
                                                        << QPointF(pX, pY));
    foreach (QGraphicsItem *item, itemsSelected) {
        if(item->pos().x()!=0 && item->pos().y()!=0)
        {
            qDebug()<<"Item = "<<item;
        }
    }

    emit signalSelectRow(numberPoint);
}

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!

6
Илья
  • March 14, 2017, 8:18 p.m.

Извините, забыл прикрепить архив с проектом:

    Evgenii Legotckoi
    • March 15, 2017, 12:46 a.m.
    • The answer was marked as a solution.

    Добрый день.
    В Файле mygraphicsview.cpp замените строку 83:

    int y = rect.bottom() - (j * (rect.height() - 1)/numYTicks);
    на следующую строку:
    int y = rect.top() + (j * (rect.height() - 1)/numYTicks);

    Что касается кода со строки 66 по строку 98 в файле mygraphicsview.cpp . Каждый раз перерисовывать сетку не самое хорошее решение. Тем более, что перерисовывать сетку нет острой необходимости, при скролле линии будут всё равно на своём месте. А вообще почитайте про QGraphicsItemGroup . С помощью этого класса можно будет группировать графические объекты, а также удалять и перерисовывать часть объектов.

      Илья
      • March 15, 2017, 6:33 a.m.

      Спасибо большое, Евгений!!! Наконец-то теперь все работает как надо. Я так понимаю такой эффект получался из неправильно выбранной точки позиционирования объектов по вертикали. Позиционировать нужно было относительно верхней границы сцены - так как оттуда начинается счет координат. По горизонтали так все было и сделано, поэтому там все нормально работало.

      По поводу оптимизации и применения QGraphicsItemGroup - именно об этом я и думал, но чуть позже. Сейчас остро стоит вопрос о первой сырой и рабочей версии проекта. Спасибо за совет.

      Хотелось бы выяснить еще одну странность. При зуммировании колесиком мыши сцена должна переходить в место, где находиться курсор мыши, для этого я включил в конструктор виджета:

      this->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);

      и в метод wheelEvent(QWheelEvent *event) делаю центрирование в точке указателя мыши:
      void MyGraphicView::wheelEvent(QWheelEvent *event)
      {
          // Scale the view / do the zoom
          const double scaleFactor = 1.15;
          if(event->delta() > 0)
          {
              // Zoom in
              if(CurZoom < 6.0){
                  CurZoom = CurZoom*scaleFactor;
                  qDebug()<<"wheelEvent(): CurZoom = " << CurZoom;
                  scale(scaleFactor, scaleFactor);
                  centerOn(mapToScene(event->pos()));
                  .....
          }
          else
          {
              // Zooming out
              if (CurZoom > 1.10) {
                  CurZoom = CurZoom/scaleFactor;
                  qDebug()<<"wheelEvent(): CurZoom = " << CurZoom;
                  scale(1.0 / scaleFactor, 1.0 / scaleFactor);
                  centerOn(mapToScene(event->pos()));
                  .....
          }
      }


      Если убрать вызовы centerOn() , то центрирование идет в место, противоположное курсору мыши.
        Илья
        • March 15, 2017, 6:37 a.m.

        Проблема с центрированием сцены при зуммировании решилась следующим образом. Оказывается

        this->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);

        в конструкторе и вызов centerOn() конфликтовали друг с другом.
        В общем закомментил this->setTransformationAnchor(QGraphicsView::AnchorUnderMouse); и все заработало как надо!
          Evgenii Legotckoi
          • March 15, 2017, 6:42 a.m.

          Да. Проблема была именно в отсчёте от нужной границы.

          Что касается центрирования, то проблема скорее всего в том, что постоянно вызывается метод slotAlarmTimer(); . Если говорить в общих чертах, то при зуме и скроллинге не требуется перерисовывать графическую сцену так, как это делаете Вы, с использованием данного метода slotAlarmTimer(); . И так всё нормально должно работать. Здесь требуется переписать логику работу, чтобы отрисовка сетки производилась только тогда, когда это действительно требуется, а не при каждом движении мыши. Тоже самое касается и других объектов на графической сцене. Сцена может скакать, когда изменяются её размеры, но если область ограничена определённым квадратом и новый графические объекты не выходят за границы этого квадрата, то таких проблем со скачками должно быть меньше. Стоит немного пересмотреть логику методов по обработке событий от мыши и избавиться от лишних перерисовок ( В первую очередь от перерисовки координатной сетки )

            Evgenii Legotckoi
            • March 15, 2017, 6:43 a.m.

            Ну что ж. Если причина в этом, то хорошо.

              Comments

              Only authorized users can post comments.
              Please, Log in or Sign up
              e
              • ehot
              • April 1, 2024, 2:29 a.m.

              C++ - Тест 003. Условия и циклы

              • Result:78points,
              • Rating points2
              B

              C++ - Test 002. Constants

              • Result:16points,
              • Rating points-10
              B

              C++ - Test 001. The first program and data types

              • Result:46points,
              • Rating points-6
              Last comments
              k
              kmssrFeb. 9, 2024, 7:43 a.m.
              Qt Linux - Lesson 001. Autorun Qt application under Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
              Qt WinAPI - Lesson 007. Working with ICMP Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
              EVA
              EVADec. 25, 2023, 11:30 p.m.
              Boost - static linking in CMake project under Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
              J
              JonnyJoDec. 25, 2023, 9:38 p.m.
              Boost - static linking in CMake project under Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
              G
              GvozdikDec. 19, 2023, 10:01 a.m.
              Qt/C++ - Lesson 056. Connecting the Boost library in Qt for MinGW and MSVC compilers Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
              Now discuss on the forum
              a
              a_vlasovApril 14, 2024, 6:41 p.m.
              Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
              Павел Дорофеев
              Павел ДорофеевApril 14, 2024, 2:35 p.m.
              QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
              f
              fastrexApril 4, 2024, 4:47 p.m.
              Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…
              AC
              Alexandru CodreanuJan. 20, 2024, 12:57 a.m.
              QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…

              Follow us in social networks