Using Custom Widgets in Qt Designer for PySide6 MDI application - python

I refer to the PySide MDI example in PySide project repository at https://github.com/pyside/Examples/blob/master/examples/mainwindows/mdi/mdi.py. I tried to adopt the same to PySide6 after changing the import reference to from PySide to PySide6. It is working fine in current form.
After making sure that the example works with PySide6, I made following additions:
Implemented a new Widget in Qt Designer (using exe installed in project VE using pip install pyside6) with just one QTextEdit widget on this form. The python code generated by designer is as below:
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'FormWidget01uyjQCJ.ui'
##
## Created by: Qt User Interface Compiler version 6.4.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QSizePolicy, QTextEdit, QWidget)
class Ui_FormWidget01(object):
def setupUi(self, FormWidget01):
if not FormWidget01.objectName():
FormWidget01.setObjectName(u"FormWidget01")
FormWidget01.resize(700, 500)
self.textEdit = QTextEdit(FormWidget01)
self.textEdit.setObjectName(u"textEdit")
self.textEdit.setGeometry(QRect(10, 10, 681, 481))
self.retranslateUi(FormWidget01)
QMetaObject.connectSlotsByName(FormWidget01)
# setupUi
def retranslateUi(self, FormWidget01):
FormWidget01.setWindowTitle(QCoreApplication.translate("FormWidget01", u"Form Widget", None))
# retranslateUi
In the mdi.py application, I added the following class for newly generated child on pattern of class MdiChild(QTestEdit) in the above example:
class FormWidget01(Ui_FormWidget01):
def __init__(self):
super(FormWidget01, self).__init__()
self.setAttribute(Qt.WA_DeleteOnClose)
self.isUntitled = True
Finally, I updated class MainWindow(QMainWindow) as below:
Added new function on pattern of newfile(self):
def newFw01(self):
_fw01 = self.createFormWidget01()
_fw01.show()
Added new function on pattern of createMdiChild(self):
def createFormWidget01(self):
fw01 = FormWidget01()
fw01.resize(400, 300)
self.mdiArea.addSubWindow(fw01)
return fw01
And updated createActions(self):
self.newFw01Act = QAction(QIcon(':/images/new.png'), "&New FW01", self,
shortcut=QKeySequence.New, statusTip="Create a new FW",
triggered=self.newFw01)
When I run the updated program and click action for new widget, I get the following error:
File "S:\***\newmdi\mdiapp.py", line 61, in __init__
self.setAttribute(Qt.WA_DeleteOnClose)
AttributeError: 'FormWidget01' object has no attribute 'setAttribute'
At this point, I realized that C++ code generated by the Designer initiate form is like this:
class Ui_FormWidget01
{
public:
QTextEdit *textEdit;
void setupUi(QWidget *FormWidget01)
{
if (FormWidget01->objectName().isEmpty())
FormWidget01->setObjectName("FormWidget01");
FormWidget01->resize(700, 500);
textEdit = new QTextEdit(FormWidget01);
textEdit->setObjectName("textEdit");
...
If I change the python UI class from class Ui_FormWidget01(object): to class Ui_FormWidget01(QWidget):, replacing object with QWidget, the action works but produces a minimized and blank instance of Ui_FormWidget01. There is no QTextEdit visible on it.
I'm not pro at coding and not sure what is going wrong.
In partcular, I need to understand if:
It is possible to create a custom Widget in Qt Designer and use it as MdiChild in Qt MDI application Framework?
If a child widget initialized with QTextEdit works as MDI child but not a child with initialized with object, what could be restrictions on type of an MDI child?
Any clue or explanation is welcome.

Related

PyQt6 Accessing UI Object Attributes

I'm trying to create a simple GUI in Python using PyQt6 and Qt Designer.
I've already made the .ui file, and am able to load it properly and have it shown when running the code.
I can't, however, seem able to access any of the objects, i.e buttons and such.
When I print out the children of the AppWindow object I do see all of them listed, but I can't change their attributes via self.button (inside the class init function)- it says 'unresolved attribute reference'.
To my understanding it's supposed to be something trivial, so I'm not quite sure what I had managed to get wrong here.
Would appreciate any help since I'm stumped.
Thank you.
Here's my code-
import sys
from PyQt6.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout
from PyQt6.QtCore import Qt
from PyQt6 import uic
class AppWindow(QWidget):
def __init__(self):
super().__init__()
uic.loadUi('gui.ui', self)
self.setWindowTitle('API-SF App')
if __name__ == '__main__':
app = QApplication(sys.argv)
app_window = AppWindow()
app_window.show()
sys.exit(app.exec())

PySide6 app crashes when using QPainter.drawLine()

On Windows 10, python3.10, PySide6 (or PyQt6) QApplication crashes when calling QPainter.drawLine() .
The terminal just displays :
Process finished with exit code -1073741819 (0xC0000005)
Please find below the code:
import sys
from PySide6.QtCore import QPoint, Qt
from PySide6.QtGui import QColor, QPainter, QPen, QPixmap
from PySide6.QtWidgets import QApplication, QLabel, QMainWindow
# from PyQt6.QtCore import QPoint, Qt
# from PyQt6.QtGui import QColor, QPainter, QPen, QPixmap
# from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.label = QLabel()
canvas = QPixmap(400, 300)
canvas.fill(Qt.GlobalColor.white)
self.label.setPixmap(canvas)
self.setCentralWidget(self.label)
self.draw_something()
def draw_something(self):
painter = QPainter(self.label.pixmap())
painter.drawLine(10, 10, 300, 200) # >=========== Crash Here
painter.end()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
This is caused by a slight (and not well documented) change in the API that happened starting with Qt5.15.
Until Qt5, pixmap() returned a direct pointer to the current pixmap of the label, while in Qt6 it returns an implicit copy of the pixmap. The difference is highlighted only for the latest Qt5 documentation of the pixmap() property:
Previously, Qt provided a version of pixmap() which returned the pixmap by-pointer. That version is now deprecated. To maintain compatibility with old code, you can explicitly differentiate between the by-pointer function and the by-value function:
To Python developers that's not obvious, but to C++ it's clear by the change of the const QPixmap * (note the asterisk, meaning that it's a pointer) to a pure QPixmap type, meaning that the returned object is a new QPixmap object based on the current pixmap, and not a reference to the pixmap object currently set for the label.
Now, the fact is that, conceptually speaking, we should not be able to directly "live draw" on the current pixmap of the label, because:
setPixmap() always creates a copy of the pixmap for the label;
due to the point above, there is no point in sharing the same pixmap object among different labels, so, as a consequence, pixmap() should always return a copy of that object;
Previously, it was possible to directly paint on a QLabel's pixmap (while ensuring that update() was immediately called on the label). The current API instead requests an explicit call to setPixmap() after drawing.
So, the solution is to create a reference to the pixmap as long as it's needed:
def draw_something(self):
pm = self.label.pixmap()
painter = QPainter(pm)
painter.drawLine(10, 10, 300, 200)
painter.end()
self.label.setPixmap(pm)

How to load a Qt Designer file (.ui) in a QWizardPage using PySide2

I want to design my QWizardPages in Qt Designer and I want to load them into my Python program with PySide2. Previously I have been using PyQt5 without any problems but making the switch to PySide2 seems harder then expected.
The problem I am facing is that when I am adding a QWizardPage to my QWizard , the page is indeed added to the Wizard, but also an other (empty) page is added. I'm not able to find what I'm doing wrong so I was wondering if someone can have a look.
I have tried to add the pages with both the functions addPage() and setPage(), but they give the same results. What I also noticed is that when I explicitely set the Title of the page with setTitle(), the empty (unwanted) page gets this title, but not the page I designed in Qt Designer.
import os
import sys
from PySide2.QtWidgets import QWizard, QWizardPage, QApplication
from PySide2.QtCore import QFile
from PySide2.QtUiTools import QUiLoader
from enum import Enum
class MyWizard(QWizard):
def __init__(self):
super().__init__()
self.setPage(PageNumbers.page_one.value, PageOne(self))
class PageOne(QWizardPage):
def __init__(self, parent):
super().__init__(parent)
ui_file = os.path.join(__file__, '..', 'pageOne.ui')
file = QFile(ui_file)
file.open(QFile.ReadOnly)
loader = QUiLoader()
loader.load(file, parent)
file.close()
self.setTitle("This is another test Title")
class PageNumbers(Enum):
page_one = 1
if __name__ == '__main__':
app = QApplication(sys.argv)
wizard = MyWizard()
wizard.show()
app.exec_()
What I would expect is to have just one QWizardPage showing up with directly the Finish button. Instead I get two QWizardPages as shown in this image:
Can someone tell me what's going on?
(I get the expected result using PyQt5 with the following code: https://pastebin.com/6W2sx9M1)
The developers of PyQt implement functions to be able to create classes based on the .ui that is not implemented in Qt by default (Qt/C++ uses the MOC to do this work), but in the case of PySide2-Qt for python it does not implement it, only has the QUiLoader class that allows to create a widget based on the .ui unlike PyQt that allows filling a class.
In conclusion there is no equivalent in PySide2 of the loadUi function so you can not implement the same logic. PySide2 is not PyQt5, there are own equivalences since they use the same base but they have implementations, limitations and advantages.
Going to the practical problem, considering that the .ui is next to the .py the solution is the following:
import os
import sys
from PySide2 import QtCore, QtWidgets, QtUiTools
from enum import Enum
class PageNumbers(Enum):
page_one = 0
class MyWizard(QtWidgets.QWizard):
def __init__(self):
super().__init__()
ui_file = os.path.join(os.path.dirname(os.path.abspath(__file__)) ,'PageOne.ui')
page_one = create_widget(ui_file, self)
self.setPage(PageNumbers.page_one.value, page_one)
def create_widget(filename, parent=None):
file = QtCore.QFile(filename)
if not file.open(QtCore.QFile.ReadOnly):
return
loader = QtUiTools.QUiLoader()
widget = loader.load(file, parent)
return widget
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
wizard = MyWizard()
wizard.show()
sys.exit(app.exec_())

pyqt5, Receiving AttributeError: 'QMainWindow' object has no attribute 'browseSlot'

I'm learning pyqt5, and specifically how to use it with the QT Designer. I'm sort of following the turorial HERE. However in this tutorial they are converting the XML interface to Python code with pyuic5, while I'm trying to import it dynamically with uic.loadUi("myui.ui"). In the tutorial we define a slot with the signals and slot editor named " browseSlot".
When I try to run/compile, at the line
dlg = uic.loadUi("myui.ui")
I get the error:
AttributeError: 'QMainWindow' object has no attribute 'browseSlot'
I think what's going on is that QT Designer connects a signal to the slot 'browseSlot' but because a 'browseSlot' method isn't defined in the myui.ui, the error is thrown, because there is no way for the interpreter to know I'm referring to a method that is outside the UI interface file. (In this case, in the module that loads the interface). As far as I can tell QT Designer only lets me connect signals to slots, not define a whole new one. I think that way this is handled in other frameworks is that there will be an abstract method that needs over riding. So what can I do in this situation to make it work?
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtCore import QObject, pyqtSlot
import sys
app = QtWidgets.QApplication([])
dlg = uic.loadUi("myui.ui")
#pyqtSlot
def returnPressedSlot():
pass
#pyqtSlot
def writeDocSlot():
pass
#pyQt
def browseSlot():
pass
dlg.show()
sys.exit(app.exec())
The slots belong to the class that is used returns loadUi(), they are not any functions since they do not magically not connect them, if you want to use loadUi() and implement these methods you must inherit from the class corresponding to the template that you used, in the example of the link Main Window was used so it must be inherited from QMainWindow:
from PyQt5 import QtCore, QtGui, QtWidgets, uic
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
uic.loadUi("mainwindow.ui", self)
#QtCore.pyqtSlot()
def returnPressedSlot():
pass
#QtCore.pyqtSlot()
def writeDocSlot():
pass
#QtCore.pyqtSlot()
def browseSlot():
pass
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
try this out
from PyQt5 import QtWidgets, uic
app = QtWidgets.QApplication([])
form = uic.loadUi("login.ui")
form2.show()
app.exec()
the above python code should display your gui app properly as long as you have install PyQt5 and PyQt5-tools,if you haven't then open CMD and typeenter code here "pip install PyQt5" and click enter.once installation is done type "pip install PyQt5-tools" then you are good to go

How to add a QVideoWidget in Qt Designer?

I want to insert video in blue box(ui image) but I don't know how to insert video file.
My code is here.
I don't know how to add video... Just know example that make video player ...
import sys
from PyQt5 import QtWidgets
from PyQt5 import QtGui
from PyQt5 import uic
from PyQt5 import QtCore
from PyQt5.QtCore import QDir, Qt, QUrl, pyqtSlot
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtWidgets import (QApplication, QFileDialog, QHBoxLayout, QLabel,
QPushButton, QSizePolicy, QSlider, QStyle, QVBoxLayout, QWidget)
dir_audience=''
dir_movie = ''
dir_export = ''
select_emotion = 'happy'
class Form(QtWidgets.QDialog):
def __init__(self, parent=None):
QtWidgets.QDialog.__init__(self, parent)
self.ui = uic.loadUi("highlight_export_form.ui", self)
self.ui.show()
self.ui.load_audience.clicked.connect(self.load_audience_clicked)
self.ui.load_movie.clicked.connect(self.load_movie_clicked)
self.ui.start_recog.clicked.connect(self.start_recog_clicked)
self.ui.radio_happy.toggled.connect(self.on_radio_button_toggled)
self.ui.radio_surprised.toggled.connect(self.on_radio_button_toggled)
def load_audience_clicked(self, event):
dir_audience, _ = QFileDialog.getOpenFileName(self, "Open Audience", QDir.homePath())
self.path_audience.setText(dir_audience)
def load_movie_clicked(self, event):
dir_movie, _ = QFileDialog.getOpenFileName(self, "Open Movie", QDir.homePath())
self.path_movie.setText(dir_movie)
def start_recog_clicked(self, event):
self.check_1.setText("start_recognition")
def on_radio_button_toggled(self):
if self.radio_happy.isChecked():
select_emotion='happy'
self.check_3.setText(select_emotion)
elif self.radio_surprised.isChecked():
select_emotion='surprised'
self.check_3.setText(select_emotion)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Form()
sys.exit(app.exec())
Thank you for reading my question.
Qt Designer does not show all the Qt widget, and often we want to add our own widget through Qt, for that there are at least 2 solutions, the first is to create a plugin and load it to Qt Designer, and the other is simpler. promote the widget, the latter is what I will show in this answer.
For this you must make certain minimum changes, I do not know what type of widget is the one you use in the blue box but you must change it to the Widget type that is in the sub-menu of the containers as shown in the following image:
after them you must right click on the widget and select Promote to ..., then a dialogue will appear, in the part of Promoted class name you must place QVideoWidget, and in the part of Header File you must place PyQt5.QtMultimediaWidgets, then press the add button and then Promote:
After that you will be able to use QVideoWidget within your application.
In the following link there is an example
Answer from here was clearer to me:
QWebKit was removed in Qt 5.6. So QWebView is no longer available. Use QWebEngineView as a replacement. In Qt Designer, just add a QWidget to your form and promote it to QWebEngineView (base class: QWidget, header: QWebEngineView). Don't forget to add webenginewidgets to your project file.
Simlar issue: want add QWebEngineView into Qt Designer
for later PySide6 to import and use .ui, exported by Qt Designer
Solution: add QWidget then Promoted to QWebEngineView
Steps
drag a new QWidget into your main ui (window)
right click QWidget -> Promoted to
new popup window, input
Base class Name: QWidget
Promoted class Name: QWebEngineView
Header File: PySide6.QtWebEngineWidgets
== parent class
Global Include: not selected
-> Screenshot
click: Add
click: Promote

Categories

Resources