Kirjoittaja: The Alchemist
Kirjoitettu: 23.12.2020 – 23.12.2020
Tagit: kirjaston käyttö, käyttöliittymä, ohjelmointitavat, hyvää koodia, koodi näytille, sovellus, vinkki, työpöytä
Niin sanottu single file -esimerkki graafisen peruslaskimen toteuttamisesta PyQt 5 -kirjastolla.
Laskin osaa laskea (vain) kahdesta numerosta ja yhdesta operaattorista koostuvat laskut.
Kun ruudulle on syötetty kaava muodossa [numero] [operaattori] [numero], laskin suorittaa laskutoimenpiteen automaattisesti ja päivittää tuloksen ruudulle.
Erikoisominaisuutena ruudulla näkyvää tulosta voidaan käyttää automaattisesti seuraavan laskun ensimmäisenä numerona, kun syötetään uuden numeron sijaan operaattori.
import math import sys from PyQt5.QtCore import Qt from PyQt5 import QtCore, QtGui, QtWidgets class MainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.setup() self.calculator = Calculator() self.equation = [] def setup(self): container = QtWidgets.QWidget(self) display = QtWidgets.QLabel() display.setFont(QtGui.QFont("monospace", 20)) display.setAlignment(Qt.AlignRight | Qt.AlignVCenter) display.setText("0") numpad = Numpad(self) numpad.number.connect(self.onNumber) numpad.operator.connect(self.onOperator) layout = QtWidgets.QVBoxLayout() layout.addWidget(display) layout.addWidget(numpad) container.setLayout(layout) self.setCentralWidget(container) self.display = display self.numpad = numpad def onNumber(self, number): if len(self.equation) == 4: self.equation = [] self.display.setText("") if len(self.equation) == 0: self.display.setText(str(number)) self.equation.append(number) if len(self.equation) == 2: new_text = "{0} {1}".format(self.display.text(), number) self.display.setText(new_text) self.equation.append(number) self.compute() def onOperator(self, operator): if len(self.equation) == 4: result = self.equation.pop() self.equation = [] self.onNumber(result) if len(self.equation) == 1: new_text = "{0} {1}".format(self.display.text(), operator) self.display.setText(new_text) self.equation.append(operator) def compute(self): result = self.calculator.compute(*self.equation) new_text = "{0} = {1}".format(self.display.text(), result) self.display.setText(new_text) self.equation.append(result) class Numpad(QtWidgets.QWidget): number = QtCore.pyqtSignal([int]) operator = QtCore.pyqtSignal([str]) def __init__(self, parent=None): super().__init__(parent) self.setup() def setup(self): layout = QtWidgets.QGridLayout() self.setLayout(layout) numbers = range(0, 10) operators = ("+", "-", "*", "/") emit_number = lambda num: lambda: self.number.emit(num) emit_operator = lambda op: lambda: self.operator.emit(op) for num in numbers: button = QtWidgets.QPushButton(str(num), self) button.clicked.connect(emit_number(num)) if num == 0: layout.addWidget(button, 3, 0, 1, 3) else: row = math.floor((num - 1) / 3) col = (num - 1) % 3 layout.addWidget(button, row, col) for i, op in enumerate(operators): button = QtWidgets.QPushButton(op, self) button.clicked.connect(emit_operator(op)) layout.addWidget(button, i, 3) class Calculator: def compute(self, first_number, operator, second_number): ops = { "+": lambda: first_number + second_number, "-": lambda: first_number - second_number, "*": lambda: first_number * second_number, "/": lambda: first_number / second_number, } return ops[operator]() if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) window = MainWindow() window.show() app.exec_()
Tässä vielä äärimmilleen viety esimerkki sovelluslogiikan ja käyttöliittymäkomponenttien eristämisestä toisistaan. Lienee mielipidekysymys, kummallako tavalla toteutettu laskin on paremman laatuista koodia.
Tämän sovelluksen toiminta on identtinen tuon ylläolevan kanssa. Koodia on kuitenkin liki 40 riviä / 33 % enemmän. Hmm...
import math import sys from PyQt5.QtCore import Qt from PyQt5 import QtCore, QtGui, QtWidgets class MainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.setup() self.calculator = Calculator() def setup(self): container = QtWidgets.QWidget(self) display = QtWidgets.QLabel() display.setFont(QtGui.QFont("monospace", 20)) display.setAlignment(Qt.AlignRight | Qt.AlignVCenter) display.setText("0") numpad = Numpad(self) numpad.number.connect(self.onNumber) numpad.operator.connect(self.onOperator) layout = QtWidgets.QVBoxLayout() layout.addWidget(display) layout.addWidget(numpad) container.setLayout(layout) self.setCentralWidget(container) self.display = display self.numpad = numpad def onNumber(self, number): if self.calculator.has_result(): self.display.setText("") self.calculator.add_number(number) if self.calculator.can_compute(): self.compute() else: self.update() def onOperator(self, operator): if self.calculator.has_result(): result = self.calculator.result self.calculator.clear() self.onNumber(result) if self.calculator.set_operator(operator): self.update() def compute(self): self.update() result = self.calculator.compute() new_text = "{0} = {1}".format(self.display.text(), result) self.display.setText(new_text) def update(self): new_text = " ".join(map(str, self.calculator.equation)) self.display.setText(new_text) class Numpad(QtWidgets.QWidget): number = QtCore.pyqtSignal([int]) operator = QtCore.pyqtSignal([str]) def __init__(self, parent=None): super().__init__(parent) self.setup() def setup(self): layout = QtWidgets.QGridLayout() self.setLayout(layout) numbers = range(0, 10) operators = ("+", "-", "*", "/") emit_number = lambda num: lambda: self.number.emit(num) emit_operator = lambda op: lambda: self.operator.emit(op) for num in numbers: button = QtWidgets.QPushButton(str(num), self) button.clicked.connect(emit_number(num)) if num == 0: layout.addWidget(button, 3, 0, 1, 3) else: row = math.floor((num - 1) / 3) col = (num - 1) % 3 layout.addWidget(button, row, col) for i, op in enumerate(operators): button = QtWidgets.QPushButton(op, self) button.clicked.connect(emit_operator(op)) layout.addWidget(button, i, 3) class Calculator: def __init__(self): self.equation = [] self.result = None def add_number(self, number): if len(self.equation) == 3: self.equation = [] if len(self.equation) == 0 or len(self.equation) == 2: self.equation.append(number) self.result = None return min(len(self.equation), 2) return None def set_operator(self, operator): if self.has_result(): self.equation = [] self.add_number(self.result) if len(self.equation) == 1: self.equation.append(operator) return True return False def can_compute(self): return len(self.equation) == 3 def has_result(self): return self.result is not None def clear(self): self.equation = [] self.result = None def compute(self): ops = { "+": lambda: first_number + second_number, "-": lambda: first_number - second_number, "*": lambda: first_number * second_number, "/": lambda: first_number / second_number, } first_number, operator, second_number = self.equation self.result = ops[operator]() return self.result if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) window = MainWindow() window.show() app.exec_()
Saatko kuvaa ohjelmasta näytille mihinkään? Kiinnostaa minkänäköisen ohjelman tuolla esimerkillä toteuttaa! Miksi muuten valisit QT:n? Kaupallinen lisenssi indie-kehittäjälle on mielestäni kuitenkin kohtuullisen kallis...
jalski kirjoitti:
Miksi muuten valisit QT:n? Kaupallinen lisenssi indie-kehittäjälle on mielestäni kuitenkin kohtuullisen kallis...
Harrastelija voi tehdä Qt:llä niin paljon kuin huvittaa ihan ilmaiseksi. Jos taas käytät Qt:tä liiketoimintaan niin ei tuo hinnoittelu mitenkään paha silloinkaan ole.
Lisäksi tarjolla on ilmainen avoimen lähdekoodin lisenssi, mikäli tuotteesi lisensointi sitä tukee.
vesikuusi kirjoitti:
Harrastelija voi tehdä Qt:llä niin paljon kuin huvittaa ihan ilmaiseksi. Jos taas käytät Qt:tä liiketoimintaan niin ei tuo hinnoittelu mitenkään paha silloinkaan ole.
Lisäksi tarjolla on ilmainen avoimen lähdekoodin lisenssi, mikäli tuotteesi lisensointi sitä tukee.
Jos taas haluaa Pythonilla käyttäen PyQT:ta kaupallista softaa kirjoitella, niin tuohon voi heti lisätä 550 USD noin ensi alkuun... Lisäksi pitää huomioida, että tuo pienelle yritykselle tarjottava lisensi ei tarjoa kunnollista teknistä tukea asennusta lukuunottamatta.
Niin, mikäli lisenssisi ei ole GPL-yhteensopiva.
Ei siinä, erikoinen kysymys vain jonkun taskulaskinkoodiesimerkin yhteydessä.
jalski kirjoitti:
Saatko kuvaa ohjelmasta näytille mihinkään? Kiinnostaa minkänäköisen ohjelman tuolla esimerkillä toteuttaa!
Luulisin jokaisen karvalakkilaskimen näyttävän 1:1 samalta...
(Kuva poistuu automaattisesti kuukauden kuluttua.)
jalski kirjoitti:
Miksi muuten valisit QT:n? Kaupallinen lisenssi indie-kehittäjälle on mielestäni kuitenkin kohtuullisen kallis...
En ole miettinyt tällaisia asioita. Mutta Qt:llahan voi kehittää kaupallistakin softaa ilmaiseksi; GPL ei sitä kiellä laisinkaan.
Aikoinaan kiinnostuin Qt:sta koska se tarjosi erinomaisen crossplatform-tuen useille eri käyttiksille. Eräs vuosia sitten tekemäni ohjelmisto on portattu jonkun minulle tuntemattoman harrastajan toimesta jopa OS/2:lle...
The Alchemist kirjoitti:
Aikoinaan kiinnostuin Qt:sta koska se tarjosi erinomaisen crossplatform-tuen useille eri käyttiksille. Eräs vuosia sitten tekemäni ohjelmisto on portattu jonkun minulle tuntemattoman harrastajan toimesta jopa OS/2:lle...
Käytin OS/2:sta useita vuosia, vieläkin pidän työpöydästä enemmän kuin Windowsin. Harmi, että IBM ei vienyt kehitystyötä eteenpäin. PowerPC alustalle oli kuitenkin jo valmiina toimiva prototyyppi käytännössä AIX:n päällä toimivasta Workplace shell työpöydästä.
8th sisältää nykyään integroidun Nuklear GUI tuen. Omaan käyttööni tuo soveltuu erittäin hyvin ja toimii kaikilla alustoilla, hieman epästandardin näköisen käyttöliittymän kustannuksella tosin. Tietysti jos tarvitaan jotain hienoja widgettejä ja erikoistoiminnallisuutta, niin ne joutuu toteuttamaan itse. QT:n etuna varmasti on, että lähestulkoon kaikenmoiset widgetit ja erikoistoiminnalisuus löytyvät valmiina ja kehittäjä voi alkaa suoraan keskittymään itse ohjelman toteutukseen.
Alla vertailun vuoksi koodi, mikä toteuttaa kyseisen laskimen:
\ Simple calculator needs nk/gui libbin font/Roboto-Regular.ttf : new-win { name: "main", wide: 0.25, high: 0.25, fonts: { f1: { font: @font/Roboto-Regular.ttf } }, fontheight: ` nk:screen-size a:open nip 20 n:/ 14 n:max `, font: "f1", title: "Simple Calculator" } "fontheight" m:@ 1.2 n:* "rowheight" swap m:! nk:win ; : +n \ nk n -- >r "num" nk:m@ 10 n:* r> n:+ "num" swap nk:m! ; : mathop \ nk w -- nk "op" swap nk:m! "num" nk:m@ "a" swap nk:m! "num" 0 nk:m! ; : main-render { title: "calc", bg: "white", flags: [ @nk:WINDOW_NO_SCROLLBAR ] } nk:begin 0 1 nk:layout-row-dynamic "num" nk:m@ null? if drop 0 "num" over nk:m! then >s 32 nk:EDIT_SIMPLE nk:EDIT_READ_ONLY n:bor nk:PLUGIN_FILTER_FLOAT edit-string drop nk:win-high nk:widget-bounds 3 a:_@ n:- 5 n:/ 4 nk:layout-row-dynamic \ the button labels: [ "1", "2", "3", "+" , "4", "5", "6", "-" , "7", "8", "9", "*" , "C", "0", "=", "/" ] ( \ nk n s swap >r \ save the button index ( \ act based on what the index is r@ [ ( 1 +n ) , ( 2 +n ) , ( 3 +n ) , ( ' n:+ mathop ) , ( 4 +n ) , ( 5 +n ) , ( 6 +n ) , ( ' n:- mathop ) , ( 7 +n ) , ( 8 +n ) , ( 9 +n ) , ( ' n:* mathop ) , ( "a" 0 nk:m! "num" 0 nk:m! ) , ( 0 +n ) , ( "eq" true nk:m! ) , ( ' n:/ mathop ) ] case "eq" nk:m@ if "eq" false nk:m! "op" nk:m@ >r "num" nk:m@ >r "a" nk:m@ r> r> w:exec "num" swap nk:m! "a" 0 nk:m! then ) nk:button-label rdrop ) a:each drop nk:end ; : app:main new-win ' main-render -1 nk:render-loop ;
jalski kirjoitti:
QT:n etuna varmasti on, että lähestulkoon kaikenmoiset widgetit ja erikoistoiminnalisuus löytyvät valmiina ja kehittäjä voi alkaa suoraan keskittymään itse ohjelman toteutukseen.
Itse asiassa tämä ns. natiivien widgettien vähyys minua alkoi ärsyttää. Qt:n vakio työkalupakki kun ei sisällä kuin Windows 98:n tasoiset vitkuttimet, ja täysin uudenlaisten omien teko on hemmetin vaikeaa.
Nykyään kehittelen harrastuspohjalta enää Linuxille, joten yritän siirtyä GTK:hon, mutta sen kanssa taas hiertää C-lähtöinen rajapinta, joka on erittäin työläs käyttää. Lisäksi GTK:ssa on joitakin suorituskykyä rampauttavia suunnittelumokia, jotka ovat nykytiedon valossa korjaantumassa vasta GTK 5:een – ja GTK 4.0 kun julkaistiin juuri männä viikolla...