Russian Qt Forum

Qt => Пользовательский интерфейс (GUI) => Тема начата: Eten от Июнь 07, 2014, 04:21



Название: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Eten от Июнь 07, 2014, 04:21
Всем доброго утра, сижу дописываю программу на дипломную работу.

Потребовалось мне сделать отслеживание мыши, для того чтобы менять курсор на схеме управления, да оказалось, что разработчики Qt сделали очень интересный финт, из-за которого вчера долго разбирался, как работает отслеживание мыши, чтобы оно конкретно именно работало. И утро, вечера мудренее, да на свежую голову. Проблему, лично для своего проекта решил, а вопросы остались. Да, еще решил эту тему запостить, т.к. никто про нее не писал, а натыкаешься в других форумах и сам наткнулся на данную дилемму.



Проблему решил, а разрабов так и не понял. Нафига, делать драг и дроп в моузеэвент?! Почему нельзя было оставить отслеживание мыши для отслеживания перемещения мыши?!



И еще один момент, который надо учесть. Например, здесь  (http://unixforum.org/index.php?showtopic=121547)указан пример, который показывает, как сделать отслеживание мыши без всякого нажатия любой кнопки мыши. Как оказалось, этот пример не всегда работает, особенно если у вас не просто окно, а еще есть центральный виджет и на нем схема управления в виде метки с изображением. А так, если без метки, то у центрального виджета тоже надо прописывать setMouseTracking(true). В моем случае заработало, когда прописал у окна, у центрального виджета, у метки с картинкой, и только после этого заработало.

Кто-нибудь, на будущее, может пояснить, а почему мне потребовалось в трех местах указать setMouseTracking(true), чтобы оно конкретно позволило работать mouseMoveEvent-у у самого окна?! Разве там не должны работать принципы наследования или иерархии классов, как это бывает с управлением стилей виджетов?!

Вот код моего окна.
заголовочный:
Код:
#ifndef STENDOSTANOV_H
#define STENDOSTANOV_H

#include <QMainWindow>
#include <QtGui>
#include <QPixmap>

#include "startmenu.h"


//Учебчный стенд остановы турбины
class stendOstanov : public QMainWindow
{
    Q_OBJECT
public:
    explicit stendOstanov(startMenu *startMenu, QWidget *parent = 0);
   
signals:

protected:
      virtual void mousePressEvent (QMouseEvent *event);
      void mouseMoveEvent (QMouseEvent *event);
   
private slots:
    void changeSelectND(int numberSelectND);
    void launchingTest();
    void interruptingTest();

private:
    //Виджеты окна
    QComboBox *selectMode;
    QPushButton *startTest;
    QPushButton *interruptTest;
    QLabel *minutes;
    QLabel *separator;
    QLabel *seconds;
    QLabel *uprTurbImage;
    QLabel *textLabel;
    QLabel *ErrorLabel;
    QLabel *reportTest;
    QScrollArea *scrollReport;



   
};

#endif // STENDOSTANOV_H

исходных кодов:
Код:
#include "stendostanov.h"

stendOstanov::stendOstanov(startMenu *startMenu, QWidget *parent) :
    QMainWindow(parent)
{
    //Устанавливаем параметры окна стенда останова турбины


    //-------------

    //Прописываем параметры окна стенда останова турбины

    setWindowTitle(QObject::trUtf8("Учебчный стенд останова турбины"));
    setWindowFlags(Qt::Window | Qt::WindowSystemMenuHint);
    setMinimumSize(800, 420);
    setMaximumSize(800, 420);
    setMouseTracking(true);

    //-------------

    //Элементы компановки виджетов в окне

    //Основной элемент компановки окна
    QBoxLayout *mainLayout = new QBoxLayout(QBoxLayout::TopToBottom);


    //Элемент компановки верхней части элементов окна
    QHBoxLayout *topLayout = new QHBoxLayout();

    //Элемент компановки таймера в верхней части элементов окна
    QHBoxLayout *timerkLayout = new QHBoxLayout();

    //Элемент компановки нижней части элементов окна
    QHBoxLayout *bottomLayout = new QHBoxLayout();


    //Элемент компановки правой части панели обучения/тестирования
    QVBoxLayout *rightPanelLayout = new QVBoxLayout();

    //-------------


    //Виджеты окна тестирования по НД

    //Виджет отчета после проведения теста
    reportTest = new QLabel("");

    reportTest->setScaledContents(true);
    reportTest->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
    reportTest->setMinimumSize(730, 330);
    scrollReport = new QScrollArea();
    scrollReport->setWidget(reportTest);
    scrollReport->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
    scrollReport->setMinimumSize(750, 350);
    scrollReport->hide();


    //Выбор теста из списка доступных
    selectMode = new QComboBox();
    connect(selectMode, SIGNAL(currentIndexChanged(int)), this, SLOT(changeSelectND(int)));
    selectMode->addItem(QObject::trUtf8("Режим обучения"));
    selectMode->addItem(QObject::trUtf8("Режим тестирования"));


    //Кнопка старта теста
    startTest = new QPushButton(QObject::trUtf8("Начать тест"));
    connect(startTest, SIGNAL(clicked()), this, SLOT(launchingTest()));

    //Кнопка прерывания теста
    interruptTest = new QPushButton(QObject::trUtf8("Прервать тест"));
    connect(interruptTest, SIGNAL(clicked()), this, SLOT(interruptingTest()));
    interruptTest->setEnabled(false);


    //Виджеты таймера: минуты, ":", секунды
    minutes = new QLabel();
    separator = new QLabel(":");
    seconds = new QLabel();

    //Схема управления паровой турбиной
    uprTurbImage = new QLabel();
    QPixmap px;
    px.load("parturbina.png");
    uprTurbImage->setPixmap(px);
    uprTurbImage->setMinimumSize(570,370);
    uprTurbImage->setMaximumSize(570,370);
    uprTurbImage->setScaledContents(true);
    QPalette pal = uprTurbImage->palette();
    pal.setBrush(QPalette::Base, QBrush(Qt::black));
    uprTurbImage->setPalette(pal);
    uprTurbImage->setFrameStyle(QFrame::Plain | QFrame::StyledPanel);
    uprTurbImage->setMouseTracking(true);


    //Текстовая метка об кол-ве ошибок и/или допущенной ошибке
    ErrorLabel = new QLabel();
    ErrorLabel->setMinimumSize(200, 70);
    ErrorLabel->setMaximumSize(200, 70);
    pal = ErrorLabel->palette();
    pal.setBrush(QPalette::Base, QBrush(Qt::black));
    ErrorLabel->setPalette(pal);
    ErrorLabel->setFrameStyle(QFrame::Plain | QFrame::StyledPanel);
    ErrorLabel->setAlignment(Qt::AlignCenter);
    ErrorLabel->setScaledContents(true);
    ErrorLabel->setWordWrap(true);


    //Текст текущей операции в учебном стенде
    textLabel = new QLabel();

    textLabel->setMinimumSize(200, 270);
    textLabel->setMaximumSize(200, 270);
    pal = textLabel->palette();
    pal.setBrush(QPalette::Base, QBrush(Qt::black));
    textLabel->setPalette(pal);
    textLabel->setFrameStyle(QFrame::Plain | QFrame::StyledPanel);
    textLabel->setAlignment(Qt::AlignCenter);
    textLabel->setScaledContents(true);
    textLabel->setWordWrap(true);



    //-------------


    //Компановка виджетов окна

    //Компановка виджетов таймера
    timerkLayout->addWidget(minutes);
    timerkLayout->addWidget(separator);
    timerkLayout->addWidget(seconds);

    //Компановка виджетов верхней части окна
    topLayout->addWidget(selectMode);
    topLayout->addSpacing(15);
    topLayout->addWidget(startTest);
    topLayout->addSpacing(15);
    topLayout->addWidget(interruptTest);
    topLayout->addSpacing(25);
    topLayout->addLayout(timerkLayout);
    topLayout->addStretch();

    //Компановка правой части панели обучения/тестирования учебного стенда
    rightPanelLayout->addWidget(textLabel);
    rightPanelLayout->addSpacing(5);
    rightPanelLayout->addWidget(ErrorLabel);

    //Компановка виджетов нижней части окна
    bottomLayout->addWidget(uprTurbImage);
    bottomLayout->addLayout(rightPanelLayout);


    //Компановка всех элементов компановки окна
    mainLayout->addLayout(topLayout);
    mainLayout->addStretch();
    mainLayout->addWidget(scrollReport);
    mainLayout->addLayout(bottomLayout);
    QWidget *centralWidget = new QWidget();
    centralWidget->setMouseTracking(true);
    centralWidget->setLayout(mainLayout);
    this->setCentralWidget(centralWidget);


    //    reportTest->setWordWrap(true);// Перенос слов
}

void stendOstanov::mousePressEvent(QMouseEvent *event)
{
    int x = event->x();
    int y = event->y();
    qDebug()<<x + "|" + y << x << y;

    textLabel->setText("");

    //ПВД 7
    if (x >= 67 && x <= 116 && y >= 305 && y <= 402)
        textLabel->setText("PVD 7");

    //ПВД 6
    if (x >= 130 && x <= 184 && y >= 305 && y <= 402)
        textLabel->setText("PVD 6");

    //ПВД 5
    if (x >= 200 && x <= 234 && y >= 305 && y <= 402)
        textLabel->setText("PVD 5");

    //Деаэратор
    if (x >= 263 && x <= 334 && y >= 265 && y <= 317)
        textLabel->setText("dearator");

    //ПНД 3
    if (x >= 373 && x <= 418 && y >= 305 && y <= 402)
        textLabel->setText("PND 3");

    //ПНД 2
    if (x >= 428 && x <= 475 && y >= 305 && y <= 402)
        textLabel->setText("PND 2");

    //ПНД 1
    if (x >= 487 && x <= 537 && y >= 305 && y <= 402)
        textLabel->setText("PND 1");

    //Конденсатор
    if (x >= 414 && x <= 476 && y >= 223 && y <= 280)
        textLabel->setText("Kondensator");

    //ЦВД
    if (x >= 194 && x <= 252 && y >= 90 && y <= 193)
        textLabel->setText("CVD");

    //ЦСД
    if (x >= 335 && x <= 383 && y >= 90 && y <= 193)
        textLabel->setText("CSD");

    //ЦНД
    if (x >= 419 && x <= 458 && y >= 90 && y <= 193)
        textLabel->setText("CND");

    //Генератор
    if (x >= 489 && x <= 540 && y >= 119 && y <= 160)
        textLabel->setText("GENERATOR");

    //Основной паропровод
    if (x >= 41 && x <= 85 && ((y >= 58 && y <= 82) || (y >= 198 && y <= 222)))
        textLabel->setText("OP");

}

void stendOstanov::mouseMoveEvent(QMouseEvent *event)
{
   setWindowTitle("mouse event grabbed!");

    int x = event->x();
    int y = event->y();

    textLabel->setText("");

    //ПВД 7
    if (x >= 67 && x <= 116 && y >= 305 && y <= 402)
        textLabel->setText("PVD 7");

    //ПВД 6
    if (x >= 130 && x <= 184 && y >= 305 && y <= 402)
        textLabel->setText("PVD 6");

    //ПВД 5
    if (x >= 200 && x <= 234 && y >= 305 && y <= 402)
        textLabel->setText("PVD 5");

    //Деаэратор
    if (x >= 263 && x <= 334 && y >= 265 && y <= 317)
        textLabel->setText("dearator");

    //ПНД 3
    if (x >= 373 && x <= 418 && y >= 305 && y <= 402)
        textLabel->setText("PND 3");

    //ПНД 2
    if (x >= 428 && x <= 475 && y >= 305 && y <= 402)
        textLabel->setText("PND 2");

    //ПНД 1
    if (x >= 487 && x <= 537 && y >= 305 && y <= 402)
        textLabel->setText("PND 1");

    //Конденсатор
    if (x >= 414 && x <= 476 && y >= 223 && y <= 280)
        textLabel->setText("Kondensator");

    //ЦВД
    if (x >= 194 && x <= 252 && y >= 90 && y <= 193)
        textLabel->setText("CVD");

    //ЦСД
    if (x >= 335 && x <= 383 && y >= 90 && y <= 193)
        textLabel->setText("CSD");

    //ЦНД
    if (x >= 419 && x <= 458 && y >= 90 && y <= 193)
        textLabel->setText("CND");

    //Генератор
    if (x >= 489 && x <= 540 && y >= 119 && y <= 160)
        textLabel->setText("GENERATOR");

    //Основной паропровод
    if (x >= 41 && x <= 85 && ((y >= 58 && y <= 82) || (y >= 198 && y <= 222)))
        textLabel->setText("OP");
}

//Прерывание теста
void stendOstanov::interruptingTest()
{
}

void stendOstanov::changeSelectND(int numberSelectND)
{
  //  numbertest = numberSelectND;
}

//Запуск тестирования
void stendOstanov::launchingTest()
{
}




Название: Re: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Igors от Июнь 07, 2014, 10:32
Проблему решил, а разрабов так и не понял. Нафига, делать драг и дроп в моузеэвент?! Почему нельзя было оставить отслеживание мыши для отслеживания перемещения мыши?!
Не понял Вашего негодования. О чем Вы?

Код:
    //ПВД 7
    if (x >= 67 && x <= 116 && y >= 305 && y <= 402)
        textLabel->setText("PVD 7");

И так еще 10 раз. Это не только длинно, но и плохо. Напр мыша выскочила туда где нет пр-ка, а текст в textLabel остался. Сделайте пр-ки виджетами и ловите события MouseEnter/MouseLeave


Название: Re: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Bepec от Июнь 07, 2014, 12:24
Всё логично у них сделано.
Каждый виджет может ловить перемещения мыши над собой.
Виджет на виджете-родителе получает только свои перемещения, в то время как родитель получает свои и своего ребенка.

А чтобы изменять надписи вполне достаточно mouseTracking на главном виджете. Или же фильтр событий.

to Igors: так не будет, как вы описали. Перед присваиванием текста он обнуляет Label. Так что это вполне рабочее решение, пусть и не особо изящное.

Я бы к примеру написал со словарём <QRect, QString>. так было бы проще во всём :)


