How to dynamically swap QWidgets - c++

I have a QMainWindow which contains, on the left, a QTreeWidget and, on the right, another QMainWindow with windowFlags set to Qt::Widget.
This is because the QMainWindow is the only way to provide docking functionality to a QWidget. (I want the docked components to be popped out of the "real" window entirely if needed.
My problem is that I want users to be able to keep popped out dock widgets even if their item is not selected on the left.
For example, here is the global layout:
Let's say I select Item 1. On the right I will have some dockable widgets which I am able to reorder as I wish. If I pop one out to keep an eye on it, I don't want it to disappear if I select Item 2.
To go even further, I could want to show ALL Items' dockable widgets at once if I want.
My original idea to achieve this was to make each Item have its dedicated QMainWindow stored inside its data and I would just switch the right one to reflect the currently active one.
Maybe what I want is a bad idea and maybe it's not even feasible.
Could someone with some Qt knowledge tell me if I'm doing/wanting something wrong please?
Edit:
It would be perfectly fine to me if there were a way to trigger the "inner QMainWindow" pop out manually. Like, for example, a button "pop out" in the top right corner which would completely pop it and make it an entirely new window (still linked to the other one, though)
Edit2:
I's like to point out that I haven't tried anything regarding this question yet. I am essentially wondering if it fits with Qt's way of doing things.
This question made me happy about being able to provide docking capabilities to only a part of the program but I still aren't sure about what I want. Can I really achieve this?
Edit7, MVCE:
Hopefully I didn't forget anything since this is done by modifying my files.
mainwindow2.cpp
#include "mainwindow2.hh"
#include "ui_mainwindow2.h"
#include <QTreeWidgetItem>
MainWindow2::MainWindow2(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow2)
{
ui->setupUi(this);
ui->mainPanel->setWindowFlags(Qt::Widget);
QTreeWidgetItem* item = new QTreeWidgetItem;
item->setData(0, 0, QVariant::fromValue(QString("Item 1")));
ui->accountsTreeWidget->addTopLevelItem(item);
QTreeWidgetItem* item2 = new QTreeWidgetItem;
item2->setData(0, 0, QVariant::fromValue(QString("Item 2")));
ui->accountsTreeWidget->addTopLevelItem(item2);
}
MainWindow2::~MainWindow2()
{
delete ui;
}
mainwindow2.hh
#ifndef MAINWINDOW2_HH
#define MAINWINDOW2_HH
#include <QMainWindow>
namespace Ui {
class MainWindow2;
}
class MainWindow2 : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow2(QWidget *parent = 0);
~MainWindow2();
private:
Ui::MainWindow2 *ui;
};
#endif // MAINWINDOW2_HH
mainwindow2.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow2</class>
<widget class="QMainWindow" name="MainWindow2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1200</width>
<height>700</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>1200</width>
<height>700</height>
</size>
</property>
<property name="windowTitle">
<string>Main Window</string>
</property>
<widget class="QWidget" name="centralWidget">
<property name="minimumSize">
<size>
<width>1200</width>
<height>658</height>
</size>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1" rowspan="2" colspan="2">
<widget class="QMainWindow" name="mainPanel"/>
</item>
<item row="0" column="0">
<widget class="QTreeWidget" name="accountsTreeWidget">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>300</width>
<height>16777215</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectItems</enum>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
<layoutdefault spacing="6" margin="11"/>
<connections/>
</ui>
main.cpp
#include "mainwindow2.hh"
#include <QApplication>
#include <QStyleFactory>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setStyle(QStyleFactory::create("Fusion"));
MainWindow2 w;
w.show();
return app.exec();
}
project.pro
#-------------------------------------------------
#
# Project created by QtCreator 2017-11-12T23:07:49
#
#-------------------------------------------------
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = project
TEMPLATE = app
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
INCLUDEPATH += $$PWD
SOURCES += \
main.cpp \
mainwindow2.cpp
HEADERS += \
mainwindow2.hh
FORMS += \
mainwindow2.ui

In the right side, you could add a parent widget, with a QBoxLayout, attach all the items to it, and just hide, show and sort them as you wish.
Make them a custom class, with a public int variable to let you identify them later on. Store their pointers in a QList, and then just loop the list and hide or show items by your criteria ( Using the int variable ).
Example:
Items form category A -> int value 0
Items form category B -> int value 1
etc...

Related

