Conhecendo o coração e as artérias do PyQt5

Wilson Martins
22 min readJan 31, 2015

Desvendando um pouco da sua anatomia

Bem, esta é a segunda parte da série de artigos que pretendem abordar exclusiva e unicamente o PyQt5 por completo. Como sabemos, nesta etapa entenderemos como funcionam as coisas por trás do PyQt5. Aprenderemos sobre o sistema de sinais e slots, o sistema de eventos, timers, threads e um pouco mais. Alguns poderiam achar que eu estaria sendo um pouco precipitado em estar abordando threads ou até mesmo timers num segundo artigo sobre PyQt5, mas eu diria que não, pelo fato de ser algo bastante simples de entender e muito útil em diversas ocasiões.

Caso você esteja chegando agora, o primeiro post desta série é o: PyQt5 — O fantástico mundo das GUIs. Nele, abordamos conceitos iniciais e fundamentais para o compreendimento e utilização do PyQt5 como um todo. Então, se ainda não o leu, recomendo fortemente uma leitura.

Em resumo a tudo que veremos aqui, basicamente iremos desbravar o módulo QtCore, local onde fica o coração do PyQt5(do PyQt4, do PySide, do Qt e os seus demais filhos). Sendo assim, é importante que você entenda os conceitos dados aqui para que a luz entre na sua mente. Sem mais delongas, vamos ao que interessa.

Atenção: Apesar deste post ser um tanto grande, o mesmo permite que você não siga um padrão linear para lê-lo. Por exemplo: Você pode iniciar lendo o último tópico presente nele, voltar para o começo e depois ler o meio; Ou até mesmo fazer o que você quiser. Porém, vale ressaltar que, se você ainda não sabe nada dos assuntos tratados aqui ou sabe um pouco é recomendável que se tenha uma leitura linear dele.

Signals e slots

Com certeza, o surgimento dos computadores pessoais foi um marco para a tecnologia da informação. Foi o passo inicial para uma série de evoluções e criações surpreendentes, que hoje estão efetivamente presentes na nossa vida. Vide a internet, que toma quase todo o nosso tempo do dia. Mas nem sempre foi assim, nem sempre existiu toda a acessibilidade dada pelos computadores pessoais.

Na idade da pedra, nós não tínhamos o mouse nem a interface gráfica bonitinha para utilizar o nosso computador. Tínhamos que fazer tudo a mão, aprender uma série de comandos para executar uma simples tarefa. Enfim, tudo isso dificultava muito a acessibilidade dos usuários mais comuns.

Desde o surgimento das interfaces gráficas a forma com que escrevemos softwares para a plataforma desktop mudou drasticamente. Agora, as nossas aplicações seriam baseadas em ações diretas do usuário em uma interface e não mais em comandos em modo texto dados para o nosso software. É neste ponto que o assunto Signal e Slots entra no nosso contexto.

Algumas bibliotecas de interfaces gráficas utilizam callbacks para poder identificar as ações realizadas pelos usuários na nossa interface, e embora eles cumpram com os seus deveres, os mesmos possuem duas falhas que fizeram com que o Qt não as utilizasse. Sendo elas: a primeira delas é que um callback não é do tipo seguro(type safe), ou seja, nós não teremos certeza de que o callback chamará o método com os argumentos declarados; O segundo problema encontrado na utilização dos callbacks é que eles são fortemente acoplados à função de processo. Vejamos a imagem 1.1 para um melhor compreendimento sobre Signal e Slots.

Imagem retirada da documentação do Qt. Explica basicamente como são os Signals e Slots.

Em alternativa a utilização dos callbacks, temos o Signals e Slots, disponibilizado no Qt. Um sinal é emitido todas as vezes que o estado de um objeto é modificado, ou seja, todas as vezes que ocorre uma ação(como clicar em um botão). Quando um Signal é ativado, ele chama o Slot(ou um método) que estiver conectado a este sinal. Na imagem acima, podemos ver claramente isso.