Название: Re: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Igors от Июнь 07, 2014, 12:44
to Igors: так не будет, как вы описали. Перед присваиванием текста он обнуляет Label. Так что это вполне рабочее решение, пусть и не особо изящное.
Ну как минимум если мышь ушла за пределы приложения - текст застрял. По поводу "рабочести" - для диплома годится, но в принципе неверно. Нас интересует не текущее положение мыши, а лишь факт что ее входа/выхода в заданные области - ну так надо и делать соответственно.

Если же по каким-то причинам не хочется делать пр-ки виджетами, то обычно запускают таймер и там отлавливают. Хотя судя по вбитым в код константам - пустой разговор, "не в коня корм"  :'(


Название: Re: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Bepec от Июнь 07, 2014, 13:07
Я тоже сначала хотел написать - проще виджеты задать, но там если посмотреть код - простая картинка. Аналог как отмечать лица в соц сетях - рамку натянул, тултип поменялся на ФИО.

Как я вижу по координатам - картинка имеет буферную зону со всех сторон. Т.е. указатель вылететь за пределы мгновенно не может, за исключением стороннего вмешательства, но тут уже другой вопрос :)

Нормальная программа. Видно, что только в первый раз написана :D Пару раз переписать и норм будя ^.^


Название: Re: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Old от Июнь 07, 2014, 13:09
Если же по каким-то причинам не хочется делать пр-ки виджетами, то обычно запускают таймер и там отлавливают.
Вот это настоящее пионерство.