Cannot initialise a parameter of type QMainWindow: ui->setupUi(this) error

I am new to Qt. I have from the link
https://www.bogotobogo.com/Qt/Qt5_QNetworkRequest_Http_File_Download.php
the code because I want to learn about QNewtorkRequest class.
But when I execute the code I get the error below:
here is a part of the source code:
#include "httpdownload.h"
#include "ui_httpdownload.h"
HttpDownload::HttpDownload(QWidget *parent) :
QDialog(parent),
ui(new Ui::HttpDownload)
{
ui->setupUi(this);
ui->urlEdit->setText("http://qt.com");
ui->statusLabel->setWordWrap(true);
ui->downloadButton->setDefault(true);
ui->quitButton->setAutoDefault(false);
progressDialog = new QProgressDialog(this);
connect(ui->urlEdit, SIGNAL(textChanged(QString)),
this, SLOT(enableDownloadButton()));
connect(progressDialog, SIGNAL(canceled()), this, SLOT(cancelDownload()));
}
Here is the ui file:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>HttpDownload</class>
<widget class="QMainWindow" name="HttpDownload">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>580</width>
<height>204</height>
</rect>
</property>
<property name="windowTitle">
Can you please help me?
There's your problem. The ui file expects a QMainWindow but your C++ code defines a QDialog. Choose one and fix the other.

Python Load module in parent to prevent overwrite problems

I'm building a QGIS plugin with Python and designed a GUI for it. I can compile it with pyuic4, but on load it gives an error. I found out that I can prevent that error by adding the line below in the compiled Python code. Only at some moments I have to recompile and so the file gets overwritten and the line is lost.
form.py
from qgis.gui import QgsMapLayerComboBox
I have a 'parent' file which imports the compiled version like so:
dialog.py
from form import Ui_Dialog
Is there some way to import the QgsMapLayerComboBox in dialog.py so I don't have to add it every time to form.py after I recompiled my GUI?
EDIT:
<widget class="QgsMapLayerComboBox" name="mMapLayerComboBox">
<property name="geometry">
<rect>
<x>100</x>
<y>18</y>
<width>160</width>
<height>22</height>
</rect>
</property>
<property name="filters">
<set>QgsMapLayerProxyModel::RasterLayer</set>
</property>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>QgsMapLayerComboBox</class>
<extends>QComboBox</extends>
<header>qgsmaplayercombobox.h</header>
</customwidget>
</customwidgets>
Open your form.ui with some text editor and replace:
<customwidget>
<class>QgsMapLayerComboBox</class>
<extends>QComboBox</extends>
<header>qgsmaplayercombobox.h</header>
</customwidget>
with
<customwidget>
<class>QgsMapLayerComboBox</class>
<extends>QComboBox</extends>
<header>qgis.gui</header>
</customwidget>
and compile again.

Why QSpinBox jumps twice the Step value

