PyQt 13 – QSpinBox, QProgressBar e + sinais

Este artigo é a parte 13 de 14 na série PyQt

Neste texto vou explorar os widgets QSpinBox, e seu semelhante QDoubleSpinBox, o QProgressBar, além de explorar a conexão de sinais através do Qt Designer. De quebra ainda utilizo o QlcdNumber como saída para um dos resultados do diálogo que será gerado.

A QSpinBox é uma widget que cria um spin box, como estes utilizados para aumentar/diminuir o tamanho de uma letra em um editor de texto. Essencialmente ela funciona como uma lista de inteiros, onde seu valor inteiro é alterado por meio de botões de incremento e decremento. A QDoubleSpinBox faz exatamente o mesmo, mas seu valor é um número real (float), podendo ter incrementos fracionários como: 0,1; 0,02; …

A widget QProgressBar apresenta uma barra de progresso, muito utilizada para indicar o progresso de alguma atividade, como a instalação de um aplicativo, a execução da compressão de um arquivo, entre outras. Aqui a usarei de forma ilustrativa, mais pela conexão de sinais pelo Qt Designer que pela sua aplicação clássica, descrita acima.

A conexão de sinais pelo Qt Designer é uma facilidade muito importante, pois simplifica em muito a codificação, uma vez que estes sinais já estão conectados a ações padrões. Não pretendo fazer nada sofisticado aqui, apenas criar a possibilidade de ilustrar sua aplicação. Estas conexões já foram realizadas em alguns outros diálogos apresentados nos textos anteriores, mas até o momento não dei nenhuma atenção a elas.

O Problema

O diálogo a ser criado será um simples programa para calcular a temperatura de um gás ideal usando a lei dos gases ideais:

P V = n R T

onde $latex R = 8,3144J / mol K$ é a constante dos gases ideais e $latex n$ o número de moles do gás, o qual será feito igual a 1. O diálogo em si é mais ilustrativo que funcional, com principal foco em apresentar algumas propriedades dos widgets em foco. Portanto não espere nenhuma utilidade sólida para este diálogo.

Neste diálogo farei poucas edições usando o Qt Designer, deixando a maioria das configurações aos widgets para serem feitas durante a programação. A intenção é apresentar melhor os métodos das widgets envolvidas.

Para isto construí um diálogo simples como o apresentado na figura abaixo:

A única customização foi aumentar o altura mínima do lcdNumber para 34 pixels (MinimumSize -> Height -> 34), além de renomear as widgets com os nomes apresentados na tabela abaixo:

[TABLE=33]

Conectando os sinais no Qt Designer

A conexão de alguns sinais pode ser feita diretamente no Qt Designer. Para isto pressione a tecla F4, ou acesse o menu Edit -> Edit Signals/Slots. Isto deve deixar o seu diálogo com o aspecto abaixo.

Observe o botão pressionado na barra de ferramentas, logo acima do diálogo. Em seguida, selecione a widget spinBox_pressao e arraste a linha da conexão para a widget progressBar_pressao, para conectar ao slot desejado. Neste momento irá aparecer um diálogo, como o da figura abaixo, listando os sinais emitidos pelo primeiro widget spinBox_pressao, e os possíveis slot da widget progressBar_pressao, que podem ser conectados a eles.

Neste caso é feito a conexão entre o sinal valueChanged(int), em spinBox_pressao, ao slot setValue(int), em progressBar_pressao. A figura a seguir mostra como fica o diálogo após ter sido feita esta conexão.

Para testar pressione CNTRL+R para ver um preview do diálogo. Experimente alterar o valor do SpinBox pressão e observe que o mesmo ocorre com a barra de status abaixo. Caso a tecla de atalho acima não funcione, acesse o preview pelo menu Form -> Preview…

Observe que não é possível fazer uma conexão semelhante entre os widgets doubleSpinBox_volume e progressBar_volume. Isto se deve ao fato de que o argumento do sinal valueChanged no doubleSpinBox ser um double (real de dupla precisão), enquanto que o setValue do progressBar espera um inteiro. Esta alimentação será feita via linha de comando mais adiante.

A próxima conexão será para o botão pushButton_sair, conectando seu sinal clicked() ao slot accept() do diálogo. Para fazer isto, selecione o botão e arraste para um espaço vazio sobre o diálogo. Isto deve abrir o diálogo de conexões como antes, apresentando os sinais do botão pushButton_sair e os slots disponíveis no diálogo.

Se tiver alguma dúvida sobre o widget selecionado, observe o seu nome na parte superior de cada quadro do diálogo de conexão, na figura acima: pushButton_sair e Dialog.

Uma vez feita a conexão, esta ficará visível com abaixo:

Neste momento, no preview, o botão sair fará com que o diálogo termine, emitindo o sinal accept(). Uma outra conexão muito como aqui, seria adicionar mais um botão Cancelar e o conectar ao sinal reject() do diálogo.

