[TUTORIAL] Python e Qt 5 [Parte 2]

Desta parte 2 vamos ver como criar interfaces utilizando o Qt Designe e o Qt Creator. Em seguida vamos ver como ler/manipular o arquivo *.ui gerado por essas ferramentas.

A ideia é criar uma interface com o mesmo designe e funcionalidades que fizemos no GTK+ 3, basicamente vamos seguir o seguinte modelo:

Como foi dito na parte 1 é comum vermos tutoriais onde o designe da interface é feito diretamente no código Python, um exemplo desta forma de criar a interface seria:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Python e PyQt5.

Criando a interface no Python.
"""
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QLineEdit, QLabel,
                             QPushButton, QApplication)


class MeuAplicativo(QWidget):
    """Classe"""

    def __init__(self):
        """Construtor."""
        super().__init__()
        # Janela principal.
        self.setWindowTitle('Olá Mundo')
        self.setObjectName('Widget')
        self.resize(400, 300)

        # Gerenciador de layout.
        verticalLayout = QVBoxLayout()
        verticalLayout.setObjectName('verticalLayout')
        self.setLayout(verticalLayout)

        # Widgets.
        self.lineEdit = QLineEdit()
        self.lineEdit.setObjectName('lineEdit')
        self.lineEdit.setPlaceholderText('Digite algo')
        verticalLayout.addWidget(self.lineEdit)

        self.label = QLabel()
        self.label.setObjectName('label')
        self.label.setText('Este texto será alterado!')
        self.label.setAlignment(Qt.AlignCenter)
        verticalLayout.addWidget(self.label)

        self.pushButton = QPushButton()
        self.pushButton.setObjectName('pushButton')
        self.pushButton.setText('Clique Aqui')
        self.pushButton.clicked.connect(self._on_button_clicked)
        verticalLayout.addWidget(self.pushButton)

    def _on_button_clicked(self):
        """Método é executado quando o botão é pressionado."""
        # Coletando o valor do campo de entrada de texto.
        text = self.lineEdit.text()
        # Verificando se algo foi digitado.
        if text:
            self.label.setText(text)
        else:
            self.label.setText('Digite algo no campo de texto :)')


if __name__ == "__main__":
    import sys

    app = QApplication(sys.argv)
    ui = MeuAplicativo()
    ui.show()
    sys.exit(app.exec_())

python-designe-code

Essa é uma forma totalmente válida de se desenvolver um aplicativo, todavia temos como limitação o fato de não poder separar a lógica da parte visual, além de limitar a possibilidade de se trabalhar com uma equipe para o designe e uma para a lógica.

Agora vamos ver uma outra possibilidade para o código a cima. Desta vez separando o designer da parte lógica.

Qt Designer

Se você optar por utilizar o Qt Designer, basta executar ele que o mesmo irá solicitar o tipo de janela que se deseja criar:

Eu escolhi o tipo Widget por que não será um interface que utiliza menus e area de status.

Assim como no Gnome Glade, tente desenhar a interface até chegar em um resultado próximo a:

Para visualizar o resultado do que está sendo criado basta utilizar o atalho Ctrl + r.

Importante notar que os widgets criados tem um campo chamado objectName este campo deve ser único, visto que iremos acessar os widgets por este nome:

Caso não queira desenhar a interface crie um arquivo chamado mainwindow-designer.ui e cole o seguinte código XML:

<?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>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Olá Mundo</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <widget class="QLineEdit" name="lineEdit">
     <property name="inputMask">
      <string/>
     </property>
     <property name="text">
      <string/>
     </property>
     <property name="placeholderText">
      <string>Digite algo</string>
     </property>
    </widget>
   </item>
   <item>
    <widget class="QLabel" name="label">
     <property name="text">
      <string>Este texto será alterado!</string>
     </property>
     <property name="alignment">
      <set>Qt::AlignCenter</set>
     </property>
    </widget>
   </item>
   <item>
    <widget class="QPushButton" name="pushButton">
     <property name="text">
      <string>Clique Aqui</string>
     </property>
    </widget>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>

Qt Creator

Já com o Qt Creator vamos precisar criar um novo projeto, para isso vamos no menu superior e clicamos em file &gt; New File or Project, na janela que se abre vamos escolher Application e Qt Widgets Applicativon:

Na tela seguinte podemos definir o nome do projeto e o local dele. clique em Next:

Agora temos a tela onde definimos o kit. Caso seja necessário crie um. clique em Next:

Na tela que segue podemos definir informações sobre a class (classe), contudo isso não é importante, visto que não vamos utilizar C/C++, apenas clique em Next:

Por fim temos a ultima tela. clique em Finish:

Com o projeto criado clique sobre a pasta forms e clique no arquivo mainwindow.ui, com isso o modo de designe será aberto:

Agora basta criar a interface, como fizemos com o Qt Designer:

Caso você queira visualizar o resultado enquanto cria a interface basta utilizar o atalho Ctrl + r.

> OBS: Vale notar que o Qt Creator gera uma janela do tipo MainWindow, contudo isso não é um problema, uma vez que nossos widgets terão o mesmo objectName.

Caso não queira tentar desenhar a interface crie um arquivo chamado mainwindow-creator.ui e cole o seguinte código XML:

<?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>Olá Mundo</string>
  </property>
  <widget class="QWidget" name="centralWidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QLineEdit" name="lineEdit">
      <property name="placeholderText">
       <string>Digite algo</string>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QLabel" name="label">
      <property name="text">
       <string>Este texto será alterado!</string>
      </property>
      <property name="alignment">
       <set>Qt::AlignCenter</set>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QPushButton" name="pushButton">
      <property name="text">
       <string>Clique Aqui</string>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menuBar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>400</width>
     <height>22</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>

Interfaces criadas, agora podemos avançar para o código em Python.

Python

> OBS: Eu realmente recomendo que você crie um ambiente virtual e instale as depêndencias neste ambiente, contudo não é obrigatorio é apenas uma recomendação.

Vamos começar criando a pasta do projeto, eu criei um pasta com o nome ola-mundo, sinta-se avontade para criar uma pasta com o nome que preferir.

Essa pasta terá a seguinte estrutura:

ola-mundo
├── forms
│   ├── mainwindow-creator.ui
│   └── mainwindow-designer.ui
├── icons
│   ├── icon.ico
│   └── icon.png
├── MainWindow.py
└── Pipfile

Estrutura do projeto

> OBS: O arquivo Pipfile é porque eu estou utilizando o pipenv para gestão do meu ambiente virtual. Se você não utilizar o pipenv este arquivo não irá existir e não é necessário.

> OBS: Tenho 2 aquivos de interface dentro da pasta forms porque criei uma interface com o Qt Creator e outra com o Qt Designe.

Agora que temos um estrutura básica do projeto vamos instalar o PyQt5, se você estiver utilizando um ambiente virtual (lembre-se de ativar o ambiente!):

pip install pyqt5

Se estiver instalando diretamente no Python do S.O.:

sudo pip3 install pyqt5

> OBS: Caso você tenha mais de uma versão do Python 3 no sistema lembre-se de verificar em qual a instalação está sendo feita ou utilize: python3.x -m pip install pyqt5. Substitua o x pela versão de Python que deseja.

Com o PyQt5 instalado vamos abrir/criar o arquivo MainWindow.py e digitar o seguinte código:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Python e PyQt5.

Acessando/interagindo com um arquivo ``*.ui`` (XML).
"""
from PyQt5.QtWidgets import QApplication
from PyQt5.uic import loadUi