Название: Re: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Igors от Июнь 07, 2014, 13:28
Вот это настоящее пионерство.
Так предложите "комсомольство"  :)


Название: Re: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Old от Июнь 07, 2014, 13:44
Так предложите "комсомольство"  :)
Отслеживать каждое перемещение мыши.


Название: Re: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Igors от Июнь 07, 2014, 14:26
Виджет на виджете-родителе получает только свои перемещения, в то время как родитель получает свои и своего ребенка.
Никогда о таком не слышал - и на всякий случай проверил (аттач).  Каждый получает "свои" mouseMoveEvent - и все.

[/off]"ребенка" - ну не звучит этот термин в переводе  :)

Отслеживать каждое перемещение мыши.
Тот же аттач. Вполне вероятно не захочется возиться со сплиттером или каким-то еще разделителем - просто при наезде мыши на вертикальную границу (+/- 2 пыкселя) курсор меняет форму (мол, двигайте). Как это сделать  отслеживая перемещение мыши?



Название: Re: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Old от Июнь 07, 2014, 14:39
Вполне вероятно не захочется возиться
Когда оно не хочется, то никто не заставит. :)
А если очень хочется сачкануть, то eventFilter + mouseTracking позволят это сделать.


Название: Re: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Igors от Июнь 07, 2014, 18:35
Когда оно не хочется, то никто не заставит. :)
А если очень хочется сачкануть,
Почему сачкануть? Что, очень хорошо там лепить виджет? А какой, как его рисовать? И его никто не спрашивал, надо еще объяснять что это  :)