I have a simple python qt application in which I want to plot something dynamically via matplotlib. I was trying to follow instructions like this and that. It works as expected except for one thing, my spinbox updates twice upon mouse click and I do not understand why. Well, during the development the valueChanged.connect() worked fine until I got to a certain point. Then the update_figure() was called twice and the displayed value jumped by two instead of the desired one. I tried to make a minimum working example, you find below. Without the sleep command in update_figue my spinbox behaves normal, with sleep it is called twice and the value jumps by two, with an delay given by sleep.
Why does the sleep in update_figure influence the spinbox, or what is happening here? Is it the way I constructed the connect()?
BTW: I observed the same behaviour on a slider.
Cheers
(I do not think it matters, but this is on openSuse Leap)
EDIT: I could make the example even "more minimal".
from PyQt5.uic import loadUiType
from PyQt5.QtWidgets import QApplication, QMainWindow
import sys
from time import sleep
Ui_MainWindow, QMainWindow = loadUiType('main.ui')
class Main(QMainWindow, Ui_MainWindow):
def __init__(self, ):
super(Main, self).__init__()
self.setupUi(self)
self.spinBox_dac.valueChanged.connect(self.update_figure)
def update_figure(self):
##### The following line makes the difference!
sleep(1) #####################################
##### why?
print "damn...once or twice?"
return
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())
Here is the main.ui:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>185</width>
<height>107</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>1100</width>
<height>905</height>
</size>
</property>
<property name="windowTitle">
<string>mini</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QSpinBox" name="spinBox_dac">
<property name="geometry">
<rect>
<x>80</x>
<y>40</y>
<width>61</width>
<height>28</height>
</rect>
</property>
<property name="minimum">
<number>3</number>
</property>
<property name="maximum">
<number>20</number>
</property>
<property name="singleStep">
<number>1</number>
</property>
<property name="value">
<number>4</number>
</property>
</widget>
</widget>
</widget>
<resources/>
<connections/>
</ui>
The sleep() call prevent qt to process the mouse-release and the QSpinBox "thinks" that the mouse is pressed long enough to increase/decrease the value again (auto-repeat).
See https://bugreports.qt.io/browse/QTBUG-33128
A solution could be:
def update_figure(self):
for _ in xrange(10):
QApplication.processEvents()
sleep(0.1)
print "Only once :-)"
Anyway it's not a good practice to block the UI while performing long operations, the work is usually delegated to another thread.
EDIT
This affect Qt4 and Qt5 both, but it behave a bit differently.
There is no setAutoRepeat because in the QAbstrectSpinBox this behavior is implemented using styles, while in QAbstractButton is implemented with a timer.
The way around this is to create a ProxyStyle and set a very long "auto-repeat" interval
from PyQt5.uic import loadUiType
from PyQt5.QtWidgets import QApplication, QMainWindow, QProxyStyle, QStyle
import sys
from time import sleep
Ui_MainWindow, QMainWindow = loadUiType('main.ui')
class CustomStyle(QProxyStyle):
def styleHint(self, hint, option=None, widget=None, returnData=None):
if hint == QStyle.SH_SpinBox_KeyPressAutoRepeatRate:
return 10**10
elif hint == QStyle.SH_SpinBox_ClickAutoRepeatRate:
return 10**10
elif hint == QStyle.SH_SpinBox_ClickAutoRepeatThreshold:
# You can use only this condition to avoid the auto-repeat,
# but better safe than sorry ;-)
return 10**10
else:
return super().styleHint(hint, option, widget, returnData)
class Main(QMainWindow, Ui_MainWindow):
def __init__(self, ):
super(Main, self).__init__()
self.setupUi(self)
self.spinBox_dac.setStyle(CustomStyle())
self.spinBox_dac.valueChanged.connect(self.update_figure)
def update_figure(self):
sleep(1)
print "Only once!"
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())

SDL2 Rendering into QWidget

I'm looking to port an emulator I wrote in GTK2 and SDL1.2 into using QT5 and SDL2.0.3. So far thats been fine as far as the emulator all works in SDL2 independantly and the GUI works fine in QT, with them both interacting. My issue is with trying to get SDL2 to appear inside a QWidget. This is something that worked fine in SDL1.2 using the window hack method.
From everything I've read online, this can be down with the SDL_CreateWindowFrom function getting the winId() from a QT Widget and passing it into it. However this seems to be for when you are using openGL.
What I want to know is how to do this when im only using SDL2's standard drawing functions to a texture, no OpenGL. All my attempts so far have failed and SDL just didn't appear in the widget at all.
Alternatively, I know you can convert an SDL_Suface to a QImage. How would I then go about getting the QT Widget to update everytime the QImage is updated (60fps). The complication with this is QT and SDL are running in seperate threads.
If anyone has some idea of a direction to point me or some useful example codes that may lead to an answer I would be very greatful. I havn't provided code because I'm not yet sure how to do it, to provide any code.
EDIT
So after some trialing with SDL and QT. I have managed to get SDL2 to draw into a QWidget using the its Standard Drawing tools. However this only works when setting the renderer to SDL_RENDERER_SOFTWARE. If i try and use SDL_RENDERER_ACCELERATED like I would perfer too. It results in the QT window locking up completely and failing to draw anything (including all other widgets in the window), and finally being terminated as not responding by the operating system (Kubuntu in my testing case).
Does any one have any leads as to why this may be. I can use SDL_RENDERER_ACCELERATED fine when SDL2 is drawing into its own window created with SDL_CreateWindow and SDL_CreateRenderer. Its only a problem when drawing to a QWidget.
Here is the code I have so far:
main.cpp
#include "mainwindow.h"
#include <QApplication>
#include <SDL2/SDL.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* window = SDL_CreateWindowFrom((void*)w.ptr_gfx->winId());
SDL_Renderer* render = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
SDL_SetRenderDrawColor(render, 255, 0, 0, 255);
SDL_RenderFillRect(render, NULL);
SDL_RenderPresent(render);
return a.exec();
SDL_DestroyWindow(window);
SDL_DestroyRenderer(render);
SDL_Quit();
}
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ptr_gfx = ui->gfx;
ui->gfx->setUpdatesEnabled(false);
}
MainWindow::~MainWindow()
{
delete ui;
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
Ui::MainWindow *ui;
QWidget* ptr_gfx;
};
#endif // MAINWINDOW_H
mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralWidget">
<widget class="QWidget" name="gfx" native="true">
<property name="geometry">
<rect>
<x>20</x>
<y>10</y>
<width>191</width>
<height>131</height>
</rect>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>20</height>
</rect>
</property>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<widget class="QStatusBar" name="statusBar"/>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>
When QImage is updated with new data, simply call the widget's repaint() method to make it paint itself on the screen.