Por exemplo, no objeto 1. Vamos supor que este objeto fosse um botão, sendo assim, este botão teria dois sinais(Signal 1 e Signal 2) conectados a dois Slots(Slot 1 e Slot 2). Todas as vezes que um sinal relacionado a este botão for emitido, ele chamará o slot correspondente a ele. Vale ressaltar que ao invés de Slot poderíamos ter métodos, como veremos mais a frente.

Para entrarmos no contexto, vamos criar um exemplo simples onde clicamos em um botão e ele nos exibe em um diálogo o texto que estiver escrito dentro de uma linha de texto. Como vimos no post anterior, o PyQt tem basicamente uma estrutura de código para criar uma simples janela, porém caso não esteja lembrado, veja o código a seguir.

Acima temos basicamente uma janela, porém, para o nosso exemplo nós precisamos mais que isso, precisamos de um botão, um layout, uma área onde o usuário possa inserir o texto e um diálogo(neste caso será o MessageBox). Sendo assim, vamos modificar as nossas importações para que possamos utilizar os recursos descritos anteriormente. As importações devem ficar similares ao trecho de código abaixo.

A diferença entre as importações acima e as anteriores é que nós adicionamos as classes QPushButton, QLineEdit, QHBoxLayout e QMessageBox. O QPushButton, como já sabemos, nos fornece um botão. O QLineEdit nos oferece uma linha de texto para o usuário digitar algo. O QHBoxLayout nos fornece um layout na posição horizontal e o QMessageBox uma caixa de diálogo com algumas configurações pré-definidas.

Agora que temos as importações, devemos encaixar os nossos widgets dentro da interface. Sendo assim, o nosso código ficará da seguinte maneira.

Basicamente o que temos no código acima é uma declaração de um botão, a declaração de uma linha de texto(ou área de texto) e a declaração de um layout simples. Não é necessário que entendamos agora o funcionamento do layout e sim, o funcionamento dos Signals e Slots. Pois bem, faltamos um toque final para que a nossa aplicação possa ser exibida. Como vimos no post anterior, o nosso trecho de código final ficará desta forma:

Ao executarmos o nosso código veremos que a linha de texto está ao lado do botão e que o layout, por esse motivo, está funcionando corretamente. Porém, há um problema, a nossa aplicação “não tem vida”. Neste ponto, utilizaremos os conceitos dados anteriormente aqui, neste código. O que faremos basicamente é: quando o usuário clicar no botão, nós pegaremos o texto que está presente dentro da área de texto e o mostraremos em um diálogo. Para tal, deveremos conhecer a forma como conectamos um objeto a um método ou slot através de um sinal.

Formas de conectar

Até meados do PyQt 4.8 a forma com que realizávamos a conexão descrita anteriormente era bem pouco intuitiva e legível. Na verdade ela se assemelha muito a forma com que é feito no Qt. Sendo assim, a forma era a seguinte:

Ou seja, possuíamos um código não muito legível e feio. Mas eis que surge a luz e após esta versão surgiu uma nova forma de conectar os nossos objetos aos nossos métodos, desta vez bem mais intuitiva, legível e bonita. A sua fórmula é a seguinte:

Ao olhar o código acima, percebemos claramente que estamos conectando um objeto a um método através de um sinal. Além de ser mais fácil de ler, é muito mais fácil de escrever. Um exemplo real da utilização da fórmula seria:

Especificamos que o botão está conectado a um método através do sinal clicked.

Voltando ao código

Aplicando o que aprendemos anteriormente, podemos conectar o nosso botão a um método e criar este método para executar o que desejamos que ele execute. Sendo assim, o nosso código inicialmente ficará da seguinte forma:

Agora que temos o nosso botão conectado ao nosso método, deveremos criar este método, obviamente. Nele, teremos que obter o texto presente na área de texto, criar um objeto da classe QMessageBox e exibi-la na tela. Ficará desta forma:

Vale ressaltar que o método acima deve ficar dentro do escopo da nossa classe Window. Para obter o texto do QLineEdit utilizamos o método text() e armazenamos este texto dentro de uma variável. Após isso criamos um objeto da classe QMessageBox e utilizamos o método information() para especificar que desejamos ter um diálogo de informação.

Conhecendo um pouco mais sobre signals