class MeuAplicativo:
    """Classe."""

    def __init__(self, window):
        """Construtor."""
        # Widgets
        self.label = window.label
        self.line_edit = window.lineEdit
        self.push_button = window.pushButton
        # Conectando um método ao evento de clique do botão.
        self.push_button.clicked.connect(self._on_button_clicked)

    def _on_button_clicked(self):
        """Método é executado quando o botão é pressionado."""
        # Coletando o valor do campo de entrada de texto.
        text = self.line_edit.text()
        # Verificando se algo foi digitado.
        if text:
            self.label.setText(text)
        else:
            self.label.setText('Digite algo no campo de texto :)')


if __name__ == "__main__":
    import sys

    app = QApplication(sys.argv)
    # Lendo o arquivo de interface.
    # window = loadUi('forms/mainwindow-creator.ui')
    window = loadUi('forms/mainwindow-designer.ui')
    ui = MeuAplicativo(window=window)
    window.show()
    sys.exit(app.exec_())

Ao executar o código a cima temos:

python-designe-code

Nossa interface criada no Qt Designer ou Qt Creator integrada no Python.

O que vejo como principal beneficio deste método é que temos uma boa separação da logica de programação do desginer da interface, favorecendo inclusive o trabalho onde podemos ter um time cuidado da interface e outro da lógica.

