I am trying to zoom in with a scale between 1 and 10 in a QGraphicsView. The code I have is complex but I created a minimal reproducible example as follows:
main.cpp
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow mainWin;
mainWin.show();
return app.exec();
}
CustomGraphicsView.h
#include
class CustomGraphicsView : public QGraphicsView
{
public:
CustomGraphicsView(QGraphicsScene* scene, QWidget* parent = nullptr) : QGraphicsView(scene, parent) {}
void resizeEvent(QResizeEvent* event) override;
inline void setSceneSize(int origX, int origY, int sizeX, int sizeY) {
m_SceneOrigX = origX;
m_SceneOrigY = origY;
m_SceneSizeX = sizeX;
m_SceneSizeY = sizeY;
}
void resize();
private:
int m_SceneOrigX = -1;
int m_SceneOrigY = -1;
int m_SceneSizeX = -1;
int m_SceneSizeY = -1;
};
CustomGraphicsView.cpp
#include "customgraphicsview.h"
void CustomGraphicsView::resizeEvent(QResizeEvent* event)
{
fitInView(m_SceneOrigX, m_SceneOrigY, m_SceneSizeX, m_SceneSizeY, Qt::KeepAspectRatio);
QGraphicsView::resizeEvent(event);
}
void CustomGraphicsView::resize()
{
fitInView(m_SceneOrigX, m_SceneOrigY, m_SceneSizeX, m_SceneSizeY, Qt::KeepAspectRatio);
}
MainWindow.h
#include <QMainWindow>
#include "customgraphicsview.h"
#include <QSlider>
#include <QGraphicsScene>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
private slots:
void showItems();
void setZoomLevel(int zoom);
private:
QGraphicsScene* m_Scene;
CustomGraphicsView* m_View;
QWidget* m_MainView;
QSlider* m_ZoomSlider;
double m_CurrentZoomLevel = 1.0;
};
MainWindow.cpp
#include "mainwindow.h"
#include <QVBoxLayout>
#include <QGraphicsRectItem>
MainWindow::MainWindow()
{
setMinimumHeight(600);
setMinimumWidth(800);
m_Scene = new QGraphicsScene();
m_View = new CustomGraphicsView(m_Scene);
m_View->setAttribute(Qt::WA_AlwaysShowToolTips);
m_MainView = new QWidget();
m_ZoomSlider = new QSlider(Qt::Horizontal);
m_ZoomSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
m_ZoomSlider->setTickPosition(QSlider::TicksAbove);
m_ZoomSlider->setMinimum(1);
m_ZoomSlider->setMaximum(10);
m_ZoomSlider->setTickInterval(1);
QVBoxLayout* vLayout = new QVBoxLayout();
vLayout->addWidget(m_View);
vLayout->addWidget(m_ZoomSlider);
m_MainView->setLayout(vLayout);
setCentralWidget(m_MainView);
connect(m_ZoomSlider, &QSlider::valueChanged, this, &MainWindow::setZoomLevel);
showItems();
}
void MainWindow::showItems() {
m_Scene->clear();
m_View->resetTransform();
m_Scene->setSceneRect(QRectF());
m_View->setSceneRect(QRectF());
m_View->fitInView(QRectF());
for (int i = 0; i < 9; i++) {
int row = i % 3;
int col = i / 3;
QGraphicsRectItem* squareItem = new QGraphicsRectItem(0, 0, 3000 * 1000, 3000 * 1000);
m_Scene->addItem(squareItem);
squareItem->setPen(QPen(QBrush(Qt::black), 10000));
squareItem->setPos((col - 1) * 3000 * 1000, (row - 1) * 3000 * 1000);
}
m_Scene->setSceneRect(-3000 * 1000, -3000 * 1000, 4 * 3000 * 1000, 4 * 3000 * 1000);
m_View->setSceneSize(-3000 * 1000 / m_CurrentZoomLevel, -3000 * 1000 / m_CurrentZoomLevel, 4 * 3000 * 1000 / m_CurrentZoomLevel, 4 * 3000 * 1000 / m_CurrentZoomLevel);
}
void MainWindow::setZoomLevel(int zoom) {
m_CurrentZoomLevel = zoom;
showItems();
}
CMakeLists.txt
cmake_minimum_required (VERSION 3.10)
project (ZoomTestProject CXX)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS 1)
find_package(Qt6 COMPONENTS Core Widgets)
cmake_policy(SET CMP0020 NEW)
cmake_policy(SET CMP0043 NEW)
include_directories(
${Qt6Widgets_INCLUDE_DIRS}
${Qt6Core_INCLUDE_DIRS})
set(DOCK_SRCS
main.cpp
mainwindow.cpp
customgraphicsview.cpp
)
set(DOCK_HEADR
mainwindow.h
customgraphicsview.h
)
add_executable(ZoomTestProject
${DOCK_SRCS}
${DOCK_HEADR})
target_link_libraries(ZoomTestProject
Qt6::Widgets
Qt6::Core)
set(ROOT_PATH ${Qt6_DIR}/../../../)
install(TARGETS ZoomTestProject DESTINATION bin)
install(FILES ${ROOT_PATH}/bin/Qt6Core.dll DESTINATION bin)
install(FILES ${ROOT_PATH}/bin/Qt6Gui.dll DESTINATION bin)
install(FILES ${ROOT_PATH}/bin/Qt6Widgets.dll DESTINATION bin)
install(FILES ${ROOT_PATH}/plugins/platforms/qwindows.dll DESTINATION bin/platforms)
The problem that I have is that when I zooming in , when I move the slider the first time to a zoom level, it zooms correctly (scroll bars are correctly positioned, the size of the shown elements is correct). When I move the slider again it zooms to a very big level, the scroll bars are not correct anymore. If I move the slider further nothing happens anymore, the extreme zoom level remains, the scroll bars keep their false size.
Does anyone have an idea about what this can be ?
I have solved this as follows:
Do not call fitinView inside the resize event of the customgraphicsview
Implement a function called resize in the customgraphicsview as follows:
void CustomGraphicsView::resize()
{
fitInView(m_SceneOrigX, m_SceneOrigY, m_SceneSizeX, m_SceneSizeY, Qt::KeepAspectRatio);
}
In the showItems() function of mainwindow do at the end as follows:
m_Scene->setSceneRect(-3000 * 1000, -3000 * 1000, 4 * 3000 * 1000, 4 * 3000 * 1000);
m_View->scale(m_ScaleFactor, m_ScaleFactor);
m_View->setSceneSize(-3000 * 1000 / m_CurrentZoomLevel, -3000 * 1000 / m_CurrentZoomLevel, 4 * 3000 * 1000 / m_CurrentZoomLevel, 4 * 3000 * 1000 / m_CurrentZoomLevel);
m_View->resize();
The setZoomLevel() function in the mainwindow was changed as follows:
void MainWindow::setZoomLevel(int zoom) {
m_ScaleFactor = double(zoom) / m_CurrentZoomLevel;
m_CurrentZoomLevel = zoom;
showItems();
}
The essence of the problem is that fitInView call when resizing of customgraphicsview does not work with the zoom function. I have read in other threads that a solution to this would be to always show the scrollbars in the view. I tried this as well but did not bring any results.
I hope this helps someone in need.
Related
I'm using Qt 5.15 with C++17. I have a hierarchy of Widgets like hinted in the title:
MainWindow : QMainWindow
->centralWidget() = QScrollArea
->widget() = QWidget
->layout() = QVBoxLayout
->children() = [
InnerWidget,
InnerWidget,
InnerWidget,
...
]
with InnerWidget.children() = [ QLabel, QLabel ].
I have set InnerWidget::setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum) inside InnerWidget's constructor.
The QLabels inside InnerWidget are initialized with some QPixmap img like
label->setPixmap(img);
label->setFixedSize(img.size());
This all works fine, however now I added an option to zoom all the pixmaps to the MainWindow's menubar. I connect to a signal MainWindow::zoom_inner(.., which itself calls InnerWidget::zoom(.. on every InnerWidget, which resizes the images / labels like this:
auto scaled = img.scaled(img.size() * factor);
label->setPixmap(scaled);
label->setFixedSize(scaled.size());
Finally I update the size of the MainWindow by finding the maximum InnerWidget.frameGeometry().width() (= inner_max_width) and calling
void MainWindow::zoom_inner() {
// ...
setMaximumWidth(inner_max_width + centralWidget()->verticalScrollBar()->width() + 3);
resize(maximumWidth(), size().height());
}
Now after calling MainWindow::zoom_inner(.., the window seems to resize to the correct size, as do the QLabel's inside the InnerWidget's. However, the InnerWidget's themselves do not resize at all, they just stay the same size as before and I have no idea why. I tried calling many combinations of
adjustSize();
update();
layout()->update();
updateGeometry();
on any of the MainWindow,QScrollArea,InnerWidget but nothing happens.. even calling InnerWidget::resize(new_size) has no effect. Clearly InnerWidget::sizeHint() gives the correct value, because the MainWindow size after MainWindow::zoom(.. is correct. So why do the InnerWidget's refuse to resize to fit the QLabel's, even though sizePolicy is set to QSizePolicy::Minimum?
This seems like some sort of misunderstanding on my part so I'm hoping the answer is simple and doesn't need a MWE. I've tried the docs but couldn't find a solution.
Below I add a MWE. For persistance of the question I pasted the code instead of providing a github link. This should build after creating the files and running the build.sh script.
Desired behavior: After choosing a zoom factor and confirming, all InnerWidget's should automatically resize to fit the newly rescaled QPixmap's/QLabel's.
Issue: The InnerWidget's never change size, even when calling .resize(.. on them, why?
MWE
include/NestedWidgetResizeIssue_MWE.hh
#include <vector>
#include <QtGui/QPixmap>
#include <QtWidgets/QAction>
#include <QtWidgets/QLabel>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenu>
#include <QtWidgets/QScrollArea>
#include <QtWidgets/QWidget>
#include <QtWidgets/QFrame>
class InnerWidget : public QFrame {
QPixmap m_img_1;
QPixmap m_img_2;
QLabel* m_label_1;
QLabel* m_label_2;
static QPixmap create_black_img();
static void update_img_label(const QPixmap& img, QLabel* label);
static void init_img_label(QPixmap& img, QLabel*& label);
static void zoom_img_label(double factor, QPixmap& img, QLabel* label);
public:
InnerWidget();
void zoom(double factor);
};
// --------------------------------------------------
class MainWindow : public QMainWindow {
Q_OBJECT
QMenu* m_edit_menu;
QAction* m_zoom_action;
QScrollArea* m_central;
QWidget* m_main;
std::vector<InnerWidget*> m_entries;
void update_max_width();
private slots:
void zoom_inner();
public:
MainWindow();
};
src/NestedWidgetResizeIssue_MWE.cc
#include <cmath>
#include <QtGui/QPainter>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QScrollBar>
#include <NestedWidgetResizeIssue_MWE.hh>
InnerWidget::InnerWidget() {
setFrameShape(QFrame::Panel);
auto* layout = new QHBoxLayout{};
init_img_label(m_img_1, m_label_1);
init_img_label(m_img_2, m_label_2);
layout->addWidget(m_label_1);
layout->addWidget(m_label_2);
setLayout(layout);
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
adjustSize();
}
QPixmap InnerWidget::create_black_img() {
QPixmap img(400, 400);
QPainter p(&img);
img.fill(QColor::fromRgb(0, 0, 0));
return img;
}
void InnerWidget::update_img_label(const QPixmap& img, QLabel* label) {
label->setPixmap(img);
label->setFixedSize(img.size());
}
void InnerWidget::init_img_label(QPixmap& img, QLabel*& label) {
img = create_black_img();
label = new QLabel{};
update_img_label(img, label);
}
void InnerWidget::zoom_img_label(double factor, QPixmap& img, QLabel* label) {
img = img.scaled(img.size() * factor);
update_img_label(img, label);
}
void InnerWidget::zoom(double factor) {
zoom_img_label(factor, m_img_1, m_label_1);
zoom_img_label(factor, m_img_2, m_label_2);
}
// --------------------------------------------------
MainWindow::MainWindow() :
m_edit_menu{ menuBar()->addMenu("&Edit") },
m_zoom_action{ new QAction{ "&Zoom" } },
m_central{ new QScrollArea },
m_main{ new QWidget } {
connect(m_zoom_action, &QAction::triggered, this, &MainWindow::zoom_inner);
m_edit_menu->addAction(m_zoom_action);
auto* layout = new QVBoxLayout{};
layout->setContentsMargins(0, 0, 0, 0);
for (int i = 0; i < 2; ++i) {
auto* entry = new InnerWidget;
m_entries.push_back(entry);
layout->addWidget(entry);
}
m_main->setLayout(layout);
m_central->setWidget(m_main);
setCentralWidget(m_central);
show();
update_max_width();
resize(maximumWidth(), 500);
}
void MainWindow::zoom_inner() {
bool confirm = false;
auto factor = QInputDialog::getDouble(this, "Zoom images", "Factor", 1.0, 0.1, 2.0, 2, &confirm, Qt::WindowFlags{}, 0.05);
if (!confirm) return;
for (auto* e : m_entries) {
e->zoom(factor);
e->adjustSize();
}
update_max_width();
resize(maximumWidth(), size().height());
}
void MainWindow::update_max_width() {
int max_entry_width = 0;
for (const auto* e : m_entries)
max_entry_width = std::max(max_entry_width, e->width());
setMaximumWidth(max_entry_width + m_central->verticalScrollBar()->width() + 3);
}
src/main.cc
#include <QtWidgets/QApplication>
#include <NestedWidgetResizeIssue_MWE.hh>
int main(int argc, char** argv) {
QApplication app(argc, argv);
MainWindow win;
return app.exec();
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(Test)
add_compile_options(-Wall -Wextra -pedantic)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
cmake_policy(SET CMP0100 NEW)
find_package(Qt5 COMPONENTS Widgets REQUIRED)
include_directories(include)
add_executable(Test
src/main.cc
include/NestedWidgetResizeIssue_MWE.hh
src/NestedWidgetResizeIssue_MWE.cc
)
target_link_libraries(Test
Qt::Widgets
)
install(TARGETS
Test
DESTINATION "${CMAKE_SOURCE_DIR}/bin")
build.sh
#!/bin/bash
mkdir -p build bin
cd build
cmake ..
make
make install
So, I checked the following questions, which seemed most similar to my existing problem:
QML: Using cpp signal in QML always results in "Cannot assign to non-existent property"
Unfortunately that did not help. (Nor did any other solution that I could find on stackoverflow/google/qt forums, etc)
I kept getting the following two errors:
qrc:/view.qml:30:9: QML Connections: Cannot assign to non-existent
property "onNewFrameReceived" qrc:/view.qml:31: ReferenceError:
imageProvide is not defined
Here is my code (edited down to make it, into a 'Minimum Working Example').
The only files important should be:
main.cpp
view.qml
imageprovidervm.cpp
imageprovidervm.h
I included the imagesource class, just to be complete, in case someone wants to compile this on his own as well.
Q1. So, I don't understand why even after setting the context property in main.cpp, the following error appears.
qrc:/view.qml:31: ReferenceError: imageProvide is not defined
What is funny is that, intellisense/autocomplete seems to detect imageProvide completely correctly.
Q2. Even though in my imageprovider.h, I added properties (newimage) and signals (newFrameReceived) that should be seen in the qml file, still I get the following error. Also, the Qt intellisense/autocomplete fails to show my defined signal (onNewFrameReceived) here.
qrc:/view.qml:30:9: QML Connections: Cannot assign to non-existent
property "onNewFrameReceived"
Additional info: Debugging and stopping on a break point in the qml file at line 31, shows in the "locals and expressions" of the qtcreator that I have only 2 signals available here, namely "objectNameChanged" and "targetChanged".
Why ???
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "imageprovidervm.h"
#include "imagesource.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQmlContext *context = new QQmlContext(engine.rootContext());
auto model = std::make_shared<ImageSource>();
auto vm = new ImageProviderVM(model);
engine.addImageProvider(QLatin1String("imageProvider"), vm);
context->setContextProperty("imageProvide", vm );
model->generateImages();
engine.load(QUrl(QStringLiteral("qrc:/view.qml")));
return app.exec();
}
view.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQml.Models 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Window 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("&Open")
onTriggered: console.log("Open action triggered");
}
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
Rectangle {
Connections {
target: imageProvide
onNewFrameReceived: image.reload();
}
anchors.fill: parent
Column {
Image {
id: image
source: "image://imageProvider/images.jpeg?id=" + Math.random()
cache: false
asynchronous: true
function reload() {
var oldSource = source;
source = "";
source = oldSource;
}
}
}
}
Label {
text: qsTr("Hello World")
anchors.centerIn: parent
}
}
imageprovidervm.h
#ifndef IMAGEPROVIDERVM_H
#define IMAGEPROVIDERVM_H
#include <QQuickImageProvider>
#include <QObject>
#include "imagesource.h"
class ImageProviderVM : public QObject, public QQuickImageProvider
{
Q_OBJECT
public:
ImageProviderVM(std::shared_ptr<ImageSource> model);
~ImageProviderVM();
virtual QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
virtual QImage requestImage(const QString & id, QSize * size, const QSize & requestedSize) override;
// Properties
Q_PROPERTY(QImage newimage READ getNewImage NOTIFY newFrameReceived)
// Signals
signals:
void newFrameReceived();
private:
QImage getNewImage() const;
QPixmap m_pixmap;
QImage m_image;
std::shared_ptr<ImageSource> m_model;
};
#endif // IMAGEPROVIDERVM_H
imageprovidervm.cpp
#include "imageprovidervm.h"
#include <functional>
#include <QPixmap>
#include <QDebug>
ImageProviderVM::ImageProviderVM()
: QQuickImageProvider(QQuickImageProvider::Image)
{
}
ImageProviderVM::ImageProviderVM(std::shared_ptr<ImageSource> model)
: QQuickImageProvider (QQuickImageProvider::Image)
, m_pixmap()
, m_model(model)
{
m_model->subscribeNewPixMap([this](QPixmap pixmap) {
qDebug() << "setting m_pixmap";
if (pixmap.size().isValid()) {
m_pixmap = pixmap;
}
else
qDebug() << "is it NULL ??? " << pixmap.isNull();
});
m_model->subscribeNewImage([this](QImage image) {
qDebug() << "setting m_image";
if (image.size().isValid()) {
m_image = image;
emit newFrameReceived();
}
else
qDebug() << "is it NULL ??? " << image.isNull();
});
qDebug() << "imageproviderVM constructed";
}
ImageProviderVM::~ImageProviderVM()
{
}
QPixmap ImageProviderVM::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
{
// look into the parameters id, size and requestedSize once the rest of the structure is there
return m_pixmap;
}
QImage ImageProviderVM::requestImage(const QString & id, QSize * size, const QSize & requestedSize)
{
return m_image;
}
QQuickTextureFactory * ImageProviderVM::requestTexture(const QString & id, QSize * size, const QSize & requestedSize)
{
// return QQuickTextureFactory::createTexture();
}
QImage ImageProviderVM::getNewImage() const
{
return m_image;
}
imagesource.h
#ifndef IMAGESOURCE_H
#define IMAGESOURCE_H
#include <QImage>
#include <boost/signals2.hpp>
class ImageSource
{
public:
ImageSource();
void generateImages();
void generatePixmaps(const QString &id, QSize *size, const QSize &requestedSize);
typedef boost::signals2::signal<void (QPixmap)> NewPixMapDelegate;
boost::signals2::connection subscribeNewPixMap(NewPixMapDelegate::slot_function_type f);
typedef boost::signals2::signal<void (QImage)> NewImageDelegate;
boost::signals2::connection subscribeNewImage(NewImageDelegate::slot_function_type f);
private:
NewPixMapDelegate m_newPixMap;
NewImageDelegate m_newImage;
};
#endif // IMAGESOURCE_H
imagesource.cpp
#include "imagesource.h"
#include <QPixmap>
#include <QPainter>
#include <thread>
ImageSource::ImageSource()
{
}
boost::signals2::connection ImageSource::subscribeNewImage(NewImageDelegate::slot_function_type f)
{
return m_newImage.connect(f);
}
void ImageSource::generateImages()
{
std::thread t([this]() {
auto image = QImage("/home/junaid/testing_ground/fourthtime/images.jpeg");
m_newImage(image);
/// useless wait. just simulating that another image comes after sometime and so on onwards.
int random_wait = 2; //sec
sleep(random_wait);
image = QImage("/home/junaid/Downloads/pnggrad16rgb.png");
m_newImage(image);
});
t.detach();
}
boost::signals2::connection ImageSource::subscribeNewPixMap(NewPixMapDelegate::slot_function_type f)
{
return m_newPixMap.connect(f);
}
void ImageSource::generatePixmaps(const QString &id, QSize *size, const QSize &requestedSize)
{
int width = 100;
int height = 50;
if (size) {
*size = QSize(width, height);
}
QPixmap pixmap(requestedSize.width() > 0 ? requestedSize.width() : width,
requestedSize.height() > 0 ? requestedSize.height() : height);
pixmap.fill(QColor(id).rgba());
// write the color name
QPainter painter(&pixmap);
QFont f = painter.font();
f.setPixelSize(20);
painter.setFont(f);
painter.setPen(Qt::black);
if (requestedSize.isValid())
painter.scale(requestedSize.width() / width, requestedSize.height() / height);
painter.drawText(QRectF(0, 0, width, height), Qt::AlignCenter, id);
m_newPixMap(pixmap);
}
and here is the CMake file:
cmake_minimum_required(VERSION 2.8.12)
project(non_existent_property LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
find_package(Qt5 COMPONENTS Core Quick REQUIRED)
file( GLOB SRCS *.cpp *.h )
add_executable(${PROJECT_NAME} "qml.qrc" ${SRCS})
target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Quick "pthread")
So, here is how the working main.cpp looks. I was mislead by the QmlContext that I was creating, and with help from #GrecKo's and jpnurmi's comments, I understood that I had to set the property for the root context.
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "imageprovidervm.h"
#include "imagesource.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
// QQmlContext *context = new QQmlContext(engine.rootContext());
auto model = std::make_shared<ImageSource>();
auto vm = new ImageProviderVM(model);
engine.addImageProvider(QLatin1String("imageProvider"), vm);
engine.rootContext()->setContextProperty("imageProvide", vm );
model->generateImages();
engine.load(QUrl(QStringLiteral("qrc:/view.qml")));
return app.exec();
}
I'm building a multi-window app and so far creates and shows MainWidget with 8 buttons. My next step is to make each button open a new window which in Qt terms is a child of QWidget. I keep all my buttons and new windows (which should be opened upon a button is clicked) in QVectors. It all compiles with no warnings or errors, however, when I click a button, the corresponding QWidget window is not shown.
mainwidget.h
#ifndef MAINWIDGET_H
#define MAINWIDGET_H
#include <QWidget>
#include <QPushButton>
#include <QGridLayout>
#include <QSignalMapper>
#include "examwindow.h"
namespace Ui { class MainWidget; }
class MainWidget : public QWidget {
Q_OBJECT
public:
explicit MainWidget(QWidget *parent = 0);
~MainWidget();
private:
Ui::MainWidget *ui;
int nExams;
QVector<QString> titles;
QVector<QPushButton*> examButtons;
QGridLayout* mainWidgetLayout;
QVector<ExamWindow*> examWindows;
public slots:
void clickedExamW ();
};
#endif // MAINWIDGET_H
mainwidget.cpp
#include "mainwidget.h"
#include "ui_mainwidget.h"
#include <QDesktopWidget>
#include <iostream>
MainWidget::MainWidget(QWidget *parent) : QWidget(parent), ui(new Ui::MainWidget)
{
ui->setupUi(this);
/**
* #brief Resize the main window size in proportion to the desktop size
*/
double ratio = 0.7;
resize(QDesktopWidget().availableGeometry(this).size() * ratio);
/**
* #brief Set the main window position in the desktop center
*/
QDesktopWidget dw;
int width = this->frameGeometry().width();
int height = this->frameGeometry().height();
int screenWidth = dw.screen() -> width();
int screenHeight = dw.screen() -> height();
this->setGeometry((screenWidth / 2) - (width / 2), (screenHeight / 2) - (height / 2), width, height);
/**
* Set the button titles
*/
titles.push_back("FCE - 2008");
titles.push_back("CAE - 2008");
titles.push_back("CPE - 2008");
titles.push_back("ЕГЭ");
titles.push_back("FCE - 2015");
titles.push_back("CAE - 2015");
titles.push_back("CPE - 2015");
titles.push_back("User's Format");
/**
* Create buttons
*/
nExams = 8; // Number of exams
examButtons.resize(nExams);
for(int i = 0; i < nExams; i++) {
examButtons[i] = new QPushButton(titles[i]);
examButtons[i]->setMinimumSize(QSize(150, 150));
examButtons[i]->setMaximumSize(QSize(500, 500));
examButtons[i]->setObjectName(titles[i]);
connect(examButtons[i], SIGNAL(clicked()), this, SLOT(clickedExamW()));
}
/**
* Add exam buttons to the main widget layout
*/
mainWidgetLayout = new QGridLayout(this);
for(int i = 0; i < nExams; i++)
if (i < nExams / 2)
mainWidgetLayout -> addWidget(examButtons[i], i, 0);
else
mainWidgetLayout -> addWidget(examButtons[i], i - nExams / 2, 1);
/**
* Create exam windows
*/
examWindows.resize(nExams);
for(int i = 0; i < nExams; i++) {
examWindows[i] = new ExamWindow(this);
examWindows[i]->setWindowTitle(titles[i]);
}
}
void MainWidget::clickedExamW() {
QObject *senderObj = sender();
QString senderObjName = senderObj->objectName();
for(int i = 0; i < nExams; i++)
if (senderObjName == titles[i]) {
this->setWindowTitle(titles[i]); // WORKS - it changes the title
examWindows[i]->show(); // DOES NOT WORK - no win shown
}
}
MainWidget::~MainWidget()
{
delete ui;
}
examwindow.h
#ifndef EXAMWINDOW_H
#define EXAMWINDOW_H
#include <QWidget>
class ExamWindow : public QWidget
{
Q_OBJECT
public:
explicit ExamWindow(QWidget *parent = 0);
signals:
public slots:
};
#endif // EXAMWINDOW_H
examwindow.cpp
#include "examwindow.h"
ExamWindow::ExamWindow(QWidget *parent) : QWidget(parent)
{
}
The way, how do you create widgets is ok (examWindows[i] = new ExamWindow(this);). But:
ExamWindow::ExamWindow(QWidget *parent)
: QWidget(parent, Qt::Window)
{}
You need to directly specify a flag that you need a "window" widget.
Or, if you don't want to set parent widget, you may set Qt::WA_DeleteOnClose attribute for automatic releasing memory. Note, that in this case you will have an invalid pointer inside your examWindows vector. It may be resolved if you will use next declaration: QVector<QPointer<ExamWindow>> examWindows;
ANSWER: it turned out that the problem was in this line:
examWindows[i] = new ExamWindow(this);
If I remove 'this', making my windows parentless, it works as intended. I'm puzzled why and I guess I have to delete them now manually.
I'm quite a n00b with Qt, but I have seen a lot of posts where people can't get a QGraphicsView to update as expected. I am trying to make very simple widget that will display two arrays of data as images overlapped with some alpha blending. From what I gather the best way to do this is to create a QImage to hold the image data, and each time I want to update the overlay display, you convert the QImage into a QPixmap, and then use that to update the pixmap of a QGraphicsPixmapItem (which is in a QGraphicsScene, which is in a QGraphicsView).
As a minimum working example, I have it setup to generate a random red image and random green image, and with random blending between them. It's setup on a timer to generate new data and, ideally, update the view. No matter how many updates/repaints I drop around, I can't seem to get it to update properly. The timer seems to be working, and the random data generation seems to be working as well, but the scene only updates if I physically change the size of the window.
Here is my code, in a few blocks. First DisplayWidget.h
#include <QtWidgets/QWidget>
#include "ui_displaywidget.h"
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsPixmapItem>
#include <QPushButton>
#include "math.h"
#include <QVBoxLayout>
#include <qsize.h>
#include <qtimer.h>
#define FULLSCALE 255
#define IM_X_MIN -5.0
#define IM_X_MAX 5.0
#define IM_Z_MIN 0.0
#define IM_Z_MAX 15.0
#define IM_PIXEL_WIDTH 200
#define IM_PIXEL_HEIGHT IM_PIXEL_WIDTH * (IM_Z_MAX-IM_Z_MIN)/(IM_X_MAX - IM_X_MIN)
#define BORDER_WIDTH 10
#define RAND_SEED 7
class DisplayWidget : public QWidget
{
Q_OBJECT
public:
DisplayWidget(int width, int height, QWidget *parent = 0);
~DisplayWidget();
void SetData(float * data, float minVal, float maxVal);
void SetTransparency(float * alpha, float minVal, float maxVal);
private:
//Ui::DisplayWidgetClass ui;
QGraphicsView * view;
QGraphicsScene * scene;
QGraphicsPixmapItem * bModeItem;
QGraphicsPixmapItem * dModeItem;
QImage * bImage;
QImage * dImage;
QTimer * frameGrab;
void CreateWidgets();
void SetupGui();
int w, h;
public slots:
void GenerateNewData();
};
And here's the relevant parts of my .cpp. GenerateNewData() is the function that doesn't seem to produce updates to the scene. This is where I have tried view/scene/item updates/repaints.
DisplayWidget::DisplayWidget(int width, int height, QWidget *parent): QWidget(parent)
{
w = width;
h = height;
CreateWidgets();
SetupGui();
// seed the random number generator
srand(RAND_SEED);
GenerateNewData();
}
void DisplayWidget::CreateWidgets()
{
view = new QGraphicsView(this);
scene = new QGraphicsScene(this);
bModeItem = scene->addPixmap(QPixmap());
dModeItem = scene->addPixmap(QPixmap());
bImage = new QImage(w, h, QImage::Format_ARGB32);
dImage = new QImage(w, h, QImage::Format_ARGB32);
frameGrab = new QTimer(this);
}
void DisplayWidget::SetupGui()
{
QVBoxLayout * layout = new QVBoxLayout(this);
layout->addWidget(view);
setLayout(layout);
scene->setSceneRect(0, 0, IM_PIXEL_WIDTH, IM_PIXEL_HEIGHT);
view->setGeometry(0, 0, IM_PIXEL_WIDTH + 2*BORDER_WIDTH, IM_PIXEL_HEIGHT + 2*BORDER_WIDTH);
view->setScene(scene);
bModeItem->setPixmap(QPixmap::fromImage(*bImage).scaled(QSize(IM_PIXEL_WIDTH, IM_PIXEL_HEIGHT)));
dModeItem->setPixmap(QPixmap::fromImage(*dImage).scaled(QSize(IM_PIXEL_WIDTH, IM_PIXEL_HEIGHT)));
connect(frameGrab, SIGNAL(timeout()),this, SLOT(GenerateNewData()));
frameGrab->start(500);
}
void DisplayWidget::GenerateNewData()
{
QRgb * bImageData = (QRgb *)bImage->scanLine(0);
QRgb * dImageData = (QRgb *)dImage->scanLine(0);
for (int i; i < w * h; i++)
{
bImageData[i] = qRgba(rand() % FULLSCALE, 0, 0, FULLSCALE);
dImageData[i] = qRgba(0, 255,0, rand() % FULLSCALE);
}
bModeItem->setPixmap(QPixmap::fromImage(*bImage).scaled(QSize(IM_PIXEL_WIDTH, IM_PIXEL_HEIGHT)));
dModeItem->setPixmap(QPixmap::fromImage(*dImage).scaled(QSize(IM_PIXEL_WIDTH, IM_PIXEL_HEIGHT)));
}
And here's my main.
#include "displaywidget.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
DisplayWidget w(25,25);
w.show();
return a.exec();
}
I would appreciate any help!
I'm messing around with QGraphicsView and QGraphicsScene to create a Tic Tac Toe clone. I add some QGraphicsLineItems to my scene and override the resizeEvent method of the Widget that contains the view, so that when the window is resized, the view and its contents are scaled appropriately. This works fine, except for the first time that I run the program:
Once I resize the window by any amount, the scene is scaled correctly:
Here's the code:
main.cpp:
#include <QtGui>
#include "TestApp.h"
int main(int argv, char **args)
{
QApplication app(argv, args);
TestApp window;
window.show();
return app.exec();
}
TestApp.h:
#ifndef TEST_APP_H
#define TEST_APP_H
#include <QtGui>
class TestApp : public QMainWindow
{
Q_OBJECT
public:
TestApp();
protected:
void resizeEvent(QResizeEvent* event);
QGraphicsView* view;
QGraphicsScene* scene;
};
#endif
TestApp.cpp:
#include "TestApp.h"
TestApp::TestApp()
: view(new QGraphicsView(this))
, scene(new QGraphicsScene(this))
{
resize(220, 220);
scene->setSceneRect(0, 0, 200, 200);
const int BOARD_WIDTH = 3;
const int BOARD_HEIGHT = 3;
const QPoint SQUARE_SIZE = QPoint(66, 66);
const int LINE_WIDTH = 10;
const int HALF_LINE_WIDTH = LINE_WIDTH / 2;
QBrush lineBrush = QBrush(Qt::black);
QPen linePen = QPen(lineBrush, LINE_WIDTH);
for(int x = 1; x < BOARD_WIDTH; ++x)
{
int x1 = x * SQUARE_SIZE.x();
scene->addLine(x1, HALF_LINE_WIDTH, x1, scene->height() - HALF_LINE_WIDTH, linePen);
}
for(int y = 1; y < BOARD_HEIGHT; ++y)
{
int y1 = y * SQUARE_SIZE.y();
scene->addLine(HALF_LINE_WIDTH, y1, scene->width() - HALF_LINE_WIDTH, y1, linePen);
}
view->setScene(scene);
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->show();
view->installEventFilter(this);
setCentralWidget(view);
}
void TestApp::resizeEvent(QResizeEvent* event)
{
view->fitInView(0, 0, scene->width(), scene->height());
QWidget::resizeEvent(event);
}
I've tried adding a call to fitInView at the end of TestApp's constructor, but it doesn't seem to do anything - resizeEvent seems to be called once at the start of the program's execution anyway.
Cheers.
Handle the view fit also inside the showEvent:
void TestApp::showEvent ( QShowEvent * event )
{
view->fitInView(0, 0, scene->width(), scene->height());
QWidget::showEvent(event);
}