O código inicial para este diálogo segue os padrões anteriores, apenas adicionei duas constantes ao início do programa para definir a constante dos gases ideais e o número de moles. Isto é o suficiente para abrir o diálogo e ver o que esta operando:

#!/bin/env python
# -*- coding: iso-8859-1 -*-
#
# gas-ideal por Rudson R. Alves
# Este programa ilustra o funcionamento das widgets
# SpinBox, DoubleSpinBox, ProgressBar e lcdNumber
#

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from gasideal import Ui_Dialog

R = 8.314472
n = 1

class GasIdealDialog(QDialog, Ui_Dialog):
    def __init__(self, parent = None):
        super(GasIdealDialog, self).__init__(parent)
        self.setupUi(self)


app = QApplication(sys.argv)
dlg = GasIdealDialog()
dlg.exec_()

Ao executar este código você verá o diálogo básico como o apresentado durante a edição no Qt Designer. Inicialmente vou fazer algumas mudanças aos widgets spinBox_pressao e progressBar_pressao, alterando alguns de seus parâmetros:

        self.lcdNumber_temp.setMinimumWidth(200)
        self.lcdNumber_temp.setNumDigits(7)

        self.progressBar_pressao.setFormat('%v Pa')
        self.progressBar_pressao.setMinimum(0)
        self.progressBar_pressao.setMaximum(500)

        self.spinBox_pressao.setSuffix(' Pa')
        self.spinBox_pressao.setValue(1)
        self.spinBox_pressao.setRange(0, 500)
        self.spinBox_pressao.setSingleStep(10)

As linhas 22 e 23 alteram a largura da widget lcdNumber_temp para 200 e o número de dígitos apresentados por ele para 7 dígitos.

A linha 25 altera o formato de apresentação no progressBar_pressao, para apresentar o valor com a unidade da pressão Pascal, ‘Pa’. As duas linhas seguintes alteram os limites inferior e superior da barra, para 1 e 500 Pa, respectivamente.

A linha 29 adiciona o sufixo ‘ Pa’, unidade, ao SpinBox pressão, enquanto que na linha 30 o valor da pressão é alterado para 1 Pa no spinBox_pressao. Lempre-se que isto emite o sinal valueChanged() no widget spinBox_pressao, que por sua vez está conectado ao slot setValue() em progressBar_pressao, sincronizando seu valor.

A linha 31 faz o mesmo que as linhas 26 e 27, alterando o valor máximo e mínimo do spinBox_pressao para 0 e 500 Pa, respectivamente. O método setRange(minimum, maximum) está pressente também no widget QProgressBar, não o usei antes apenas por questões ilustrativas.

A linha 32 altera o Step, variação, do SpinBox para 10 Pa. Agora o diálogo deve iniciar com a aparência abaixo:

As próximas linhas, 34 a 38, fazem edições semelhantes aos widgets progressBar_volume e doubleSpinBox_volume, relativos ao volume do gás:

        self.progressBar_volume.setFormat('%p%')

        self.doubleSpinBox_volume.setSuffix(' m^3')
        self.doubleSpinBox_volume.setRange(0.10, 20.00)
        self.doubleSpinBox_volume.setSingleStep(0.50)

O próximo passo é fazer as conexões necessárias para terminar as funcionalidades do diálogo. Primeiro vou cuidar da conexão entre o doubleSpinBox_volume e o progressBar_volume, que não foi feita durante a construção do diálogo. As linhas abaixo cuidam desta conexão:

    @pyqtSignature('double')
    def on_doubleSpinBox_volume_valueChanged(self, v):
        perc = int(100*v/20)
        self.progressBar_volume.setValue(perc)
        self.new_temp()

Este trecho de código conectam o sinal valueChanged(float), emitido pelo widget doubleSpinBox_volume, linhas 41 e 42, ao slot setValue(int) do widget progressBar_volume, linha 44. Neste caso optei por apresentar a porcentagem do volume selecionado, com base no volume máximo de 20m³, calculado na linha 43. A última linha executa o código para calcular a temperatura do gás, apresentado mais adiante.

A última conexão é para fazer com que a temperatura seja recalculada a cada alteração na pressão do gás, o que ainda não está ocorrendo no código. As linhas seguintes fazem isto, além de adicionar a função new_temp, para calcular a temperatura do gás:

    @pyqtSignature('int')
    def on_spinBox_pressao_valueChanged(self, p):
        self.new_temp()


    def new_temp(self):
        P = self.spinBox_pressao.value()
        V = self.doubleSpinBox_volume.value()
        T = P*V/(n*R)-273.16
        self.lcdNumber_temp.display(T)

