My widget is not updated
-
Hello,
This is an Android application...
I wanted to create a 'timeline' type widget with 'handles' as time zone delimiters. This widget works correctly as long as I call it only once (in the main window). However, if I want to place three of them, the first one of the three (and only the first one) does not work properly. It seems that it is not being refreshed.
I've placed qDebug() statements all over the place, and I don't see any difference between the messages received for each of the different timerangewidgets.
Do you see anything wrong in my code?
main.cpp
#include <QApplication> #include "mainwindow.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QPushButton> #include <QVBoxLayout> #include "timerangewidget.h" class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void addTimeRange(int widgetIndex); void removeTimeRange(int widgetIndex); private: QWidget *centralWidget; QVBoxLayout *mainLayout; QList<TimeRangeWidget*> timeRangeWidgets; QList<QPushButton*> addButtons; QList<QPushButton*> removeButtons; void createTimeRangeWidget(); }; #endif // MAINWINDOW_H
timerangewidget.h
#ifndef TIMERANGEWIDGET_H #define TIMERANGEWIDGET_H #include <QElapsedTimer> #include <QWidget> #include <QList> #include <QPair> class TimeRangeWidget : public QWidget { Q_OBJECT public: explicit TimeRangeWidget(QWidget *parent = nullptr); void addTimeRange(); void removeTimeRange(); protected: void paintEvent(QPaintEvent *event) override; bool event(QEvent *event) override; private: struct Handle { int position; bool isMoving; }; struct TimeRange { Handle start; Handle end; }; QList<TimeRange> timeRanges; int handleWidth; int handleHeight; void drawTimeline(QPainter &painter); void drawHandles(QPainter &painter); void drawTimeRanges(QPainter &painter); int positionToTime(int position); int timeToPosition(int time); void snapToGrid(Handle &handle); Handle* findNearestHandle(const QPoint &point); void handleDoubleTap(const QPoint &pos); QElapsedTimer m_doubleTapTimer; bool m_firstTap; QPoint m_lastTapPos; const int DOUBLE_TAP_INTERVAL = 300; // milliseconds const int DOUBLE_TAP_DISTANCE = 30; // pixels }; #endif // TIMERANGEWIDGET_H
mainwindow.cpp
#include "mainwindow.h" #include <QHBoxLayout> #include <QLabel> #include <QGuiApplication> #include <QScreen> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { QScreen *screen = QGuiApplication::primaryScreen(); QRect screenGeometry = screen->geometry(); int screenWidth = screenGeometry.width(); int screenHeight = screenGeometry.height(); qreal devicePixelRatio = screen->devicePixelRatio(); int physicalWidth = screenWidth * devicePixelRatio; int physicalHeight = screenHeight * devicePixelRatio; centralWidget = new QWidget(this); setCentralWidget(centralWidget); int topMargin = 50; // Ajustez selon la hauteur de la barre système centralWidget->setContentsMargins(0, topMargin, 0, 0); mainLayout = new QVBoxLayout(centralWidget); QLabel * ql = new QLabel("aa", this); mainLayout->addWidget(ql); // Create three TimeRangeWidgets for (int i = 0; i < 4; ++i) {createTimeRangeWidget();} } MainWindow::~MainWindow() { } void MainWindow::createTimeRangeWidget() { int index = timeRangeWidgets.size(); TimeRangeWidget *widget = new TimeRangeWidget(this); timeRangeWidgets.append(widget); mainLayout->addWidget(widget); QPushButton *addButton = new QPushButton("+", this); QPushButton *removeButton = new QPushButton("-", this); addButtons.append(addButton); removeButtons.append(removeButton); QHBoxLayout *buttonLayout = new QHBoxLayout(); buttonLayout->addWidget(addButton); buttonLayout->addWidget(removeButton); mainLayout->addLayout(buttonLayout); //if (index==0) {widget->hide();addButton->hide();removeButton->hide();} connect(addButton, &QPushButton::clicked, this, [this, index]() { addTimeRange(index); }); connect(removeButton, &QPushButton::clicked, this, [this, index]() { removeTimeRange(index); }); } void MainWindow::addTimeRange(int widgetIndex) { if (widgetIndex >= 0 && widgetIndex < timeRangeWidgets.size()) { timeRangeWidgets[widgetIndex]->addTimeRange(); } } void MainWindow::removeTimeRange(int widgetIndex) { if (widgetIndex >= 0 && widgetIndex < timeRangeWidgets.size()) { timeRangeWidgets[widgetIndex]->removeTimeRange(); } }
timerangewidget.cpp
#include "timerangewidget.h" #include <QPainter> #include <QTouchEvent> //TimeRangeWidget::TimeRangeWidget(QWidget *parent) // : QWidget(parent), handleWidth(20), handleHeight(40) //{ TimeRangeWidget::TimeRangeWidget(QWidget *parent) : QWidget(parent), handleWidth(20), handleHeight(40), m_firstTap(false) { setAttribute(Qt::WA_AcceptTouchEvents); //setMinimumHeight(60); //setFixedHeight(30); addTimeRange(); // Add initial time range } void TimeRangeWidget::addTimeRange() { TimeRange newRange; newRange.start = {timeToPosition(7 * 60), false}; // 7:00 AM newRange.end = {timeToPosition(10 * 60), false}; // 10:00 AM timeRanges.append(newRange); update(); } void TimeRangeWidget::removeTimeRange() { if (!timeRanges.isEmpty()) { timeRanges.removeLast(); update(); } } void TimeRangeWidget::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QPainter painter(this); drawTimeline(painter); // Dessine l'échelle du temps drawTimeRanges(painter); // Interval entre deux handles drawHandles(painter); // Dessin des handles qDebug() << "paintEvent called for widget" << this; //update(); } /* bool TimeRangeWidget::event(QEvent *event) { switch (event->type()) { case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: { QTouchEvent *touchEvent = static_cast<QTouchEvent*>(event); if (touchEvent->points().count() == 1) { const QTouchEvent::TouchPoint &touchPoint = touchEvent->points().first(); QPoint pos = touchPoint.position().toPoint(); Handle *nearestHandle = findNearestHandle(pos); if (nearestHandle) { if (event->type() == QEvent::TouchBegin) { nearestHandle->isMoving = true; } else if (event->type() == QEvent::TouchUpdate) { nearestHandle->position = pos.x(); snapToGrid(*nearestHandle); } else if (event->type() == QEvent::TouchEnd) { nearestHandle->isMoving = false; } update(); return true; } } break; } default: break; } return QWidget::event(event); } */ bool TimeRangeWidget::event(QEvent *event) { switch (event->type()) { case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: { QTouchEvent *touchEvent = static_cast<QTouchEvent*>(event); if (touchEvent->points().count() == 1) { const QTouchEvent::TouchPoint &touchPoint = touchEvent->points().first(); QPoint pos = touchPoint.position().toPoint(); if (event->type() == QEvent::TouchBegin) { if (!m_firstTap) { m_firstTap = true; m_doubleTapTimer.start(); m_lastTapPos = pos; } else { if (m_doubleTapTimer.elapsed() < DOUBLE_TAP_INTERVAL && (pos - m_lastTapPos).manhattanLength() < DOUBLE_TAP_DISTANCE) { // Double tap detected handleDoubleTap(pos); m_firstTap = false; return true; } else { m_firstTap = true; m_doubleTapTimer.restart(); m_lastTapPos = pos; } } } Handle *nearestHandle = findNearestHandle(pos); if (nearestHandle) { //qDebug() << "nearestHandle" << this; if (event->type() == QEvent::TouchBegin) { nearestHandle->isMoving = true; qDebug() << "Touch Begin" << this; } else if (event->type() == QEvent::TouchUpdate) { nearestHandle->position = pos.x(); snapToGrid(*nearestHandle); qDebug() << "Touch Update" << this; } else if (event->type() == QEvent::TouchEnd) { nearestHandle->isMoving = false; qDebug() << "Touch End" << this; } repaint(); // update(); // à l'origine return true; } } break; } default: break; } return QWidget::event(event); } /* void TimeRangeWidget::drawTimeline(QPainter &painter) { painter.drawLine(0, height() / 2, width(), height() / 2); for (int hour = 0; hour <= 24; hour++) { int x = timeToPosition(hour * 60); painter.drawLine(x, height() / 2 - 5, x, height() / 2 + 5); if (hour % 3 == 0) { painter.drawText(x - 10, height() - 5, QString::number(hour)); } } } */ void TimeRangeWidget::drawTimeline(QPainter &painter) { int trHeight = 30; painter.drawLine(0, trHeight / 2, width(), trHeight / 2); for (int hour = 0; hour <= 24; hour++) { int x = timeToPosition(hour * 60); painter.drawLine(x, trHeight / 2 - 5, x, trHeight / 2 + 5); if (hour % 3 == 0) { painter.drawText(x - 10, trHeight - 5, QString::number(hour)); } } } void TimeRangeWidget::drawHandles(QPainter &painter) { for (const TimeRange &range : timeRanges) { painter.fillRect(range.start.position - handleWidth / 2, 0, handleWidth, handleHeight, Qt::blue); painter.fillRect(range.end.position - handleWidth / 2, 0, handleWidth, handleHeight, Qt::blue); } } void TimeRangeWidget::drawTimeRanges(QPainter &painter) { int trHeight = 30; for (const TimeRange &range : timeRanges) { painter.fillRect(range.start.position, 0, range.end.position - range.start.position, trHeight, QColor(200, 200, 255)); // painter.fillRect(range.start.position, 0, range.end.position - range.start.position, height(), QColor(200, 200, 255)); } } int TimeRangeWidget::positionToTime(int position) { return (position * 24 * 60) / width(); } int TimeRangeWidget::timeToPosition(int time) { return (time * width()) / (24 * 60); } void TimeRangeWidget::snapToGrid(Handle &handle) { int time = positionToTime(handle.position); time = (time / 15) * 15; // Snap to nearest 15 minutes handle.position = timeToPosition(time); } TimeRangeWidget::Handle* TimeRangeWidget::findNearestHandle(const QPoint &point) { Handle *nearest = nullptr; int minDistance = INT_MAX; for (TimeRange &range : timeRanges) { int distStart = qAbs(range.start.position - point.x()); int distEnd = qAbs(range.end.position - point.x()); if (distStart < minDistance && distStart < handleWidth) { minDistance = distStart; nearest = &range.start; } if (distEnd < minDistance && distEnd < handleWidth) { minDistance = distEnd; nearest = &range.end; } } return nearest; } void TimeRangeWidget::handleDoubleTap(const QPoint &pos) { // Implement your double tap logic here // For example, you could add a new time range at the tapped position int time = positionToTime(pos.x()); TimeRange newRange; newRange.start = {timeToPosition(time - 30), false}; // 30 minutes before tapped time newRange.end = {timeToPosition(time + 30), false}; // 30 minutes after tapped time timeRanges.append(newRange); update(); }
-
Hi,
Thanks for sharing your code.
Please help us to narrow the issue down.- what happens if you run the app in a desktop?
- simplify the issue into three simple widgets, that are e.g. updated with a timer or when a button is pressed.
Most importantly: Which Qt Version do you use? Make sure it’s not affected by https://bugreports.qt.io/browse/QTBUG-97482
-
Hi,
I'm under Qt 6.6.3 (MSVC 2019, x86_64)
In a desktop I haven't any problem.I reorganized the layout of my widgets by modifying some layouts, and to my surprise, all the widgets are working perfectly. In this case, they are actually located in the lower part of my smartphone screen, below the middle of the screen. However, as soon as I rotate the screen by a quarter turn, the top widget in the stack ends up above the middle of the screen, and the problem reappears.
So, I doubt that it's a coding issue.
-
I followed your link: https://bugreports.qt.io/browse/QTBUG-97482 I also think this is the crux of the problem.
In this paragraph, it mentions modifying a file with the given lines of code. However, I can't see which file it's referring to. I searched through my folders at C:\Qt\6.7.1\android_x86_64 without success.
"
Changing this however may not be trivial, so a short term fix is needed. Fundamentally, the issue is caused by QAndroidPlatformWindow losing its connection to a backingstore when a QWindow is destroyed and then created(). And this happens because QAndroidPlatformBackingStore only does this association once, guarded by the m_backingStoreSet member.A fix to this issue is to replace the !m_backingStoreSet member with a check for !androidPlatformWindow->backingStore(), which will catch the situation when a QWindow has been recreated with a new platform window that needs to be re-assosciated with a backing store.
https://gist.github.com/torarnv/27d8cb7bfd3f163d2116e92df9180a28
This fix also removes the need to modify QWidgetPrivate::create().
" -
I followed your link: https://bugreports.qt.io/browse/QTBUG-97482 I also think this is the crux of the problem.
In this paragraph, it mentions modifying a file with the given lines of code. However, I can't see which file it's referring to. I searched through my folders at C:\Qt\6.7.1\android_x86_64 without success.
"
Changing this however may not be trivial, so a short term fix is needed. Fundamentally, the issue is caused by QAndroidPlatformWindow losing its connection to a backingstore when a QWindow is destroyed and then created(). And this happens because QAndroidPlatformBackingStore only does this association once, guarded by the m_backingStoreSet member.A fix to this issue is to replace the !m_backingStoreSet member with a check for !androidPlatformWindow->backingStore(), which will catch the situation when a QWindow has been recreated with a new platform window that needs to be re-assosciated with a backing store.
https://gist.github.com/torarnv/27d8cb7bfd3f163d2116e92df9180a28
This fix also removes the need to modify QWidgetPrivate::create().
" -
https://bugreports.qt.io/browse/QTBUG-97482
has been effectively fixed. We just decided to revert the first approach to fix it and replace it with a better one. If your issue doesn’t disappear in 6.7.1, there is another, probably related bug.