Sabemos que os signals são emitidos todas as vezes que o estado de um objeto é modificado, ou seja, quando ocorre por exemplo um clique em um botão. Os signals nada mais são do que funções públicas que podem ser emitidas de qualquer lugar, porém é recomendável que o sinal somente seja emitido da classe que a define ou de alguma subclasse.

Quando um sinal é emitido, automaticamente o slot ou método conectado a ele é executado. Esta chamada de método é totalmente independente de qualquer event loop presente na nossa GUI, evitando travamentos. Outra característica interessante do Signal e Slots é que ao invés dos callbacks, eles são desacoplados um do outro, ou seja, uma classe que emite um sinal não se importa nem conhece quais slots irão receber este sinal.

Todo widget presente no PyQt possui os seus próprios sinais e algumas vezes slots. Por exemplo: um botão possui três tipos de sinais; o clicked, o pressed e o release. Já um QSpinBox possui apenas um sinal, o valueChanged. Ou seja, um botão que emite um sinal do tipo clicked não se importará quais slots receberão este sinal, até porque o sinal pode não estar conectado a um slot e sim a um método.

Voltando ao exemplo inicial

Podemos incrementar a nossa aplicação exibindo diversos tipos de diálogos. Para tal, utilizaremos um widget chamado QRadioButton, onde o usuário irá escolher qual utilizar para exibir a mensagem que ele deseja. Também utilizaremos as classes QVBoxLayout e QGroupBox para deixar a nossa aplicação mais organizada. Sendo assim, temos que adicionar à nossa importação as classes descritas anteriormente.

Primeiramente devemos modificar as nossas importações, que ficará da seguinte maneira:

Após isso, devemos criar os layouts correspondente a cada conjunto de widget. A nossa classe Window ficará da seguinte maneira:

Como já mencionado, não é necessário que compreendamos ainda sobre as diversas formas de layout presentes no PyQt, pois abordaremos isto com mais profundida no próximo post. Para uma explicação rápida, o que fazemos acima é: criamos um objeto da classe QGroupBox para agrupar os três objetos da classe QRadioButton e após isso organizamos os radio buttons dentro de um layout com orientação na posição vertical. Depois, organizamos o botão e a área de texto em um layout horizontal(como tínhamos feito anteriormente) e por fim, criamos um layout vertical para alinhar o GroupBox e o layout horizontal numa posição vertical. O resultado será este:

Resultado do código descrito logo acima.

Também deveremos modificar o método exibir() que criamos anteriormente. Ele deverá ficar da seguinte forma:

No trecho de código acima, modificamos o método exibir() para que ele verifique qual dos radio buttons estão marcados e assim, definir qual tipo de diálogo exibir. Podemos ver o resultado desta simples aplicação logo abaixo.

Versão final da aplicação.

Handler Events

Assim como a emissão de signals, um evento ocorre todas as vezes que o usuário realiza uma ação na nossa interface. Porém, diferentemente dos signals todos os eventos são derivados da classe QEvent. Alguns eventos em especial possuem classes próprias para tratá-los, como é o caso do closeEvent(), que é um método presente em classes como: QWidget, QMainWindow e QDialog. E ainda sim possui uma classe chamada QCloseEvent, que disponibiliza métodos especiais para tratá-lo de uma forma mais livre.

Quando um evento ocorre no Qt, ele automaticamente cria um objeto deste evento para poder representá-lo através da construção de uma subclasse da classe QEvent ou qualquer outra que derive da mesma, e depois o entrega para uma instância específica da classe QObject chamando o método event().

Através dos eventos nós podemos realizar uma série de ações e manipulações na nossa interface. Na realidade, podemos controlar quase tudo. Uma classe(ou widget), assim como os signals, já vem com uma série de eventos pré-definidos que precisamos apenas declará-los para modificá-los. Este eventos “pré-definidos” são chamados de handler events. Um claro exemplo disso é o já citado closeEvent().

Os handler events possuem algumas pequenas características das quais devemos ter conhecimento. A primeira coisa que devemos prestar atenção é que, diferentemente dos métodos que declaramos para os signals, um handler event exige um parâmetro adicional na sua declaração, ou seja, ele exige que tenhamos um parâmetro a mais para poder tratar aquele determinado evento, seja ignorando, seja aceitando-o, etc.