Bom com isso temos um pequena introdução a criação de interfaces gráficas com Python e Qt 5. Talvez haja uma parte 3 com alguns código de exemplo para o PySide2 ou algo assim :smiley: .

E claro se encontrar algum erro ou dificuldade entre em contato para que o material possa ser atualizado.

Esse tipo de postagem é bom demais…
é isso que precisamos aqui

Como faz com gtk? Tô aprendendo python aos pouquinhos

Muito interessante e completo o Tutorial!! Parabéns @natorsc !!

@filipemosca tem um tutorial completo sobre PyGTK em português aqui:
https://python-gtk-3-tutorial.readthedocs.io/pt_BR/latest/

Eu estou aprendendo Vala-GTK e de vez em quando vou na documentação do python pra esclarecer as coisas, já que documentação de Vala é escarsa.

Bom dia. Como faço para criar um app com vários forms?
Devo criar um arquivo “.ui” para cada form, gerar o “.py” correspondente e importar no python?
Ex:

pyuic5 form1.ui -o form2.py
pyuic5 form2.ui -o form2.py

from PyQt5.QtWidgets import QMainWindow, QApplication
from form1 import form1
from form2 import form2

class MyApp (QMainWindow, form1, form2)

Olá @Roberto_d_Avila

Você pode trabalhar criando os arquivos de interface e convertendo eles.

Uma vantagem dessa técnica é ter um bom linter (verificação de erros) quando se está editando o código Python.

A desvantagem é que sempre que você tiver uma alteração na interface terá que converter novamente a mesma e tem que tomar o cuidado de realizar as alteração sempre no arquivo de interface, visto que se a alteração for feita no código Python o arquivo de interface estará desatualizado.

A sua lógica está corretíssima cada arquivo de interface será uma tela e cada tela representa uma classe (objeto). depois basta importar e instanciar cada classe em uma variável.

Fiz um exemplo, contudo deixei todas as classes no mesmo arquivo para facilitar a execução de quem quiser testar:

# -*- coding: utf-8 -*-
"""Abrindo múltiplas janelas."""

from PySide2.QtWidgets import (QApplication, QLabel, QMainWindow,
                               QPushButton, QVBoxLayout, QWidget)


class Form1(QWidget):
    """Janela 1.

    Essa classe poderia estar outro arquivo e ser importado.
    """

    def __init__(self):
        super().__init__()
        self.setWindowTitle('Este é o form 1')

        layout = QVBoxLayout()
        self.setLayout(layout)

        self.label = QLabel('Este é o form 1')
        layout.addWidget(self.label)


class Form2(QWidget):
    """Janela 2.

    Essa classe poderia estar outro arquivo e ser importado.
    """

    def __init__(self):
        super().__init__()
        self.setWindowTitle('Este é o form 2')

        layout = QVBoxLayout()
        self.setLayout(layout)

        self.label = QLabel('Este é o form 2')
        layout.addWidget(self.label)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        screen_size = app.primaryScreen().geometry()
        width = screen_size.width()
        height = screen_size.height()
        self.resize(int(width / 2), int(height / 2))
        self.setMinimumSize(int(width / 3), int(height / 3))
        self.setWindowTitle('Janela Principal')

        # Instância da classe Form1().
        self.form1 = Form1()
        # Instância da classe Form2().
        self.form2 = Form2()

        vbox = QVBoxLayout()
        button1 = QPushButton("Abrir Form1()")
        button1.clicked.connect(self.open_form1)
        vbox.addWidget(button1)

        button2 = QPushButton("Abrir Form2()")
        button2.clicked.connect(self.open_form2)
        vbox.addWidget(button2)

        w = QWidget()
        w.setLayout(vbox)
        self.setCentralWidget(w)

    def open_form1(self, checked):
        if self.form1.isVisible():
            self.form1.hide()
        else:
            self.form1.show()

    def open_form2(self, checked):
        if self.form2.isVisible():
            self.form2.hide()
        else:
            self.form2.show()