то eventFilter + mouseTracking позволят это сделать.
Ну это решение октябренка (если Вы им были  :))


Название: Re: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Old от Июнь 07, 2014, 19:36
Ну если отслеживать перемещение мыши в обработчике события перемещения мыши "октябризм", то пользуйтесь для этого таймером. :)


Название: Re: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Bepec от Июнь 07, 2014, 21:16
Если по таймеру - это очень похоже на процедурное программирование, без использования классов в полной мере.

А вот использовать обработчик перемещений мыши у класса окна - как раз таки ООП.


Название: Re: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Old от Июнь 07, 2014, 22:12
Если по таймеру - это очень похоже на процедурное программирование, без использования классов в полной мере.
Не знаю насколько это похоже на процедурное программирование... скорее это как читать файл с диска через QSerialPort. :)
Как бы никто не запрещает, но нельзя сказать что "обычно делают так". :)


Название: Re: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Igors от Июнь 08, 2014, 11:23
Как бы никто не запрещает, но нельзя сказать что "обычно делают так". :)
Техника "по таймеру" существует с незапамятных времен. Не скажу что она идеальна - но возможна. Многие классы Qt ее тоже используют, напр хоть QAbstractItemView для autoScroll (недавно там лазил)

Перекрывать mouseMoveEvent (на том основании что "это ООП - и значит хорошо") в моем примере явно глупо - придется наследоваться для решения частной/мелкой задачи. Предлагаете фильтр - но он здесь не универсален. Для левого айтема надо отследить "мышь на правом крае", для правого - наоборот. А отследив - что делать? Послать сигнал тому это может двигать (а его еще надо знать). В итоге все равно окно двигает - так не лучше ли сделать это одним вызовом таймера?