Outro fato interessante sobre os handler events é que eles nada mais são do que métodos virtuais pertencentes às classes das quais derivamos. Por exemplo: no nosso exemplo anterior, nós utilizamos como top-level da nossa aplicação a classe QWidget, que por sua vez, possui uma série de métodos, eventos, signals e slots implementados. Quando herdamos a classe QWidget na nossa classe e declaramos um método como o closeEvent(), estamos fazendo nada mais nada menos do que sobrescrever este método para que ele haja da forma que desejamos.

Para um exemplo prático, vamos perguntar ao usuário todas as vezes que ele clicar no botão de fechar da janela se ele deseja realmente fechá-la. Inicialmente, nós sabemos que temos o seguinte código(os passos finais foram ocultados mas devem ser mantidos no seu código original):

Devemos adicionar o método closeEvent() explicitamente ao escopo da nossa classe com um parâmetro adicional, podendo nomeá-lo da forma que desejar. Da seguinte maneira:

A primeira coisa que fazemos é ignorar o evento de fechamento da nossa aplicação, para assim podermos exibir o diálogo para o usuário. Através do diálogo de questionamento perguntamos se o mesmo deseja fechar a aplicação, caso ele clique no no botão “Yes”, então fechamos a nossa aplicação.

Além da situação citada anteriormente, os handler events são úteis em diversas outras ocasiões, como por exemplo: no redimensionamento de janelas, na minimização de janelas, na utilização dos drag and drops em diversos widgets, etc.

Handler events em widgets específicos

Vimos acima que é possível utilizar handler events para uma série de situações, mas não vimos como é possível utilizar um handler event em um widget específico, ou seja, fora de uma janela. Para falar verdade, isso é bem simples, uma vez que estaremos apenas incrementando funcionalidades em um determinado widget. Para este exemplo, iremos abondar o código escrito anteriormente e iremos fazer o seguinte: todas as vezes que o usuário clicar sobre uma imagem que ficará dentro de um QLabel, ele abrir um diálogo para o usuário abrir outra imagem. Caso ainda não saiba, um QLabel só emite dois sinais, que são eles: o linkHovered() e o linkActivate(), que somente são utilizados quando temos um link em um label(isso é possível através de tags html).

Para iniciarmos vamos primeiramente criar um novo arquivo e definir o nome dele como: signal_emited.py. Nele criaremos a estrutura básica de toda aplicação(simples) que utiliza o PyQt. Dentro da nossa classe nós não adicionaremos nada ainda, pois como já descrito, nós teremos que incrementar funcionalidades na classe QLabel para que ela reconheça o clique de um botão. Sendo assim, vamos criar uma outra classe antes da classe principal da nossa aplicação e herdá-la da classe QLabel. Inicialmente ela ficará desta forma:

Nós declaramos o método setMouseTracking() como True para informar que desejamos obter a posição do cursor do mouse sempre que ele se mover. Isso será importante para quando formos modificar o ponteiro do mouse. A segunda coisa que deveremos fazer é declarar o handler event mousePressEvent. Este evento é ativado todas as vezes que o usuário clica sobre algo, permitindo a nós fazer com que a nossa imagem reconheça um evento de clique, similar ao clicked presente nos botões. Ficará dessa forma:

Vale ressaltar que este método deve ficar dentro do escopo da nossa classe CustomLabel. Basicamente declaramos um QFileDialog(que deveremos acrescentar às nossas importações, que fica no módulo QtWidgets), inserimos um título e após isso um filtro. Percebemos também que ele recebe duas variáveis, isso acontece porque ele retorna dois valores. O primeiro deles é a url do arquivo selecionado e a segunda é se o usuário clicou em Ok ou Cancel, retornando respectivamente True ou False. Depois, pegamos a url do arquivo que o usuário selecionou e colocamos no nosso label.

A nossa classe principal deverá ficar da seguinte maneira.

Para deixar a nossa aplicação um pouco mais intuitiva, vamos fazer o seguinte. Quando o usuário passar o mouse por cima da nossa imagem, o cursor mudará para uma mão(aquela que aparece quando você vai clicar em um link ou uma imagem no Facebook). Pois bem, deveremos adicionar a classe CustomLabel estes dois métodos.