if __name__ == '__main__':
    import sys

    app = QApplication(sys.argv)
    mainwindow = MainWindow()
    mainwindow.show()
    sys.exit(app.exec_())

Outra possibilidade é fazer a leitura dos arquivos de interface (XML ou QML), dessa forma você tem como vantagens:

  • A separação do design da interface da lógica de negocio (lógica do aplicativo).
  • Você pode ter uma equipe trabalhando no design do aplicativo e outra na parte de lógica do negocio.

A principal desvantagem está no fato do Qt permitir que essa leitura e atribuição de widgets seja feita de muitas formas, tanto que você verá tutoriais fazendo isso de formas diferentes.

Segue um exemplo também:

Form1:

<?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>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Este é o form 1</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <widget class="QLabel" name="label">
     <property name="text">
      <string>Este é o form 1</string>
     </property>
     <property name="alignment">
      <set>Qt::AlignCenter</set>
     </property>
    </widget>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>

Form2:

<?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>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Este é o form 2</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <widget class="QLabel" name="label">
     <property name="text">
      <string>Este é o form 2</string>
     </property>
     <property name="alignment">
      <set>Qt::AlignCenter</set>
     </property>
    </widget>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>

Janela principal:

<?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>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Janela Principal</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QLabel" name="label">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
        <horstretch>0</horstretch>
        <verstretch>0</verstretch>
       </sizepolicy>
      </property>
      <property name="layoutDirection">
       <enum>Qt::LeftToRight</enum>
      </property>
      <property name="text">
       <string>Exemplo</string>
      </property>
      <property name="alignment">
       <set>Qt::AlignCenter</set>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QPushButton" name="btn_open_form1">
      <property name="text">
       <string>Abrir form 1</string>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QPushButton" name="btn_open_form2">
      <property name="text">
       <string>Abrir Form 2</string>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>800</width>
     <height>29</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

Código Python que faz a leitura dos arquivos de interface:

# -*- coding: utf-8 -*-
"""."""
from PySide2.QtCore import QObject, QCoreApplication, Qt
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication, QWidget, QMainWindow