Название: Re: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Old от Июнь 08, 2014, 11:43
Перекрывать mouseMoveEvent (на том основании что "это ООП - и значит хорошо") в моем примере явно глупо - придется наследоваться для решения частной/мелкой задачи.
А кто заставляет что-то перекрывать? Более того, часто это невозможно сделать.

Предлагаете фильтр - но он здесь не универсален. Для левого айтема надо отследить "мышь на правом крае", для правого - наоборот. А отследив - что делать?
Это потому, что вы не знаете как можно сделать универсально.
Отслеживать перемещение мыши ловя соответствующие события самое естественное решение. А таймер здесь это костыль.


Название: Re: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Igors от Июнь 08, 2014, 11:56
Отслеживать перемещение мыши ловя соответствующие события самое естественное решение. А таймер здесь это костыль.
Мое мнение наоборот, но не вижу смысла препираться - слишком мелкая тема  :) Умолкаю


Название: Re: Особенности работы с setMouseTracking или с отслеживанием мыши
Отправлено: Old от Июнь 08, 2014, 17:31
Техника "по таймеру" существует с незапамятных времен. Не скажу что она идеальна - но возможна. Многие классы Qt ее тоже используют, напр хоть QAbstractItemView для autoScroll (недавно там лазил)
Не знаю что вы понимаете под "техникой по таймеру", но таймеры действительно существуют очень давно. И используются там, где нужно выполнять действия через заданный интервал времени. Как, кстати, и в случае с автоскроллом - эта операция напрямую зависит от времени, потому что служит для плавной анимации скроллирования данных.
А вот координаты мыши по таймеру проверять избыточно, т.к. для этого генерируется специальное событие. Т.е. если мышку никто не трогает, то и события никакие не генерируются и обработки никакой не производится. В отличие от...

А с использованием фильтров событий это вообще делается в несколько строк кода.