É importante que o método setMouseTracking esteja habilitado.

Estudando os timers

Timers nada mais são do que temporizadores e eles podem ser utilizados para uma série de coisas, tais como: verificar algo a cada 3 segundos, estabelecer timeouts, criar cronômetros, etc. Para esta seção nós utilizaremos dois exemplos: o primeiro e mais simples será a criação de uma barra de progresso e o segundo e um pouco mais avançado, será a criação de um software para tirar screenshot.

O PyQt disponibiliza basicamente de duas classes para nós tratarmos os timers, sendo elas: QBasicTimer e QTimer. A diferença entre essas duas, é que a classe QBasicTimer fornece métodos simples para o gerenciamento de temporizadores, enquanto a QTimer oferece métodos como o singleShoot() e sinais como o timeout que permite um melhor controle sobre os nossos temporizadores.

Utilizando um timer na classe QProgressBar

Neste exemplo mais simples e inicial, utilizaremos como já descrito a classe QBasicTimer. Ela fornece quatro métodos principais: o start(), o stop(), o isActive() que retorna True quando o timer está ativo e o timerId() que retorna um inteiro indicado qual é o id do temporizador.

Uma coisa que devemos saber antes de utilizar os timers é que sempre que um timer é ativado, um handler event é chamado. Neste caso o handler event que é chamado é o timerEvent. Será este handler event que utilizaremos. Nas nossas importações devemos incluir a classe QBasicTimer que fica dentro do módulo QtCore, afinal, ela faz parte das features do core fornecidas pelo PyQt. As importações ficarão da seguinte maneira:

Após isso, vamos criar a nossa classe, como de costume, e um método settings(). Ficará da seguinte maneira:

Agora devemos criar o nosso método create_widgets(), que ficará responsável por criar os widgets(obviamente) e inicializar o nosso temporizador. O nosso método ficará assim:

No trecho de código acima, nós definimos um objeto da classe QProgressBar onde podemos notar a utilização do self durante a criação do objeto. Como já explicado, quando um determinado widget não está contido diretamente em um determinado layout, nós deveremos referenciar o pai(ou parent, com preferir) para garantir que ele seja exibido. Depois, criamos um objeto da classe QBasicTimer, uma variável e por fim, inicializamos o nosso temporizador.

O método start() pode receber 3 parâmetros, sendo eles: o timeout em milissegundos, ou seja, de quanto em quanto tempo vamos ativar o handler event timerEvent; o segundo parâmetro corresponde ao tipo de temporizador e o terceiro está referente ao nosso pai(ao parent). Para terminamos, devemos declarar o handler event timerEvent e adicionar uma simples lógica a ele, como podemos ver logo abaixo:

A lógica por trás do código acima é bem simples: primeiro o objeto do QBasicTimer emite um evento, no qual é ativado o timerEvent, após isso nós verificamos se a variável self.step é igual a 100, caso contrário, incrementamos mais um a ele e inserimos o valor dela no nosso ProgresssBar. E isso se repete até o valor chegar ao número 100. É basicamente como um loop. Podemos ver o resultado na imagem abaixo.

Tirando screenshot com PyQt

Como mencionado anteriormente, agora teremos um exemplo da utilização dos timers um pouco mais interessante, criando um software para tirar screenshot da nossa tela, like a KSnapshot. Isto servirá para um melhor entendimento do seu funcionamento, além de fornecer maiores exemplos sobre a utilização dos timers no PyQt.

Inicialmente nós iremos, como sempre, realizar as importações necessárias. Necessitaremos das seguintes classes para a interface gráfica: QWidget, QApplication, QPushButton, QLabel, QGridLayout, QFileDialog e QPixmap. Porém não assuste-se, o código ao total tem 60 linhas. As nossas importações ficarão da seguinte maneira:

Devemos prestar atenção no código acima. Vemos que a classe QPixmap não foi importada do módulo QtWidgets e sim do módulog QtGui. Como explicado no post de introdução ao PyQt, no PyQt5 a separação das classes de acordo com as suas responsabilidades agora estão bastante definidas em cada módulo. Após isso, vamos declarar a nossa classe e o construtor inicial da mesma.

