Currently working on a Python program that uses Qt Widgets from an .ui file to display an interactable GUI. However, i have not found a way to integrate the QQuickview widget to display any QML code, which i have read is possible.
I'm using PySide2 to convert the .ui file from Qt Designer and have both attempted to use the QQuickWidget found in Qt Designer, and manualy adding a QQuickView to the gridLayout in the .ui to no success.
The QQuickWidget i added in Qt Designer was, as far as i could tell transformed to a QWidget when run in python, so setSource(QUrl) or .load(QUrl) Made no sense when running the code.
My attempt at adding the QQuickView:
def connect_map_click(self):
# Function for handling the connect map button
engine = QQuickView()
ctx = engine.rootContext()
url = QUrl.fromLocalFile('QMLtest.qml')
engine.setSource(url)
container = QWidget.createWindowContainer(engine, self)
container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
engine.show()
self.window.grd_map.addWidget(container, 0, 0)
The QML file:
import QtQuick 2.7
Rectangle {
id: rectangle
color: "red"
width: 200
height: 200
visible: true
Text {
id:text
text: "It's working!"
}
}
I'm attempting to run the qml window on the right side of the screen, shown below.
Solved it myself as one tends to do right after asking for help.
Ended up finding out that i had not imported the QQuickWidget to the Python file before. So my solution ended up being to create a QQuickWidget in python, setting the source to the qml file and adding it to the grid in the .ui GUI.
def connect_map_click(self):
# Function for handling the connect map button
qml_widget = QtQuickWidgets.QQuickWidget()
qml_widget.setSource(QUrl('QMLtest.qml'))
self.window.grd_map.addWidget(qml_widget)
resulting GUI:
Explanation:
QQuickView is a local variable that will be deleted when "connect_map_click" is finished executing nothing in the container.
Solution:
The solution is to extend the life cycle and for this there are the following alternatives:
Pass the QWindow associated with the window as parent:
def connect_map_click(self):
engine = QQuickView(self.window.grd_map.parentWidget().window().windowHandle())
# ...
Make the QQuickView an attribute of another object that has a longer life cycle, for example the container:
# ...
container = QWidget.createWindowContainer(engine, self)
container.engine = engine
# ...
or the self:
def connect_map_click(self):
# Function for handling the connect map button
self.engine = QQuickView()
ctx = self.engine.rootContext()
url = QUrl.fromLocalFile('QMLtest.qml')
self.engine.setSource(url)
container = QWidget.createWindowContainer(self.engine, self)
container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
engine.show()
self.window.grd_map.addWidget(container, 0, 0)
Notes:
As you point out another solution is to use QQuickWidget since its life cycle depends on your parent who is self so he will live as long as the class. But QQuickWidget has limitations as the docs points out, including that you will not be able to record items that may be one of your requirements.
This behavior happens in PySide2 but in PyQt5 your initial code works since the container passes as parent to the QWindow of the window.
Related
I'm trying to create a script that makes API calls to a website to get info (via the requests module).
The user can then manipulate the website using my script/gui.
My app's main window will have several buttons at the bottom which acts like tabs to switch between windows(let's say for argument's sake 2 buttons to switch between two windows).
When certain changes are made, I need the QStackedWidget to refresh all the widgets/labels in certain windows that are not currently being displayed.
Summary of my code:
# Global Variables
app = QApplication(sys.argv)
win = QtWidgets.QStackedWidget()
response = {} # global dict to store API response from website
class UiWindowOne(QMainWindow):
# This window mostly shows information that I get from the website.
def __init__(self):
super(UiWindowOne, self).__init__()
self.setup_ui(self)
self.retranslate_ui(self)
# Then I map buttons to methods
def setup_ui(self, WindowOne):
# This was generated by QT Designer and places widgets
def retranslate_ui(self, WindowOne):
# This was generated by QT Designer and places widgets
def refresh(self):
'''
This function refreshes the current window. Basically, I put everything in the __init__ function in here (except "super(UiWindowOne, self).__init__()".
:return: None
'''
self.setup_ui(self)
self.retranslate_ui(self)
# Also map buttons to methods
class UiWindowTwo(QMainWindow):
def __init__(self):
super(UiWindowTwo, self).__init__()
self.setup_ui(self)
self.retranslate_ui(self)
# Then I map buttons to methods
def setup_ui(self, WindowTwo):
# This was generated by QT Designer
def retranslate_ui(self, WindowTwo):
# This was generated by QT Designer
def refresh(self):
'''
This function refreshes the current window. Basically, I put everything in the __init__ function in here (except "super(UiWindowTwo, self).__init__()".
:return: None
'''
self.setup_ui(self)
self.retranslate_ui(self)
# Also map buttons to methods
def update_website(self):
# Make changes to website
# After changes were made, I want to get modified info from the website and re-initialize/refresh both windows to reflect the changes made.
# I can easily call self.refresh() to refresh WindowTwo. But I cannot refresh WindowOne from here.
def main():
# Here I make API calls to the Website to get info/images
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(".\\imgs/static/A.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
win.setWindowIcon(icon)
win.setWindowTitle("NAME")
first_window = UiWindowOne()
second_window = UiWindowTwo()
win.addWidget(first_window)
win.addWidget(second_window)
win.setGeometry(250, 250, 820, 854)
win.setFixedWidth(820)
win.setFixedHeight(854)
win.show()
sys.exit(app.exec())
if __name__ == '__main__':
main()
I have tried doing a "first_window.refresh()" under the update_website() function in UiWindowTwo, but then python tells me that first_window is not defined.
I then tried making first_window and second_window global variables, but then I ended up reordering my whole script and couldn't get it to run.
I'm trying to create an application that contains a web browser within it, but when I add the web browser my menu bar visually disappears but functionally remains in place. The following are two images, one showing the "self.centralWidget(self.web_widget)" commented out, and the other allows that line to run. If you run the example code, you will also see that while visually the entire web page appears as if the menu bar wasn't present, you have to click slightly below each entry field and button in order to activate it, behaving as if the menu bar was in fact present.
Web Widget Commented Out
Web Widget Active
Example Code
import os
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtWebEngineWidgets import *
class WebPage(QWebEngineView):
def __init__(self, parent=None):
QWebEngineView.__init__(self)
self.current_url = ''
self.load(QUrl("https://facebook.com"))
self.loadFinished.connect(self._on_load_finished)
def _on_load_finished(self):
print("Url Loaded")
class MainWindow(QMainWindow):
def __init__(self, parent=None):
# Initialize the Main Window
super(MainWindow, self).__init__(parent)
self.create_menu()
self.add_web_widet()
self.show()
def create_menu(self):
''' Creates the Main Menu '''
self.main_menu = self.menuBar()
self.main_menu_actions = {}
self.file_menu = self.main_menu.addMenu("Example File Menu")
self.file_menu.addAction(QAction("Testing Testing", self))
def add_web_widet(self):
self.web_widget = WebPage(self)
self.setCentralWidget(self.web_widget)
if __name__ == "__main__":
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.showMaximized()
sys.exit(app.exec_()) # only need one app, one running event loop
Development Environment
Windows 10, PyQt5, pyqt5-5.9
EDIT
The problem doesn't seem to be directly related to the menu bar. Even removing the menu bar the issue still occurs. That said, changing from showMaximized() to showFullScreen() does seem to solve the problem.
I no longer believe this is an issue with PyQt5 specifically but rather a problem with the graphics driver. Specifically, if you look at Atlassian's HipChat application it has a similar problem which is documented here:
https://jira.atlassian.com/browse/HCPUB-3177
Some individuals were able to solve the problem by running the application from the command prompt with the addendum "--disable-gpu" but that didn't work for my python application. On the other hand, rolling back the Intel(R) HD Graphics Driver did solve my problem. Version 21.20.16.4627 is the one that seems to be causing problems.
I'm writing a GUI in PyQt4 (and migrating to PyQt5). This is how I start my GUI:
if __name__== '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Fusion')) # <- Choose the style
myGUI = MyMainWindow("First GUI")
app.exec_()
Default Styles in PyQt4 :
Apparently, PyQt4 has the following styles:
'Windows'
'WindowsXP'
'WindowsVista'
'Motif'
'CDE'
'Plastique'
'Cleanlooks'
Default Styles in PyQt5 :
PyQt5 has the following styles:
'Windows'
'WindowsXP'
'WindowsVista'
'Fusion'
Custom styles?
None of these styles has proper support for HiDpi displays (4k and the like). For example, scrollbars are too small( see this post: How to resize the scrollbar from a QTextEdit in PyQt?). And I didn't even mention the problems for those people with unsharp eyesight..
Do you know a style (preferably open-source) that provides good support for 4k displays or for people with eyesight problems?
If so, how can one download this style, and install it?
Thank you so much.
I got the answer (or let's say a workaround) through another question:
How to make Icon in QMenu larger (PyQt)?
The most straightforward way to create a new QStyle is deriving it from an existing one. PyQt provides the QProxyStyle class for that purpose. Below is the example that I've also given in the How to make Icon in QMenu larger (PyQt)? question. In this example, a custom QStyle is created (derived from "Fusion" style), and the custom style provides very big icons for the QMenu.
import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
# Create a custom "QProxyStyle" to enlarge the QMenu icons
#-----------------------------------------------------------
class MyProxyStyle(QProxyStyle):
pass
def pixelMetric(self, QStyle_PixelMetric, option=None, widget=None):
if QStyle_PixelMetric == QStyle.PM_SmallIconSize:
return 40
else:
return QProxyStyle.pixelMetric(self, QStyle_PixelMetric, option, widget)
# This is the main window class (with a simple QMenu implemented)
# ------------------------------------------------------------------
class TestWindow(QMainWindow):
def __init__(self):
super(TestWindow, self).__init__()
# 1. Set basic geometry and color.
self.setGeometry(100, 100, 400, 400)
self.setWindowTitle('Hello World')
palette = QPalette()
palette.setColor(QPalette.Window, QColor(200, 200, 200))
self.setPalette(palette)
# 2. Create the central frame.
self.centralFrame = QFrame()
self.centralFrame.setFrameShape(QFrame.NoFrame)
self.setCentralWidget(self.centralFrame)
# 3. Create a menu bar.
myMenuBar = self.menuBar()
fileMenu = myMenuBar.addMenu("&File")
testMenuItem = QAction(QIcon("C:\\my\\path\\myFig.png"), "&Test", self)
testMenuItem.setStatusTip("Test for icon size")
testMenuItem.triggered.connect(lambda: print("Menu item has been clicked!"))
fileMenu.addAction(testMenuItem)
# 4. Show the window.
self.show()
# Start your Qt application based on the new style
#---------------------------------------------------
if __name__== '__main__':
app = QApplication(sys.argv)
myStyle = MyProxyStyle('Fusion') # The proxy style should be based on an existing style,
# like 'Windows', 'Motif', 'Plastique', 'Fusion', ...
app.setStyle(myStyle)
myGUI = TestWindow()
sys.exit(app.exec_())
Just copy-paste the code snippet, and paste it in a *.py file. Of course, you should replace the path to the icon with a valid path on your local computer. Just provide a complete path ("C:..") to be 100% sure that Qt finds the icon drawing.
Try it out, and you should get the following window:
My question is tough to explain but I am trying my best. Please help me in this regard.
I designed a gui in QtDesigner and converted .ui files into .py e.g. main_window.py. Now in order to avoid changing in main_window.py I created another class for listeners.
class Main():
window = None
app = None
def __init__(self):
self.launch()
self.attach_listener()
self.execute()
''' Launch GUI '''
def launch(self):
self.app = QtGui.QApplication(sys.argv)
self.window = Ui_MainWindow()
self.window.show()
''' Execute Window '''
def execute(self):
sys.exit(self.app.exec_())
''' Attach Listeners '''
def attach_listener(self):
self.window.add_button.clicked.connect(self.add_listener)
self.window.delete_button.clicked.connect(self.delete_listener)
self.window.update_button.clicked.connect(self.update_listener)
self.window.connect(self.window.combo_box, QtCore.SIGNAL('activated(QString)'), self.logout_listener)
I have another child_window.py with same structure, but I can't open that window from this one because of QApplication. I searched for the answer but couldn't apply on my code. Those answers were applicable when class was extending from QtGui.QMainWindow or QtGui.QWidget, but my scenario is different.
You are mixing up the Ui_MainWindow object with the actual window object (QMainWindow, QDialog,QWidget etc.) self.window = Ui_MainWindow() doesn't do anything because the class you are attaching it to is not a Window. You need to create a window and apply the Ui_MainWindow to it.
Apparently you can make this work, but it doesn't look pretty. You need to access your widgets via findChild. The only benefit I can see is that you don't run pyside-uic after changing a form in the designer, and that's pretty easy.
The easier way
When you use pyuic / pyside-uic it converts the .ui files to .py files.
You shouldn't edit the .py's as they will be overwritten the next time you use QtDesigner. You need to create a window class and apply the UI class to it.
Setting up a new form
Produce the file mainWinui.ui in QtDesigner - Class name MainWindow
Convert with pyside-uic -o mainWinui.py mainWinui.ui. mainWinui.py is never edited by hand
Create mainWinClass.py to load the ui.py and do all the custom UI work
Declare signals and slots, use .connect etc. in the mainWinClass.py
Some of these module names might seem a bit awkward, but I've settled on them because in the past I've had trouble with name clashes between modules and classes; caused by me not understanding how Qt Designer was going to handle it's naming.
If you look at the file that pyside-uic created, the top of it contains the correct class and method names that you need to use in your mainWinClass.py
mainWinui.py
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'mainWinui.ui'
#
# Created: Sun Feb 7 14:22:09 2016
# by: pyside-uic 0.2.15 running on PySide 1.2.4
#
# WARNING! All changes made in this file will be lost!
from PySide import QtCore, QtGui
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
Create a new mainwinClass.py and copy the correct imports and class names to it, plus a bit of boilerplate to load the .ui.
It looks like this:
mainWinClass.py
from mainWinui import Ui_MainWindow
from PySide import QtGui
class MainWin(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.setup_signals()
# Is this is the same as your Listeners ??
def setup_signals(self):
# Signal for updating the lineedits from the grid
self.ui.tabWidget.currentChanged.connect(self.onTabChanged)
# Connect the "Add Staff" button to the addStaffMember method
self.ui.btnAddStaff.clicked.connect(self.addStaffMember)
Then use another file to launch the app itself, and to maintain some non GUI aspects of the app, like updaters or global logging.
I've seen code where all the child windows are instantiated in here to, but I don't (normally) do it that way. I keep them in the main form instead. It depends on how you intend to design the app.
appname.py
from PySide import QtGui, QtCore
from mainwinClass import MainWin
import sys
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mainWin = MainWin()
mainWin.show()
sys.exit(app.exec_())
# Nothing else _needed_ in here
Now for any child windows follow the same again.
Modal forms
In Qt Designer create a new 'Dialog with buttons bottom'.
Add widgets as desired and save as dialogAddStaffui.ui.
Run
pyside-uic -o dialogAddStaffui.py dialogAddStaffui.ui.
Create a new, empty text document called dialogAddStaffClass.py and using
dialogAddStaffui.ui as a reference for class names etc. edit dialogAddStaffClass.py to look like this:
dialogAddStaffClass
from dialogAddStaffui import Ui_DialogAddStaff
from PySide import QtCore, QtGui
class DialogAddStaff(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.ui = Ui_DialogAddStaff()
self.ui.setupUi(self)
# Your own init stuff
Those two imports are the only ones needed here.
If you are trying to copy this, realise that in the Qt Designer I have
windowModality = ApplicationModal and the form was a "Dialog with Buttons Bottom"
For these simple forms, they have an accept method that checks the validity of data that the user has entered and closes with self.done(1).
If you want to see how the validation and close is handled:
dialogAddStaffClass
def validate(self):
retval = True
if not self.ui.editLname.text():
retval = False
QtGui.QMessageBox.information(self, 'Invalid Last name',
"Last Name must not be blank")
self.ui.editLname.setFocus()
return retval
def accept(self):
if self.validate():
self.done(1)
With these dialog forms Qt has automatically set the OK button to fire accept. I just overrode that method.
If you want communication between parent and child you can either set a property on the child that references the parent, or read the the properties of the child after it closes, but before it's variable gets garbage collected. There can be issues with creating circular references so be careful.
As the new form is modal, the user can't interact with the Main Form until they have closed the Child Form, and the function that launches the chil d window will halt until the child window is closed, therefore it's ok to use a local variable to hold the child class.
The 'Add Staff' button is connected to the addStaffMember function.
mainWinClass.py
from dialogAddStaffClass import DialogAddStaff
def addStaffMember(self):
addStaffForm = DialogAddStaff()
res = addStaffForm.exec_() # exec_ waits, show would continue
# Function waits here for the Modal form to close.
if res: # child was closed with OK and validated correctly
print(addStaffForm.ui.editLname.text())
# Saveing other Staff data happens here
Because the child form is run with exec_, the main form waits untill the child form closes before continuing. When the function exits the addStaffForm variable is garbage collected, so there is no longer any reference to the attributes of the child form. (and probably no form ...)
If you want to open a Long lived form, you would instantiate it somewhere more long lasting.
Non Modal forms
Here is an example of a SisterForm. It is was created in the designer from the 'Main Window' type (It has it's own menu's and Status bar etc.). If you don't need these frills, use a Dialog form but set it's windowModality to NonModal.
Create sisterWinui.ui in Qt Designer - set objectName SisterWin
pyside-uic -o sisterWinui.py sisterWinui.ui
Create a file sisterwinClass.py - set up it's import and init
Make a variable to hold it in a long lived place (MainWin itself, use a self. ref attribute)
Make a launch button for it and connect it to method in the MainWin Class
sisterWinui.ui
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'sisterWinui.ui'
#
# Created: Mon Feb 8 12:05:37 2016
# by: pyside-uic 0.2.15 running on PySide 1.2.4
#
# WARNING! All changes made in this file will be lost!
from PySide import QtCore, QtGui
class Ui_SisterWin(object):
def setupUi(self, SisterWin):
SisterWin.setObjectName("SisterWin")
Run uic
pyside-uic -o sisterWinui.py sisterWinui.ui
Create a file sisterwinClass.py - set up it's import and init
sisterwinClass.py
from sisterWinui import Ui_SisterWin
from PySide import QtCore, QtGui
class SisterWin(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.ui = Ui_SisterWin()
self.ui.setupUi(self)
# Your custom stuff after this
In Qt Designer, add a button or whatever to your main form to launch sisterForm. Then make some edits to the mainwinClass.
Make a variable to hold it in a long lived place
mainwinClass
from sisterwinClass import SisterWin
# no other new imports needed, just the stuff you had before
class MainWin(QtGui.QMainWindow):
def __init__(self, parent=None):
# These three lines were already there
QtGui.QMainWindow.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# Add a long lived attribute to hold the class instance
self.sisterWin = None
# Next line was already there
self.setup_signals()
def setup_signals(self):
# Connect button to openSisterWin
self.ui.btnSisterwin.clicked.connect(self.openSisterWin)
# You probably have other connects after this
# This toggles the window
def openSisterWin(self):
if not self.sisterWin:
self.sisterWin = SisterWin()
if self.sisterWin.isVisible():
print('Hiding')
self.sisterWin.hide()
# hide or close, it's your choice
# self.sisterWin.close()
else:
print('Showing')
self.sisterWin.show()
I hope that covers what you were looking for now ?
If are trying to find out how to hide the main window , look here
Happy hacking :-)
I'm developing an application using the Qt Designer and PyQt4, I need to make several screens where each screen I capture user-specific data, for that I need to implement a next button and a back button similar to
where the current screen closes and the following opens when the user clicks next or if he clicks back, the screen closes and opens the previous screen, I made an example with only the next buttons and back to exemplify, if I was not clear:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Frm(QWidget):
def __init__(self, parent = None):
super(Frm, self).__init__(parent)
next = QPushButton('Next >', self)
back = QPushButton('< Back', self)
hbox = QHBoxLayout()
hbox.addWidget(back)
hbox.addWidget(next)
self.setLayout(hbox)
if __name__ == '__main__':
import sys
root = QApplication(sys.argv)
app = Frm(None)
app.show()
root.exec_()
In short: How do I implement a function that calls another screen and close the current at the click of a button?
First about a misconception: you do usually not create/show one screen (window) and close another, you usually only exchange the content of a wizard-like dialog window upon actions like pressing the buttons. The window is alive the whole time until the multiple page task is finished.
So I take it your question is really about:
How to exchange a widget in a layout?
Since you may still use PyQt4 which does not yet have QLayout.replaceWidget, it's best to just use methods removeWidget and addWidget of QLayout and since addWidget adds a widget to the end of the layout items list, I prefer a dedicated layout just for the interchangeable content of your wizard (see also: How to add an Item to the specific index in the layout).
Example code using PyQt5 but easily transferrable to PyQt4. Only the next button is implemented.
from PyQt5 import QtWidgets
class MyWizard(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# vertical layout, wraps content layout and buttons layout
vertical_layout = QtWidgets.QVBoxLayout()
self.setLayout(vertical_layout)
# content widget and layout
self.content_layout = QtWidgets.QVBoxLayout() # could be almost any layout actually
self.content = QtWidgets.QLabel('Page1') # customize with your content
self.content_layout.addWidget(self.content)
vertical_layout.addLayout(self.content_layout)
# back, forward buttons wraped in horizontal layout
button_layout = QtWidgets.QHBoxLayout()
button_layout.addStretch()
back_button = QtWidgets.QPushButton('Back')
back_button.clicked.connect(self.back_button_clicked)
button_layout.addWidget(back_button)
forward_button = QtWidgets.QPushButton('Forward')
forward_button.clicked.connect(self.forward_button_clicked)
button_layout.addWidget(forward_button)
vertical_layout.addLayout(button_layout)
def back_button_clicked(self):
"""
The back button is clicked.
"""
pass
def forward_button_clicked(self):
"""
The forward button is clicked.
"""
# remove old content
self.content_layout.removeWidget(self.content)
self.content.deleteLater()
# create new content
self.content = QtWidgets.QLabel('Another Page')
# add new content
self.content_layout.addWidget(self.content)
app = QtWidgets.QApplication([])
wizard = MyWizard()
wizard.setWindowTitle('MyWizard Example')
wizard.setFixedSize(600, 400)
wizard.show()
app.exec_()
And it looks like:
However, as already written in the comment by Marius, there is quite extensive support for such dialogs in Qt using QWizard. So I strongly recommend to use that instead. The example above is only to demonstrate the ability of inserting and removing widgets in layouts.
You should definitely use QWizard for such problems!
There is a QWizard class that allows you to create wizards in Qt and PyQt. It implements all the functionality you want, and lots more. All you do is design your pages by extending QWizardPage, and add them to the wizard. This is much simpler than doing the whole thing from scratch as you propose.
Old question but missing example with existing API
You don't need to create all structure of an Wizard by yourself. Qt (and PyQt) already provides a class called QWizard.
Basic example:
wizard = QtWidgets.QWizard()
page1 = QtWidgets.QWizardPage()
page1.setTitle('Page 1 is best!')
page1.setSubTitle('1111111111')
lineEdit = QtWidgets.QLineEdit()
hLayout1 = QtWidgets.QHBoxLayout(page1)
hLayout1.addWidget(lineEdit)
wizard.addPage(page1)
Complete example and some explanation:
https://www.youtube.com/watch?v=kTJ1QULxXjg
https://impatientprogrammer.net/2018/07/06/pyside-pyqt-qwizard-in-3-minutes/