Vamos a seguir evolucionando hacia hacer GUIs cada vez más sofisticados. En este capítulo describo Los tipos de ventanas principales que podemos usar en nuestras aplicaciones y los rasgos principales que las diferencian.
Índice:
- Instalación de lo que vamos a necesitar.
- Qt, versiones y diferencias.
- Hola, Mundo.
- Módulos en Qt.
- Añadimos icono a la ventana principal.
- Tipos de ventana en un GUI (este capítulo).
- Ventana de inicio – SplashScreen.
- Menú principal. Introducción.
- Mejorando algunas cosas vistas.
- Gestión de eventos o Acción y reacción.
- Introducción a Designer.
- Los Widgets vistos a través de Designer: Primera parte.
- Los Widgets vistos a través de Designer: Segunda parte.
- Los widgets vistos a través de Designer: Tercera Parte.
- Los widgets vistos a través de Designer: Cuarta Parte.
- Los widgets vistos a través de Designer: Quinta Parte.
- Los widgets vistos a través de Designer: Sexta parte.
- TBD… (lo actualizaré cuando tenga más claro los siguientes pasos).
[Los materiales para este capítulo los podéis descargar de aquí]
[INSTALACIÓN] Si todavía no has pasado por el inicio del curso, donde explico cómo poner a punto todo, ahora es un buen momento para hacerlo y después podrás seguir con esta nueva receta.
Hasta ahora hemos usado la clase QWidget
para crear nuestras ventanas, las cuales no son muy útiles de momento… Pero tenemos varias formas de crear ventanas usando otras clases.
Normalmente, QWidget
no será la clase que usaremos para crear la ventana principal de nuestra aplicación. QWidget
será usada para crear subventanas que llamaremos desde la aplicación principal.
En la aplicación principal querremos usar una instancia de QMainWindow
. Que no es más que un QWidget
un poco más trabajado.
Hay una tercera forma de ventana que son los QDialog
s que son ventanas con botones típicos de ‘Si’ o ‘No’, ‘Aceptar’ o ‘Cancelar’, ‘Cerrar’,…
Vamos a definir un poco más formalmente estas cosas usando esta respuesta que he encontrado en StackOverflow.
QWidget
Un QWidget
es la clase base para todos los widgets que vamos a usar. Cualquier clase que herede de QWidget
se podría mostrar como una ventana cuando no tiene un padre. Un padre será otra ventana que hará de contenedor de nuestro QWidget
. Lo que hemos venido haciendo hasta ahora es usar la clase QWidget
sin padre, es decir, como la ventana principal. Esto tiene sentido
cuando estemos haciendo aplicaciones muy sencillas que no necesiten, por
ejemplo, un menú o una barra de herramientas.
QDialog
QDialog
se basa en QWidget
.
Dispone de funciones que hacen que funcione de forma conveniente con
botones típicos como ‘Aceptar’, ‘Cancelar’, ‘Sí’, ‘No’,… Es decir, lo
usaremos cuando queramos establecer un diálogo entre la aplicación y el
usuario donde le daremos opciones al usuario sobre las que tendrá que
responder y retroalimentar a la aplicación para que sepa cómo tiene que
proceder.
QMainWindow
La clase QMainWindow
ha sido diseñada para contemplar necesidades básicas que debería tener una ventana principal de una aplicación. Dispone de lugares apropiados para, por ejemplo, una barra de menús, una barra de estado o una barra de herramientas. La realidad es que esta clase hereda de QWidget
y le añade la funcionalidad específica para que actúe de ventana principal.
Refactorizando
Como hicimos en el capítulo anterior vamos a reformatear nuestra aplicación para que use QMainWindow
como ventana principal. Ahí va el código que luego pasaremos a explicar:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
''' Curso de creación de GUIs con Qt5 y Python Author: Kiko Correoso Website: pybonacci.org Licencia: MIT ''' import os os.environ['QT_API'] = 'pyside2' import sys from pathlib import Path from qtpy.QtWidgets import QApplication, QMainWindow ## NUEVA LÍNEA from qtpy.QtGui import QIcon class MiVentana(QMainWindow): ## NUEVA LÍNEA def __init__(self): super().__init__() self._create_ui() def _create_ui(self): self.resize(500, 300) self.move(0, 0) self.setWindowTitle('Hola, QMainWindow') ruta_icono = Path('.', 'imgs', 'pybofractal.png') self.setWindowIcon(QIcon(str(ruta_icono))) self.show() if __name__ == '__main__': app = QApplication(sys.argv) w = MiVentana() sys.exit(app.exec_()) |
Como véis, el código es prácticamente igual que antes pero donde antes usábamos QWidget
ahora estamos usando QMainWindow
(en el import
y en la definición de la clase). Este código lo tenéis guardado en el fichero main00.py en la carpeta apps/05-QWidget_QMainWindow_QDialog/ del repositorio.
Si vemos todo lo disponible en nuestra instancia w
de nuestra clase MiVentana
vemos lo siguiente:
1 |
print(dir(w)) |
1 |
['AllowNestedDocks', 'AllowTabbedDocks', 'AnimatedDocks', 'DockOption', 'DockOptions', 'DrawChildren', 'DrawWindowBackground', 'ForceTabbedDocks', 'GroupedDragging', 'IgnoreMask', 'PaintDeviceMetric', 'PdmDepth', 'PdmDevicePixelRatio', 'PdmDevicePixelRatioScaled', 'PdmDpiX', 'PdmDpiY', 'PdmHeight', 'PdmHeightMM', 'PdmNumColors', 'PdmPhysicalDpiX', 'PdmPhysicalDpiY', 'PdmWidth', 'PdmWidthMM', 'RenderFlag', 'RenderFlags', 'VerticalTabs', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_create_ui', 'acceptDrops', 'accessibleDescription', 'accessibleName', 'actionEvent', 'actions', 'activateWindow', 'addAction', 'addActions', 'addDockWidget', 'addToolBar', 'addToolBarBreak', 'adjustSize', 'autoFillBackground', 'backgroundRole', 'backingStore', 'baseSize', 'blockSignals', 'centralWidget', 'changeEvent', 'childAt', 'childEvent', 'children', 'childrenRect', 'childrenRegion', 'clearFocus', 'clearMask', 'close', 'closeEvent', 'colorCount', 'connect', 'connectNotify', 'contentsMargins', 'contentsRect', 'contextMenuEvent', 'contextMenuPolicy', 'corner', 'create', 'createPopupMenu', 'createWinId', 'createWindowContainer', 'cursor', 'customContextMenuRequested', 'customEvent', 'deleteLater', 'depth', 'destroy', 'destroyed', 'devType', 'devicePixelRatio', 'devicePixelRatioF', 'devicePixelRatioFScale', 'disconnect', 'disconnectNotify', 'dockOptions', 'dockWidgetArea', 'documentMode', 'dragEnterEvent', 'dragLeaveEvent', 'dragMoveEvent', 'dropEvent', 'dumpObjectInfo', 'dumpObjectTree', 'dynamicPropertyNames', 'effectiveWinId', 'emit', 'ensurePolished', 'enterEvent', 'event', 'eventFilter', 'find', 'findChild', 'findChildren', 'focusInEvent', 'focusNextChild', 'focusNextPrevChild', 'focusOutEvent', 'focusPolicy', 'focusPreviousChild', 'focusProxy', 'focusWidget', 'font', 'fontInfo', 'fontMetrics', 'foregroundRole', 'frameGeometry', 'frameSize', 'geometry', 'getContentsMargins', 'grab', 'grabGesture', 'grabKeyboard', 'grabMouse', 'grabShortcut', 'graphicsEffect', 'graphicsProxyWidget', 'hasFocus', 'hasHeightForWidth', 'hasMouseTracking', 'height', 'heightForWidth', 'heightMM', 'hide', 'hideEvent', 'iconSize', 'iconSizeChanged', 'inherits', 'initPainter', 'inputMethodEvent', 'inputMethodHints', 'inputMethodQuery', 'insertAction', 'insertActions', 'insertToolBar', 'insertToolBarBreak', 'installEventFilter', 'internalWinId', 'isActiveWindow', 'isAncestorOf', 'isAnimated', 'isDockNestingEnabled', 'isEnabled', 'isEnabledTo', 'isEnabledToTLW', 'isFullScreen', 'isHidden', 'isLeftToRight', 'isMaximized', 'isMinimized', 'isModal', 'isRightToLeft', 'isSeparator', 'isSignalConnected', 'isTopLevel', 'isVisible', 'isVisibleTo', 'isWidgetType', 'isWindow', 'isWindowModified', 'isWindowType', 'keyPressEvent', 'keyReleaseEvent', 'keyboardGrabber', 'killTimer', 'layout', 'layoutDirection', 'leaveEvent', 'locale', 'logicalDpiX', 'logicalDpiY', 'lower', 'mapFrom', 'mapFromGlobal', 'mapFromParent', 'mapTo', 'mapToGlobal', 'mapToParent', 'mask', 'maximumHeight', 'maximumSize', 'maximumWidth', 'menuBar', 'menuWidget', 'metaObject', 'metric', 'minimumHeight', 'minimumSize', 'minimumSizeHint', 'minimumWidth', 'mouseDoubleClickEvent', 'mouseGrabber', 'mouseMoveEvent', 'mousePressEvent', 'mouseReleaseEvent', 'move', 'moveEvent', 'moveToThread', 'nativeParentWidget', 'nextInFocusChain', 'normalGeometry', 'objectName', 'objectNameChanged', 'overrideWindowFlags', 'overrideWindowState', 'paintEngine', 'paintEvent', 'painters', 'paintingActive', 'palette', 'parent', 'parentWidget', 'physicalDpiX', 'physicalDpiY', 'pos', 'previousInFocusChain', 'property', 'raise_', 'receivers', 'rect', 'redirected', 'registerUserData', 'releaseKeyboard', 'releaseMouse', 'releaseShortcut', 'removeAction', 'removeDockWidget', 'removeEventFilter', 'removeToolBar', 'removeToolBarBreak', 'render', 'repaint', 'resize', 'resizeDocks', 'resizeEvent', 'restoreDockWidget', 'restoreGeometry', 'restoreState', 'saveGeometry', 'saveState', 'scroll', 'sender', 'senderSignalIndex', 'setAcceptDrops', 'setAccessibleDescription', 'setAccessibleName', 'setAnimated', 'setAttribute', 'setAutoFillBackground', 'setBackgroundRole', 'setBaseSize', 'setCentralWidget', 'setContentsMargins', 'setContextMenuPolicy', 'setCorner', 'setCursor', 'setDisabled', 'setDockNestingEnabled', 'setDockOptions', 'setDocumentMode', 'setEnabled', 'setFixedHeight', 'setFixedSize', 'setFixedWidth', 'setFocus', 'setFocusPolicy', 'setFocusProxy', 'setFont', 'setForegroundRole', 'setGeometry', 'setGraphicsEffect', 'setHidden', 'setIconSize', 'setInputMethodHints', 'setLayout', 'setLayoutDirection', 'setLocale', 'setMask', 'setMaximumHeight', 'setMaximumSize', 'setMaximumWidth', 'setMenuBar', 'setMenuWidget', 'setMinimumHeight', 'setMinimumSize', 'setMinimumWidth', 'setMouseTracking', 'setObjectName', 'setPalette', 'setParent', 'setProperty', 'setShortcutAutoRepeat', 'setShortcutEnabled', 'setSizeIncrement', 'setSizePolicy', 'setStatusBar', 'setStatusTip', 'setStyle', 'setStyleSheet', 'setTabOrder', 'setTabPosition', 'setTabShape', 'setToolButtonStyle', 'setToolTip', 'setToolTipDuration', 'setUnifiedTitleAndToolBarOnMac', 'setUpdatesEnabled', 'setVisible', 'setWhatsThis', 'setWindowFilePath', 'setWindowFlags', 'setWindowIcon', 'setWindowIconText', 'setWindowModality', 'setWindowModified', 'setWindowOpacity', 'setWindowRole', 'setWindowState', 'setWindowTitle', 'sharedPainter', 'show', 'showEvent', 'showFullScreen', 'showMaximized', 'showMinimized', 'showNormal', 'signalsBlocked', 'size', 'sizeHint', 'sizeIncrement', 'sizePolicy', 'splitDockWidget', 'stackUnder', 'startTimer', 'staticMetaObject', 'statusBar', 'statusTip', 'style', 'styleSheet', 'tabPosition', 'tabShape', 'tabifiedDockWidgets', 'tabifyDockWidget', 'tabletEvent', 'takeCentralWidget', 'testAttribute', 'thread', 'timerEvent', 'toolBarArea', 'toolBarBreak', 'toolButtonStyle', 'toolButtonStyleChanged', 'toolTip', 'toolTipDuration', 'topLevelWidget', 'tr', 'underMouse', 'ungrabGesture', 'unifiedTitleAndToolBarOnMac', 'unsetCursor', 'unsetLayoutDirection', 'unsetLocale', 'update', 'updateGeometry', 'updateMicroFocus', 'updatesEnabled', 'visibleRegion', 'whatsThis', 'wheelEvent', 'width', 'widthMM', 'winId', 'window', 'windowFilePath', 'windowFlags', 'windowHandle', 'windowIcon', 'windowIconChanged', 'windowIconText', 'windowIconTextChanged', 'windowModality', 'windowOpacity', 'windowRole', 'windowState', 'windowTitle', 'windowTitleChanged', 'windowType', 'x', 'y'] |
¡¡¡Guauuu!!! Tenemos unas cuantas cosas para toquetear. Algunas ya las hemos estado usando, como los métodos resize
, move
, setWindowTitle
, setWindowIcon
o show
. Algunas otras las iremos viendo cuando tenga sentido verlas. De momento quedaos con que es un objeto con bastante funcionalidad.
Barra de estado
Para que este capítulo no sea tan corto, antes de terminar el mismo vamos a hacer alguna cosita más con esta nueva ventana principal más potente que la que habíamos usado hasta ahora. Vamos a toquetear alguna cosa más para añadir una barra de estado para nuestra aplicación. Como antes, dejo primero el código y luego comentamos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
''' Curso de creación de GUIs con Qt5 y Python Author: Kiko Correoso Website: pybonacci.org Licencia: MIT ''' import os os.environ['QT_API'] = 'pyside2' import sys from pathlib import Path from qtpy.QtWidgets import QApplication, QMainWindow from qtpy.QtGui import QIcon class MiVentana(QMainWindow): def __init__(self): super().__init__() self._create_ui() def _create_ui(self): self.resize(500, 300) self.move(0, 0) self.setWindowTitle('Hola, QMainWindow') ruta_icono = Path('.', 'imgs', 'pybofractal.png') self.setWindowIcon(QIcon(str(ruta_icono))) self.statusBar().showMessage('Ready') ## NUEVA LÍNEA self.show() if __name__ == '__main__': app = QApplication(sys.argv) w = MiVentana() sys.exit(app.exec_()) |
Lo único que hemos añadido ha sido la línea:
1 |
self.statusBar().showMessage('Ready') |
en la cual usamos el método showMessage
del widget QStatusBar
. El widget QStatusBar
lo obtenemos llamando al método statusBar
del QMainWindow
. Como he comentado, QMainWindow
no es más que un QWidget
con esteroides y entre estos esteroides tenemos la posibilidad de acceder a una barra de estado que ya ha sido incluida en QMainWindow
. Si os fijáis de nuevo en todo lo que dispone un QMainWindow
(el print(dir(w))
que hemos hecho más arriba) veréis cosas como menuBar
o centralWidget
,… Pero no nos adelantemos que luego se complica. Seguimos dando pasitos cortos y sencillos.
Este código lo tenéis guardado en el fichero main01.py en la carpeta apps/05-QWidget_QMainWindow_QDialog/ del repositorio.
El resultado con la barra de estado lo podéis ver en la imagen siguiente donde abajo a la derecha veis el mensaje que estamos mostrando (Ready).

Y aquí lo dejamos hoy. En el próximo capítulo seguiremos añadiendo cosas a nuestra GUI.
P.D.: Si tienes alguna duda, por favor, usa los comentarios.