Hojas de estilo en Qt

Una de las características más atractivas de Qt es la de poder modificar el aspecto de los widgets a través de las hojas de estilo o QSS (Qt Style Sheets). A continuación cuatro de sus cualidades generales:

  • De fácil implementación
  • Un lenguaje de estilo común
  • Mayor organización del código
  • Aspecto gráfico totalmente configurable

Es indiferente el paquete que utilices para acceder al framework (PyQt o PySide). En este artículo haré uso del primero, pero no es más que modificar las importaciones para crear compatibilidad con el segundo. Si bien el código mostrado a lo largo del artículo es compatible con Python 2 y 3, la última versión es únicamente soportada por PyQt.

La función que usaremos para establecer los estilos es setStyleSheet(), perteneciente a la clase QWidget de la cual derivan los demás widgets o controles. Nótese que los layouts no heredan las funciones de dicha clase, por lo que su aspecto no es modificable a través de este método.

Antes de comenzar, a continuación dejo el código del cual partiré para luego añadir los demás controles.

from PyQt4.QtGui import QApplication, QMainWindow

class Window(QMainWindow):
    
    def __init__(self):
        QMainWindow.__init__(self)
        self.resize(400, 250)
        
        # ...

if __name__ == "__main__":
    app = QApplication([])
    window = Window()
    window.show()
    app.exec_()

Nótese el «# …» en donde se debe posicionar todo código a partir de ahora. Recuerda importar las clases que se van utilizando a lo largo del artículo. Por ejemplo, si el código indica que self.button = QPushButton(self) deberás añadir dicha clase a las importaciones:

from PyQt4.QtGui import QApplication, QMainWindow, QPushButton

Al igual que con los demás widgets.

En la porción de código que llevamos hasta el momento simplemente creo una ventana y le atribuyo un tamaño. Vamos a comenzar por crear un label o etiqueta.

self.label = QLabel(self)
self.label.setGeometry(10, 10, 100, 50)
self.label.setText("Hola mundo!")

Recuerda añadir la clase a las importaciones de la siguiente manera:

from PyQt4.QtGui import QApplication, QMainWindow, QLabel

Como primer paso le aplicaremos un color de fondo:

self.label.setStyleSheet("background-color: #4075CC;")

Sin embargo, el negro con el azul no es una muy buena combinación para la lectura; por lo tanto cambiaremos el color del texto a blanco, y añadiremos un margen izquierdo para mejorar la legibilidad. Pueden indicarse varios estilos en una misma cadena separándolos con «;».

self.label.setStyleSheet("margin-left: 2px; color: #FFFFFF; background-color: #4075CC;")

Y algunos widgets más para completar:

self.button = QPushButton(self)
self.button.setGeometry(10, 70, 70, 25)
self.button.setText(u"Botón")
self.button.setStyleSheet("font-size: 14px; color: #A0184B;")

self.line_edit = QLineEdit(self)
self.line_edit.setGeometry(10, 110, 100, 20)
self.line_edit.setStyleSheet("border: 2px solid #3232C0;")

Vista previa 1

La mayoría de las propiedades de CSS son compatibles con QSS, como habrás notado (font, font-size, border, border-color, etc.).

Ahora implementaremos un fondo oscuro a toda la ventana:

self.setStyleSheet("background-color: black;")

black, #FFFFFF, rgb(0, 0, 0) resultan en lo mismo. Incluso si se quiere aplicar transparencia al fondo puede utilizarse rgba en donde el cuarto parámetro es el grado de alpha (255 es totalmente opaco, 0 totalmente transparente).

Si pruebas el código notarás que el negro se aplica también a todos los widgets hijos de la ventana que no han especificado un fondo (button y line_edit). Para evitar esto, podemos especificar la clase a la cual queremos que se aplique el estilo:

self.setStyleSheet("QMainWindow {background-color: black;}")

De esta manera el estilo solo se verá reflejado en la ventana.

De todas maneras sería óptimo poder separar la parte de los estilos del resto del código, almacenándolos en un archivo .css o .qss. Así, el widget padre (la ventana) cargaría el contenido del archivo para que se aplique a todos los hijos. Para poder llevar a cabo dicho proceso, es necesario atribuirle un nombre a cada uno de los widgets, para que Qt sepa a cuál se le aplica el estilo. Haciendo uso de la funcion setObjectName el código quedaría así:

self.setObjectName("window")

self.label = QLabel(self)
self.label.setGeometry(10, 10, 100, 50)
self.label.setText("Hola mundo!")
self.label.setObjectName("label")

self.button = QPushButton(self)
self.button.setGeometry(10, 70, 70, 25)
self.button.setText("Boton")
self.button.setObjectName("button")

self.line_edit = QLineEdit(self)
self.line_edit.setGeometry(10, 110, 100, 20)
self.line_edit.setObjectName("line_edit")

Creamos un archivo llamado styles.css en el directorio de la aplicación que contenga:

QMainWindow#window {background-color: black;}

QLabel#label {
    margin-left: 2px;
    color: #FFFFFF;
    background-color: #4075CC;
}

QPushButton#button {
    font-size: 14px;
    color: #A0184B;
}

QLineEdit#line_edit {border: 2px solid #3232C0;}

Nótese el modo en el que se aplica el estilo a cada uno de los widgets. Primero se indica la clase y luego se especifica el nombre, intercalados por un «#» (numeral). El nombre de la clase podría haberse omitido, sin embargo le brinda mayor legibilidad.

Para aplicar el mismo estilo, por ejemplo, a todos los botones, simplemente se especifica el nombre de la clase:

QPushButton {font-size: 14px;}

De este modo todos los botones tendrán 14px de tamaño de fuente.

Ahora sí añadimos la línea correspondiente a nuestro código para cargar el archivo CSS:

with open("styles.css") as f:
    self.setStyleSheet(f.read())

Vista Previa 2

Código completo:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from PyQt4.QtGui import (QApplication, QMainWindow, QLabel, QPushButton,
                         QLineEdit)

class Window(QMainWindow):
    
    def __init__(self):
        QMainWindow.__init__(self)
        self.resize(400, 250)
        
        self.setObjectName("window")
        with open("styles.css") as f:
            self.setStyleSheet(f.read())
        
        self.label = QLabel(self)
        self.label.setGeometry(10, 10, 100, 50)
        self.label.setText("Hola mundo!")
        self.label.setObjectName("label")
        
        self.button = QPushButton(self)
        self.button.setGeometry(10, 70, 70, 25)
        self.button.setText("Boton")
        self.button.setObjectName("button")
        
        self.line_edit = QLineEdit(self)
        self.line_edit.setGeometry(10, 110, 100, 20)
        self.line_edit.setObjectName("line_edit")

if __name__ == "__main__":
    app = QApplication([])
    app.setStyle("plastique")
    window = Window()
    window.show()
    app.exec_()

Todas las propiedades aplicables en las hojas de estilo se encuentran en este enlace, junto con los pseudo-estados y los subcontroles. Por ejemplo, para cambiar el color del texto de un label cuando el mouse está por encima podría aplicarse:

QLabel#label:hover {color: #FF0000;}

Con respecto a los subcontroles, se utilizan para diferenciar entre diversas partes de un widget. Por ejemplo, QTreeWidget contiene el subcontrol item, QTabBar el subcontrol tab, QSlider el subcontrol groove, etc. Puede accederse a ellos de la siguiente manera:

Clase#Nombre:Subcontrol

Por ejemplo:

QTreeWdiget#tree:item {background-color: white;}
QTabBar#tab_bar:tab {color: red;}
QSlider#slider:groove {border: 2px solid black;}

A su vez los subcontroles pueden tener otros subcontroles, por ejemplo, no es el mismo estilo que se aplica al groove cuando es horizontal que vertical:

QSlider#slider:groove:horizontal {border: 2px solid black;}
QSlider#slider:groove:vertical {border: 3px solid white;}

Versión

Python 2, Python 3

Curso online 👨‍💻

¡Ya lanzamos el curso oficial de Recursos Python en Udemy! Un curso moderno para aprender Python desde cero con programación orientada a objetos, SQL y tkinter en 2024.

Consultoría 💡

Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y empresas. Consultanos por tu proyecto.

Deja una respuesta