A quarta linha é a linha que faz o screenshot ser possível no PyQt, ou seja, ela quem faz a mágica acontecer. Nós atribuímos um valor “concreto” para a variável self.preview_screen para que ao usuário iniciar a nossa aplicação ele possa ver em algum local o screenshot da mesma, assim como acontece no software KSnapshot. Ainda no inicializador da nossa classe nós temos os métodos settings(), create_widgets() e set_layout, que servirá para nós criarmos e organizarmos toda a nossa interface. O método settings() e create_widgets() ficarão da seguinte maneira:

Como já sabemos o método settings() insere configurações para a nossa janela, já o create_widgets cria e configura os widgets necessários. Nela criamos um label para exibir um preview do nosso screenshot, criamos dois botões e por fim, conectamos estes botões aos seus respectivos métodos. Pois bem, primeiramente iremos criar o método para salvar a nossa screenshot em algum lugar do computador. Realizar isto é simples, uma vez que necessitamos apenas do diretório e nome do arquivo desejado pelo usuário. Ficará assim:

No trecho de código acima nós primeiramente declaramos duas variáveis que receberiam os valores retornados pelo método getSaveFileName(). Também neste método definimos que o usuário somente poderá salvar imagens .jpg ou .png através do parâmetro filter. Por fim, nós temos dois condicionais para verificar mesmo se o arquivo está com a extensão correta. Caso alguma das condições forem verdadeiras então salvamos o arquivo no local indicado com a sua respectiva extensão.

O próximo método a ser criado será o new_screenshot() que ficará responsável por retirar um novo screenshot e atualizar a imagem presente no label. Ele ficará da seguinte maneira:

Nele primeiramente nós ocultamos a nossa janela para que ela não apareça no screenshot, após isso nós tiramos o screenshot(como fizemos lá no inicializador da classe), atualizamos a imagem presente no label e por fim mostramos novamente a nossa janela.

Agora devemos apenas criar uma instância da classe QApplication e todo o resto que nós já sabemos. Sendo assim, temos a nossa aplicação pronta, certo? Então por que não testá-la? Ao executar a nossa aplicação tudo ocorre normalmente, porém quando nós tiramos um novo screenshot acontece o que podemos ver na imagem abaixo.

WE HAVE PROBLEMS!

Bem, parece que quando tiramos um novo screenshot a nossa aplicação aparece e isto era o que nós não queríamos. Mas e a solução? A solução é utilizar timers!

Para tal, iremos modificar o método new_screenshot() da seguinte maneira:

O que o método singleShot() da classe QTimer fará é o seguinte: após 1 segundo ele chamará o método take_screenshot() para tirar o screenshot da nossa tela sem que a nossa aplicação apareça nela. Para finalizar, abaixo do método new_screenshot() vamos definir o método que acabei de citar. Ele ficará basicamente como o new_screenshot() estava anteriormente.

O método acima tira uma screenshot e armazena na variável self.preview_screen, atualiza a imagem presente no label e por fim, mostra a nossa aplicação de volta. Agora, devemos executar novamente a nossa aplicação e ver o resultado. Para um teste mais conciso vamos tirar um screenshot após a inicialização do software para vermos o que aconteceu.

E tcharam! A nossa aplicação agora funciona perfeitamente graças aos Timers.

Dando um agasalho à sua interface

Estamos quase no final deste post e nada melhor do que finalizá-lo com um assunto muito importante nos dias de hoje que é a utilização de Threads na nossa interface. Utilizar threads são necessárias em diversos cenários, desde em uma aplicação web, até mesmo no mobile.

Utilizamos as threads basicamente para solucionar um problema: travamento da nossa aplicação. Quando a nosso software é executado, independente se ele está utilizando algum framework ou não, ele automaticamente possuirá uma thread para poder executar o que ele deve executar, porém os problemas começam a surgir quando nós temos executar algo que trava a thread principal do nosso software, como por exemplo um loop. Para tal, vamos criar inicialmente um simples exemplo de como travar a nossa GUI.