class Form1(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Este é o form 1')
        QUiLoader().load('./Form1.ui', self)
        self.show()


class Form2(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Este é o form 2')
        self.window = QUiLoader().load('./Form2.ui', self)
        self.show()


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.window = QUiLoader().load('./MainWindow.ui')

        # Instância da classe Form1().
        self.form1 = None
        # Instância da classe Form2().
        self.form2 = None

        btn_open_form1 = self.window.findChild(QObject, 'btn_open_form1')
        btn_open_form1.clicked.connect(self.open_form1)

        btn_open_form2 = self.window.findChild(QObject, 'btn_open_form2')
        btn_open_form2.clicked.connect(self.open_form2)

        self.window.show()

    def open_form1(self, checked):
        if self.form1 is None:
            self.form1 = Form1()
        self.form1.show()

    def open_form2(self, checked):
        if self.form2 is None:
            self.form2 = Form2()
        self.form1.show()


if __name__ == "__main__":
    import sys

    QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
    app = QApplication(sys.argv)
    mainwindow = MainWindow()
    sys.exit(app.exec_())

Tentei criar um gif do código acima em execução, contudo não ficou legal, segue link para um video privado:

https://youtu.be/oNT1kQAiAOk

O meu conteudo de Qt e alguns outros não está muito atualizado no GithHub, segue link de alguns exemplos de código com Qt que estão no meu site pessoal:

https://codigoninja.dev/listings/qt/

Quaquer duvida sinta-se a vontade para entrar em contato aqui pelo forum ou redes sociais.

Agradeço imensamente sua resposta. Ajudou muito!!!

Obrigado.

Gostaria de saber como eu posso colocar uma imagem com opacidade no fundo de uma janela e deixar os botões, campos de texto e combo boxes utilizáveis? Eu consegui colocar a imagem transparente, mas ela está sobre os outros widgets o que dificulta o uso deles.
O código está assim:


import sys
from PIL import ImageGrab
from Codes.Cad_Trajes import *
from Codes.Cad_Usr import *
from Codes.Rent import *
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QApplication, QTabWidget, QVBoxLayout, QLabel, QGraphicsOpacityEffect

class MainWindow(QWidget):
    def __init__(self):
	super(MainWindow, self).__init__( )
	img = ImageGrab.grab( ).size
	wid = img[0]
	hg = img[1]
	left = wid // 4
	top = int(hg * 0.1)
	width = wid // 2
	height = int(hg * 0.8)

	picLabel = QLabel(self)
	picLayout = QVBoxLayout( )
	pic = QPixmap("Pics/E_Trajes.svg")
	picLabel.setPixmap(pic.scaled(width, height, Qt.KeepAspectRatio, Qt.SmoothTransformation))
	picLabel.setHidden(False)
	effect = QGraphicsOpacityEffect( )
	effect.setOpacity(.09)
	picLabel.setGraphicsEffect(effect)
	picLayout.addWidget(picLabel, alignment=Qt.AlignCenter)

	ct = CadTrajes( )
	cadusr = CadUsr( )
	rent = Renting( )
	box = QVBoxLayout( )
	tab = QTabWidget( )
	tab.currentWidget( )
	tab.addTab(cadusr, " CLIENTES (CADASTRO E EDIÇÃO) ")
	tab.addTab(rent, " ALUGUEL DE TRAJES ")
	tab.addTab(ct, " TRAJES (CADASTRO E EDIÇÃO) ")
	tab.setLayout(picLayout)
	box.addWidget(tab)
	self.setWindowTitle("ELEGANT TRAJES")
	self.setGeometry(left, top, width, height)
	self.setWindowIcon(QIcon("Pics/E_Trajes_01.svg"))
	self.setStyleSheet(open('CSS/etrajes.css').read( ))
	self.setLayout(box)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    mw = MainWindow( )
    mw.show( )
    app.exec_( )

Ele está funcionando, abre normal, mas como a imagem está por cima dos widgets, eu não consigo usar nada da interface usando o mouse. Como eu faria pra colocar essa imagem como plano de fundo da aba, mas sem interferir nos outros widgets?

Ola @ACMM .

Faz um teste, tenta definir a imagem utilizado o método setStyleSheet().

Ex:

nome-do-widget.setStyleSheet("background-image: url(image.png)")

Se funcionar me avisa, ainda não testei essa possibilidade.

@natorsc
Opa! Tudo certo? Serviria, sim, se eu redimensionasse a imagem manualmente e deixasse um tamanho fixo direto no arquivo da imagem pois ela tem um tamanho de 128x128px e ela é também usada como a logo que fica na parte superior da tela, mas como eu quero que a imagem se redimensione baseada no tamanho do widget , fica complicado fazer isso. Bom, pelo menos, eu ainda não descobri como fazer isso. A imagem esta pegando as dimensões da tela como mostra no seguinte trecho do código:


picLabel.setPixmap(pic.scaled(width, height, Qt.KeepAspectRatio, Qt.SmoothTransformation))

Bom, eu já havia tentado essa dica e, infelizmente, não funcionou como deveria. Colocou a imagem na tela, mas não ocupou o espaço todo se limitando aos 128x128px do tamanho da imagem.
Deu pra entender?

@ACMM

Nesse caso eu acredito que a melhor forma seja utilizando border-image.

Só que ao invés de utilizar setStyleSheet() pode ser interessante trabalhar diretamente no css da janela principal.

Assim vamos chegar neste resultado:

pyqt6-background-image

Eu peguei um computador emprestado aqui :joy: e acabei fazendo um exemplo com PyQt6 porque já tinha essa base de código pronta no meu Github:

# -*- coding: utf-8 -*-
"""Python e Qt 6: PyQt6 QMainWindow() adicionando imagem no background."""

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (QApplication, QLabel, QLineEdit, QMainWindow,
                             QPushButton, QVBoxLayout, QWidget)


class MainWindow(QMainWindow):

    def __init__(self, application):
        super().__init__()
        self.application = application

        vbox = QVBoxLayout()
        central_widget = QWidget()
        central_widget.setLayout(vbox)
        self.setCentralWidget(central_widget)

        line_edit = QLineEdit()
        vbox.addWidget(line_edit)

        label = QLabel()
        label.setText('Um texto qualquer.')
        label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        vbox.addWidget(label)

        push_button = QPushButton()
        push_button.setText('Clique Aqui!')
        vbox.addWidget(push_button)

        self.show()


if __name__ == "__main__":
    import sys

    # Imagem fica fixa e centralizada.
    stylesheet_01 = """
    MainWindow {
        background-image: url('terno.png'); 
        background-repeat: no-repeat; 
        background-position: center;
    }
    """

    # Imagem será redimensionada para o tamanho da janela.
    stylesheet_02 = """
    MainWindow {
        border-image: url('terno.png'); 
    }
    """

    application = QApplication(sys.argv)
    application.setStyleSheet(stylesheet_02)

    window = MainWindow(application=application)

    sys.exit(application.exec())

Faça mais alguns testes para ver se essa ideia atende as suas necessidades.

Se não funcionar vai avisando que vamos tentando pensar em novas possibilidades :blush:

Eu fiz uma adaptação pra minha aplicação e ficou assim:

class MainWindow(QMainWindow):
	def __init__(self, app):
		super( ).__init__( )
		self.app = app
        self.app.setObjectName("main")
		ct = CadTrajes( )
		cadusr = CadUsr( )
		rent = Renting( )
		img = ImageGrab.grab().size
		wid = img[0]
		hg = img[1]
		left = wid // 4
		top = int(hg * 0.1)
		width = wid // 2
		height = int(hg * 0.8)

		vbox = QVBoxLayout( )
		main = QWidget( )
		main.setLayout(vbox)
		self.setCentralWidget(main)

		tab = QTabWidget( )
		tab.addTab(cadusr, " CLIENTES (CADASTRO E EDIÇÃO) ")
		tab.addTab(rent, " ALUGUEL DE TRAJES ")
		tab.addTab(ct, " TRAJES (CADASTRO E EDIÇÃO) ")
		vbox.addWidget(tab)
		self.setWindowTitle("ELEGANT TRAJES")
		self.setGeometry(left, top, width, height)
		self.setWindowIcon(QIcon("Pics/E_Trajes_01.svg"))
		self.setStyleSheet(open('CSS/etrajes.css').read( ))
		#label.setAlignment(Qt.AlignmentFlag.AlignCenter)
		self.show( )


if __name__ == "__main__":
	app = QApplication(sys.argv)
	window = MainWindow(app = app)
	sys.exit(app.exec( ))

Mas, infelizmente, ainda não funcionou.
Eu coloquei a parte do CSS (stylesheet_02) direto no arquivo etrajes.css e fiz a chamada na classe principal, mas mesmo assim não funciona. Eu fiz um teste usando o teu exemplo e funcionou, mas na minha aplicação não vai de jeito nenhum. Sera que é porquê os outros widgets vem de arquivos diferentes? Bom, o que eu estou fazendo de errado?

Depois de muita pesquisa, raiva, vontade de desistir e dor de cabeça, eu consegui chegar no resultado que eu queria. A interface está como foi mostrado na foto alguns posts acima e agora eu tenho acesso a todos os widgets. Ficou assim:

import sys
from PIL import ImageGrab
from Codes.Cad_Trajes import *
from Codes.Cad_Usr import *
from Codes.Rent import *
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMainWindow, QWidget, QApplication, QTabWidget, QVBoxLayout, QLabel, QGraphicsOpacityEffect

class Picture(QWidget):
	def __init__(self, pic, *args, **kwargs):
		super( ).__init__(*args, **kwargs)
		img = ImageGrab.grab( ).size
		wid = img[0]
		hg = img[1]
		width = wid // 2
		height = int(hg * 0.8)
		self.setGeometry(0, 0, width, height)
		self.pic = pic
		self.picLayout = QVBoxLayout( )
		self.picLayout.setContentsMargins(0,0,0,0)
		self.picLayout.setSpacing(0)
		picLabel = QLabel(self)
		pic = QPixmap(self.pic)
		picLabel.setPixmap(pic.scaled(width, height, Qt.KeepAspectRatio, Qt.SmoothTransformation))
		picLabel.setHidden(False)
		picLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
		self.setStyleSheet(open('CSS/etrajes.css').read( ))
		self.picLayout.addWidget(picLabel)
		effect = QGraphicsOpacityEffect( )
		effect.setOpacity(.1)
		picLabel.setGraphicsEffect(effect)
		self.setLayout(self.picLayout)

class Overlay(QWidget):
	def __init__(self, *args, **kwargs):
		super( ).__init__(*args, **kwargs)

		ct = CadTrajes( )
		cadusr = CadUsr( )
		rent = Renting( )
		
		img = ImageGrab.grab( ).size
		wid = img[0]
		hg = img[1]
		left = 0
		top = 0
		width = wid // 2
		height = int(hg * 0.8)
		self.setGeometry(left, top, width, height)
		box = QVBoxLayout( )
		tab = QTabWidget( )
		tab.addTab(cadusr, " CLIENTES (CADASTRO E EDIÇÃO) ")
		tab.addTab(rent, " ALUGUEL DE TRAJES ")
		tab.addTab(ct, " TRAJES (CADASTRO E EDIÇÃO) ")
		tab.setStyleSheet("background-color: transparent;")
		box.addWidget(tab)
		self.setWindowTitle("ELEGANT TRAJES")
		self.setGeometry(left, top, width, height)
		self.setWindowIcon(QIcon("Pics/E_Trajes_01.svg"))
		self.setStyleSheet(open('CSS/etrajes.css').read( ))
		self.setLayout(box)

class MainWindow(QWidget):
	def __init__(self, *args, **kwargs):
		super( ).__init__(*args, **kwargs)
		img = ImageGrab.grab( ).size
		wid = img[0]
		hg = img[1]
		left = wid // 4
		top = int(hg * 0.1)
		width = wid // 2
		height = int(hg * 0.8)
		self.layout = QVBoxLayout( )
		self.layout_2 = QVBoxLayout( )
		self.pic = Picture("Pics/E_Trajes.svg", parent = self)
		self.overlay = Overlay(parent = self)
		self.setStyleSheet(open('CSS/etrajes.css').read( ))
		self.setGeometry(left, top, width, height)

if __name__ == "__main__":
	app = QApplication(sys.argv)
	mw = MainWindow( )
	mw.show( )
	app.exec_( )

FIca aí como exemplo pra quem está tentando fazer algo desse tipo.
Valeu pela força @natorsc

Parabéns, excelente aprendizado!

:clap: :clap: :clap: :clap: :clap:

Solucionei esse “problema” da conversão através de um script que realizar a leitura dos arquivos .ui dentro de uma pasta e converte todos.

Script para conversão de .ui para .py

Depois criei o arquivo run.py que serve para executar os comandos como se estivesse no terminal.

É uma boa estratégia para utilizar para não utilizar o carregamento dinâmico dos arquivos .ui, assim ganhar um pouco de performance na aplicação e tempo no desenvolvimento.

Sou fã do seu trabalho, parabéns!
Abraços.

Fantástico! :clap: :clap:

O seu raciocínio está corretíssimo e o mesmo se aplica a em diversos sistemas de empacotamento como o Flatpak, onde você acabaria descrevendo essa fase de conversão com meson por exemplo.

Recentemente tornei público um repositório sobre PySide6: https://github.com/natorsc/gui-python-pyside-qt.