Este nova captura do sinal valueChanged() não interfere na conexão feita durante a construção do diálogo, no Qt Designer. Na verdade, durante a execução, ambas as conexões serão tratadas, primeiramente a conexão feita pelo Qt Designer, na linha 71 do arquivo gasideal.py reproduzida abaixo, e depois a conexão feita na linha 48-49 acima.

        self.verticalLayout.addLayout(self.horizontalLayout_4)

        self.retranslateUi(Dialog)
        QtCore.QObject.connect(self.spinBox_pressao, QtCore.SIGNAL("valueChanged(int)"), self.progressBar_pressao.setValue)
        QtCore.QObject.connect(self.pushButton_sair, QtCore.SIGNAL("clicked()"), Dialog.accept)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

Bom isto termina o código, a seguir farei apenas mais algumas considerações sobre os sinais das widgets utilizadas.

Sinais das widgets

Neste código exemplo não implementei todos os sinais das widgets envolvidos, ainda porque elas são bem simples e podem ser facilmente implementadas pelo leitor.

A QSpinBox e QDoubleSpinBox, possuem os mesmos sinais, se diferenciando apenas pelo argumento, que no caso do QDoubleSpinBox usa um double enquanto que no QSpinBox o argumento é um inteiro. A tabela abaixo resume os sinais destas widgets:

[TABLE=34]

O widget QProgressBar também emite um sinal valueChanged(int), sempre que o seu valor é alterado. Uma implementação simples deste sinal poderia ser feito com as linhas abaixo, onde os sinal emitidos pelos dois QPregressBar fazem com que uma mensagem simples seja impressa no terminal:

    @pyqtSignature('int')
    def on_progressBar_volume_valueChanged(self, v):
        print u'Seu volume mudou para: %d m^3' % v


    @pyqtSignature('int')
    def on_progressBar_pressao_valueChanged(self, p):
        print u'Sua pressão mudou para: %d Pa' % p

Por fim o QLCDNumber emite apenas um sinal, o overflow() que é emitido quando o QLCDNumber é solicitado a apresentar um número ou string maior que o seu valor limite, configurado pelo método setNumDigits(int), como feito na linha 23 deste código.

Bye

Isto termina mais um texto da série PyQt. O código empregado aqui pode ser baixado no link pyqt-13.zip. Divirta-se.

Este post tem 9 comentários

  1. Amaro

    Realmente muito boa sua série PyQt!!

  2. Rodrigo

    Amigo,

    Os exemplos de código PyQt estão todos com alinhamento Centralizado!

    Obrigado pelas aulas!

  3. rudsonalves

    Deu algum problema com o plugin de formatação. Vou ver se conserto. Valeu.

  4. rudsonalves

    Com a ajuda do Almir, agora está funcionando. Valeu Rodrigo.

  5. Rodrigo

    Valeu Rudson, agora ficou show novamente!

    Obrigado pelo tutorial do PyQt, às vezes venho aqui consultar para tirar uma dúvida!

    Um abraço

  6. Pedro Monteiro

    rudsonalves, realmente muito obrigado por essas suas aulas, me deram uma boa introdução em um programa que preciso desenvolver, muito obrigado mesmo… no mais, queria pedir uma ajuda em relação a uma direção sobre o que estudar para desenvolver um software no qual ele teria várias etapas, ou telas, no qual o usuário clicaria em avançar para ir à próxima ou voltar para retornar à anterior, e em meio a isso, capturar alguns dados, por fim, como eu desenvolvo uma tela com os botões de maximizar/minimizar e também onde eu possa colocar o tamanho da tela constante, sem o usuário poder redimensioná-la, ufs, são muitas perguntas, se puder responder agradeço… ^_^

    1. rudsonalves

      Desculpe a demora. Fixar o tamanho do diálogo é simples, basta passar as dimensões para a função setFixedSize(width, height), que é uma função do QWidget, herdado pelo QDialog. Quanto às várias telas você pode usar diversos diálogos e usar hide() e show() para o diálogo que deseja ativar. Um botão next pode esconder o diálogo corrente e mostrar o seguinte, e um previous o contrário. Mas tem muitas outas formas de fazer isto, tem que conhecer melhor a necessidade.

      1. Diego

        Rudson, dá para setarmos o layout para nulo e setarmos manualmente a posição dos elementos? Se sim, como?

        1. rudsonalves

          Olá Diego.

          Se pegar os primeiros textos da série, faço todo o layout manualmente. Tem algumas ferramentas para isto como grid, layout vertical, horizontal, …

          Tem algumas limitações mas dá para fazer muita coisa. Veja os artigos:

          http://localhost/MyWorks/2009/07/14/pyqt-serie-02/

          http://localhost/MyWorks/2009/07/17/python-pyqt-03-usando-qmessagebox/

          http://localhost/MyWorks/2010/02/27/pyqt-07-qlabel-e-designer-um-gui-designer-para-qt/

          Em todos eles monto diálogos manualmente.

Deixe um comentário

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.