Inicialmente criaremos mais um arquivo, desta vez denominado de thread_test.py e dentro dele criaremos a estrutura comum de uma janela no PyQt. Após isso, vamos criar um botão que quando ativado irá executar um loop infinito e veremos o que irá acontecer. Este exemplo inicial ficará basicamente da seguinte maneira:

Agora, devemos executar a nossa aplicação e clicar no botão para vermos o que irá acontecer. Bem, como já havíamos previsto a nossa aplicação travou completamente(e se você estiver utilizando o Sublime Text, travou ele junto também). A solução é mais simples do que imaginamos.

Solucionando problemas

Para solucionar esse problema o que temos que fazer é simplesmente criar uma outra thread separada da nossa thread principal para podermos executar este loop sem travar a nossa aplicação. Para tal, poderíamos utilizar módulos presentes no Python, porém, como o PyQt possui classes para isto, vamos utilizar a classe disponibilizada por ele.

A classe que utilizaremos será a QThread(nome óbvio). O PyQt ainda disponibiliza de outra classe chamada QThreadPool que permite gerenciar multiplas threads de uma forma concisa e simples. A classe QThread, assim como todas as outras abordadas neste tutorial, está presente dentro do módulo QtCore. Iremos criar uma nova classe que será herdada da classe QThread e que também será definida antes da nossa classse em que criamos a nossa janela anteriormente. Então, mãos a obra.

A nossa importação ficará da seguinte maneira:

Após isso, declaramos a nossa classe que herdará da classe QThread. Neste contexto ela não necessitará de um inicializador, já que não iremos utilizar nenhum parâmetro para a nossa classe neste exemplo. Sendo assim, definimos nela apenas o método run(), que será chamado automaticamente quando chamarmos o método start() através do objeto da nossa classe. Vejamos logo abaixo.

Também iremos modificar o método que está conectado ao botão para ele chamar a thread e não executar o loop diretamente. Sendo assim, o nosso método ficará da seguinte forma:

Ao executar a aplicação novamente percebemos que ela ainda continua a mostrar o print declarado no loop, porém desta vez a aplicação não trava e continua a funcionar normalmente.

Indo um pouco mais além

Agora que sabemos como funciona basicamente a classe QThread podemos criar algo um pouco mais avançado para explorarmos ainda mais esta classe e as funcionalidades do PyQt. Para tal, criaremos um software para fazer download de músicas no formato mp3.

Antes de começarmos, devemos ter pelo menos uma ideia de como será fundamentada a nossa interface, porém, como este é um tutorial nós já possuímos o resultado dela pronta. Sendo assim, ao final deste tópico nós teremos a seguinte aplicação:

Resultado final da nossa aplicação.

Baseado nesta interface podemos de cara definir quais são as classes que precisamos para criá-la. São elas: QFormLayout, QLineEdit, QPushButton, QWidget e QFileDialog. Outras classes que utilizaremos para implementar outras funcionalidades presentes no software serão: QThread, QRegExp e requests. As nossas importações ficarão da seguinte maneira:

A primeira classe que criaremos será a responsável por realizar o download do arquivo mp3 e salvá-lo no computador. Como já sabemos, esta classe herdará da classe QThread. Também receberá três parâmetros na sua instanciação, sendo eles: o nome do arquivo, o local onde deveremos salvar o arquivo e a url para fazer o download. Ficará da seguinte forma:

No método run() o que fazemos basicamente é: mesclar o local em que o usuário selecionou para salvar o arquivo com o nome que ele deseja e depois realizamos o download do arquivo através da biblioteca requests que foi importada logo acima. Após isso vamos definir uma segunda classe, porém, desta vez contendo a nossa interface gráfica.

No inicializador da nossa classe vamos definir a ordem de chamada dos métodos que criarão e organizarão a nossa interface. Ficará da seguinte maneira:

Nela podemos ver que uma variável foi declarada, esta chamada de self.path. Ela ficará responsável por armazenar o diretório onde deveremos salvar a música. O método settings() e o create_widgets() ficarão da seguinte maneira:

No settings() definimos algumas configurações para a nossa janela. No create_widgets(), como já esperado, definimos os widgets que utilizaremos e realizamos duas conexões: A primeira delas é conectando o objeto self.btn_select_path ao método select_path(), e a segunda é conectando o objeto self.btn_down ao método download(). Para finalizar a interface, deveremos organizá-la dentro de um layout. Para tal utilizaremos a classe QFormLayout, pois para a forma com que está apresentada a organização dos widgets é a mais condizente(estudaremos isso no próximo capítulo).

Agora iremos criar dois métodos, estes que estão conectados aos dois botões descritos no método create_widgets(). Ficará assim:

Os dois métodos acima são bem simples, uma vez que o primeiro apenas exibe uma caixa de diálogo perguntando ao usuário em qual diretório salvar a música e logo atribuindo este valor retorna a variável self.path. O segundo método contém referências(chamadas) para outros métos que criaremos a seguir. O verify_fields(), como o próprio nome já diz, verifica se os campos(diretório onde salvar o arquivo, nome do arquivo e url da música) estão preenchidos corretamente. Caso estejam então chamamos o método manage_interface() com o parâmetro False, indicando que nós queremos desabilitar todos os widgets e posteriormente chamamos o método thread_qt() que criará um objeto da classe DownloaderMusic(criada anteriormente) passando os parâmetros necessários, além de iniciar o download.

O método verify_fields() ficará da seguinte maneira:

O interessante do método acima é que ele utiliza a classe QRegExp para poder validar os campos, onde na verdade nós poderíamos muito bem utilizar um módulo do próprio Python, porém nós devemos seguir a seguinte lógica(há exceções para isso): Se existe uma funcionalidade no PyQt, utilize-a e esqueça as opções externas a ela.

Agora, deveremos criar os métodos thread_qt() e manage_interface(). Eles ficarão da seguinte maneira:

No método thread_qt percebemos que temos um outro objeto conectado a um outro método, ou seja, o objeto self.thre está conectado ao método self.downfin com a condição de quê: Quando a thread for finalizada chame-o. Sendo assim, também deveremos criá-lo por outro motivo: quando nós iniciamos o download nós simplesmente desabilitamos os widgets da nossa aplicação para o usuário(bobalhão) não ficar mexendo nela sem necessidade. Quando o download termina não tem porquê os widgets ficarem desabilitados, sendo assim, temos que reabitá-los para o usuário poder utilizar novamente o software. E aproveitando o gancho podemos mostrar uma notificação para o mesmo de quê o download foi finalizado com sucesso. Este método ficará da seguinte forma:

A classe QSystemTrayIcon também fornece outras funcionalidades como por exemplo exibir um ícone do seu programa na sessão de ícones ocultos da barra de tarefas do Windows. No código acima ela apenas exibe uma mensagem com o título:”Download Finalizado”; e a frase:”O download da sua música foi realizado com sucesso”. Também definimos que a mensagem ficará exibida por 3 segundos. Por fim, reabilitamos a interface.

Para finalizar este software, vamos criar uma instância da classe QApplication e realizar aqueles procedimentos padrões que já conhecemos.

Finalizando

Pois bem, chegamos a mais um final de um post sobre o PyQt5. Embora tenha sido um post um tanto grande, isto não é tudo sobre o módulo QtCore, e sim uma parte dele. Por exemplo no módulo QtCore nós temos a possibilidade de manipular arquivos, manipular arquivos JSON(veremos na quarta parte), manipular arquivos XMLs, dentre outros.

Neste post iniciamos introduzindo os conceitos básicos sobre Signals e Slots, exploramos um pouco os Signals, posteriormente aprendemos sobre Handler Events e fizemos com que um QLabel reconhecesse um clique. Após isso iniciamos os nossos estudos sobre temporizadores(Timers) onde nos foi apresentado dois exemplos para uma melhor compreensão. Por fim, conhecemos um pouco da classe QThread criando um software para realizar download de arquivos mp3.

No próximo post iremos desbravar o mundo colorido e reluzente das GUIs, onde aprenderemos sobre Dialogs, Main Windows, a agilizar a criação da interface com QtDesigner, layouts e muito mais.

Então é isso, e até a próxima.

--

--