Qt - Using QUiLoader to load a widget with no parent

I have just started learning Qt and I am trying to create a simple widget using this QUiLoader. But I am getting this error : "Designer: An error has occurred while reading the UI file at line 1, column 0: Premature end of document."
#include <QtGui/QApplication>
#include <QtUiTools/QUiLoader>
#include <QFile>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QUiLoader loader;
QFile file(":/aks.ui");
file.open(QFile::ReadOnly);
QWidget *myWidget = loader.load(&file);
if(myWidget){
myWidget->show();
}
return a.exec();
}
I constructed the ui file using QtCreator 2.4.1 and I am on Qt 4.7.4. Check out the ui file too.
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>131</width>
<height>129</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="checkBox">
<property name="text">
<string>A</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_2">
<property name="text">
<string>B</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_3">
<property name="text">
<string>C</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_4">
<property name="text">
<string>D</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_5">
<property name="text">
<string>E</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
My project file:
#-------------------------------------------------
#
# Project created by QtCreator 2012-05-21T19:48:31
#
#-------------------------------------------------
QT += core gui
TARGET = Example
TEMPLATE = app
SOURCES += main.cpp \
sortdialog.cpp
HEADERS += \
sortdialog.h
FORMS += \
sortdialog.ui \
aks.ui
CONFIG += uitools
You need to add your .ui file to the resources of your project. Resources are files which get "compiled inside" of your app and are then available to Qt classes by file paths starting with ":/".
In order to add resources to your project, you need to create a resource file listing the files you want to register as resources. This file will be another XML file and can be created and edited by QtCreator. In the project manager, add another file and select the type Qt -> Qt resource file from within the dialog.
In your .pro file then appears a section:
RESOURCES += \
resources.qrc
In the resource file you need to add a prefix; just name it / (or leave it empty). Within this prefix you can add the file aks.ui so it will be named :/aks.ui.
Note that this type of UI creation takes place in runtime. That means, it is more flexible (maybe the ui file gets created only at runtime), but a little bit slower (since parsing and some more runtime processing takes place).
If you're new to Qt, you probably don't know that you can also let Qt create a class file for your ui file in the build process. This is already done when you list your ui file in the pro file in the FORMS += section.
To use the automatically built class, you should also have created a designer form class, which is another class where you put your own code inside. This class will load the automatically built class to setup your ui.
So there are two classes:
* The automatically generated class for your ui file, called Ui::Aks (in a namespace Ui), found in the file ui_aks.h in the build folder.
* Your own wrapper class; the acutal widget class, which uses the ui class.
If you want to create the second class manually, you can write (QtCreator actually does exactly this step when you add a designer form class instead of only a designer form):
aks.h:
#ifndef AKS_H
#define AKS_H
#include <QWidget>
namespace Ui {
class Aks; // <-- this is the automatically created ui class which we need
}
class aks : public QWidget // <-- your own widget class using the designer UI
{
Q_OBJECT
public:
explicit Aks(QWidget *parent = 0);
~Aks();
private:
Ui::Aks *ui; // <-- instance of the automatically created ui class
};
#endif // AKS_H
aks.cpp:
#include "aks.h"
#include "ui_aks.h" // <-- include the automatically generated ui class
Aks::Aks(QWidget *parent) :
QWidget(parent),
ui(new Ui::Aks)
{
ui->setupUi(this); // <-- here, the widgets from the ui file get created
}
Aks::~Aks()
{
delete ui;
}