pyGTK und Glade - Anfänger baut Taschenrechner

Programmierung für GNOME und GTK+, GUI-Erstellung mit Glade.
Antworten
deTTo
User
Beiträge: 7
Registriert: Freitag 22. Juni 2007, 20:04

Nunja, so mehr oder weniger ist ein kleiner Taschenrechner mein Ziel. Ich arbeite zwar noch nicht lange mit Python (eigentlich erst seit paar Wochen), möchte (oder muss) aber unbedingt dieses Progrämmchen fertig bekommen. Mit fehlt leider das Wissen, vll auch etwas zu großes ausgesucht für den Einstieg? Nunja... :roll:

Das hier ist mein bisheriger Code, den ich mühselig iwie zusammengeschrieben habe, und für mich soweit völlig logisch erscheint, jedoch wahrscheinlich noch so einiges an Fehlern beinhaltet, wer weiß :?

Code: Alles auswählen

#!/usr/bin/python

import sys
try:
	import pygtk
	pygtk.require("2.0")
except:
	pass
try:
	import gtk
	import gtk.glade
except:
	sys.exit(1)


class rechner():
	def __init__(self):
		"""
		Startet das Hauptprogramm
		und das MainWindow
		"""
		# Fenstertitel festlegen
		self.windowname = "MainWindow"
		# Glade-XML-Datei einbinden
		self.gladefile = "taschenrechner.glade"
		self.wTree = gtk.glade.XML(gladefile, self.windowname)
		
		# Das dictionary erstellen und einbinden lassen
		dic = { "on_MainWindow_destroy" : gtk.main_quit,
		       "on_rechenmodus_changed" : self.rechenmodus_changed,
			     "on_zahl1_changed" : self.zahl1_changed,
			     "on_zahl2_changed" : self.zahl2_changed }
		self.wTree.signal_autoconnect(dic)
		
	
	def rechenmodus_changed(self, widget):
		self.widget = self.wTree.get_widget("rechenmodus")
		self.aktiv = self.widget.get_active()
		return
	
	def zahl1_changed(self, widget):
		self.widget = self.wTree.get_widget("zahl1")
		self.changed_1 = self.widget.get_text()
		return
	
	def zahl2_changed(self, widget):
		self.widget = self.wTree.get_widget("zahl2")
		self.changed_2 = self.widget.get_text()
		return
		
	def rechnung(self):
		if self.aktiv == "Addition":
			ergebnis = float(self.changed_1) + float(self.changed_2)
		elif self.aktiv == "Subtraktion":
			ergebnis = float(self.changed_1) - float(self.changed_2)
		elif self.aktiv == "Multiplikation":
			ergebnis = float(self.changed_1) * float(self.changed_2)
		elif self.aktiv == "Division":
			ergebnis = float(self.changed_1) / float(self.changed_2)
		return ergebnis
		
	def ergebnis_box(self, widget):
		self.widget = self.wTree.get_widget("ergebnis")
		self.eintrag = self.widget.set_text("ergebnis")


if __name__ == "__main__":
	main = rechner()
	gtk.main()
Ich glaub ich muss auch die glade-XML anhängen, das wäre verdammt viel denk ich :?
Ich habs einfach mal hier hochgeladen: http://www.siteupload.de/p343821-projek ... erzip.html

Wär nett wenn mir jmd dazu helfen könnte.
Mir fehlt zum einen die Möglichkeit einfach 'Enter' zu drücken in einem der beiden Fenster, damit die Rechnung und die Ergebnisausgabe startet/stattfindet. Obs letzendlich so überhaupt funktioniert weiß ich nicht. (bin mir da bei den mathem. Formeln nicht ganz sicher)
Das Programm startet auf jedenfall schonmal! Zumindest ein kleines Erfolgserlebnis. :)

mfg,
deTTo
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

Hm ich hab selbst mal einen solchen Rechner mit Pygtk geschrieben, vielleicht hilft der der Source ja etwas.

Code: Alles auswählen

#!/usr/bin/env python

import pygtk
pygtk.require('2.0')
import gtk
import math

class Calculator:

	def calc(self, widget, data=None):
		self.entry.set_text(str(eval(self.entry.get_text())))
	
	def add(self, widget, data=None):
		self.entry.set_text(self.entry.get_text() + widget.get_label())
	
	def clear(self, widget, data=None):
		self.entry.set_text('')
	
	def info(self, widget, data=None):
		dialog = gtk.AboutDialog()
		dialog.set_name('veecalc')
		dialog.set_version('0.1.1')
		dialog.set_copyright('Copyright 2006 Jonas Wagner')
		dialog.set_comments('Just a small calculator written in python using pygtk')
		dialog.set_authors(['Jonas Wagner'])
		dialog.show()
		self.
	
	def destroy(self, widget, data=None):
		gtk.main_quit()
	
	def __init__(self):
		self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
		self.window.set_border_width(5)
		self.window.set_title('veeCalc')
		self.window.connect("destroy", self.destroy)
		
		self.vbox = gtk.VBox(False, 0)
		
		self.entry = gtk.Entry()
		self.vbox.pack_start(self.entry)
		
		self.hbox = gtk.HBox(False, 0)
		self.vbox.pack_start(self.hbox)	
		
		labels = ['(', ')', 'C', 'I', 7, 8, 9, '/', 4, 5, 6, '*', 1, 2, 3, '-', 0, '.', '=', '+']
		for i in range(int(math.ceil(len(labels)/4.0))):
			hbox = gtk.HBox(False, 0)
			self.vbox.pack_start(hbox, True, True, 2)
			for c in range(4):
				label = str(labels[i*4+c])
				button = gtk.Button(label)
				if label == 'C':
					button.connect("clicked", self.clear, None)
				elif label == 'I':
					img = gtk.image_new_from_stock(gtk.STOCK_ABOUT,  gtk.ICON_SIZE_BUTTON)
					button.set_property("image", img)
					button.set_label("")
					button.connect("clicked", self.info, None)
				elif label == '=':
					button.connect("clicked", self.calc, None)
				else:
					button.connect("clicked", self.add, None)
				hbox.pack_start(button, True, True, 2)
				
		self.window.add(self.vbox)
		self.window.show_all()
	
	def main(self):
		gtk.main()

if __name__ == "__main__":
	Calculator().main()
BlackJack

@all: Vorsicht mit Komplettlösungen, das könnte eine Hausaufgabe sein. ;-)

@deTTo: Der Quelltext ist mit Tabs eingerückt, das kann bei einer Sprache bei der Einrückung wichtig ist, manchmal Probleme geben, wenn man Tabs und Leerzeichen mischt, oder wenn man Quelltext von anderen in das eigene Programm kopiert. Bei Python ist einrücken mit vier Leerzeichen pro Ebene üblich.

Bei ``except`` sollte man immer eine konkrete Ausnahme angeben, sonst werden *alle* Ausnahmen dort behandelt. So macht man sich die Fehlersuch unnötig schwer. Wenn man eine Ausnahmebehandlung macht, dann sollte man in aller Regel auch wirklich etwas tun und die Ausnahme nicht einfach ignorieren oder das Programm einfach so abbrechen. Wenn `gtk` oder `gtk.gade` nicht importiert werden kann, dann beendet sich Dein Taschenrechner einfach "wortlos" und man weiss überhaupt nicht warum. Da ist es besser die Ausnahmebehandlung einfach weg zu lassen und einen informativeren Traceback auf der Konsole zu bekommen.

Klassennamen werden per Konvention in CamelCase geschrieben. Die Klasse würde dann also `Rechner` heissen. Leere Klammern bei den Basisklassen funktioniert übrigens erst ab Python 2.5 ─ wenn das Programm auch mit älteren Versionen funktionieren soll, müsste man explizit `object` als Basisklasse angeben.

Es wird mehr an das Objekt gebunden als notwendig ist. Der Fenstername und der Name der Glade-Datei zum Beispiel müssen nicht an das Objekt gebunden werden. Und bei den Eingabefeldern sollte das Widget-Objekt doch als Argument übergeben werden, muss also nicht mit `get_widget()` geholt werden. Auch muss/sollte `widget` in diesen Methoden nicht an das Objekt gebunden werden. Der Name wird nur lokal in der Methode benötigt.

Ein ``return`` ohne Rückgabewert am Ende einer Funktion oder Methode ist überflüssig.

In der `__init__()` sollte man alle Attribute setzen, die vom Objekt verwendet werden. Wenn man das, wie mit `changed_1`, `changed_2` und `aktiv` erst in Methoden macht, dann hängt das Verhalten des Objekts extrem davon ab, in welcher Reihenfolge die Methoden aufgerufen werden. Mal angenommen es gäbe schon einen "Ausrechnen"-Button, dann müsste voher *unbedingt* eine Rechenart ausgewählt und die beiden Zahlen eingegeben worden sein, sonst bekäme man einen `AttributeError`. Es ist also besser diese Attribute vorzubelegen. Zum Beispiel mit "Addieren" und 0 für die beiden Zahlen.

Was in der Tat noch fehlt ist ein "Ausrechnen"-Button. Der müsste `rechnung()` aufrufen und das Ergebnis in das entsprechende Widget schreiben.

Bei der `rechnung()` könnte man statt der vielen ``if``/``elif`` auch ein Dictionary wie bei den Signalen in der `__init__` verwenden. Die Operatoren gibt's im `operator`-Modul als Funktionen. Ausserdem sollte man den Fall berücksichtigen, dass eine Rechenart ausgewählt wurde, die es nicht gibt und dass in den Eingabefeldern keine gültigen Zahlen stehen und `float()` deswegen nicht klappt.
deTTo
User
Beiträge: 7
Registriert: Freitag 22. Juni 2007, 20:04

Hey dankeschön erstmal für eure Antworten!

@veers: Werd's mir anschaun, bestimmt noch was dabei zum Lernen. Aber ich denke ich werde erstmal versuchen das so weiter durchzuziehn mit dem Rechner() :)

@BlackJack: Hausaufgabe ist es nicht ganz, aber deine Vermutung ist schon ganz richtig. Wir sollen eigentlich ein Projekt in Mono(develop) erstellen. Einfach irgendetwas simples was funktioniert. Da ich mich absolut nicht mit Mono anfreuden kann und werde, und 2 Wochen zuvor mit Python anfing, dacht ich mir "hey überrasch ihn doch und präsentier was in pyGTK". Den Taschenrechner hab ich schon komplett fertig, nur eben nicht mit GTK, sondern als Bashprogramm.
Also angefangen mir den GTK-kram reinzuziehn (auch wenn ich dort noch gar nicht anfangen sollte, grade mal Krams mit Klassen gelesen und kaum angewendet). Also alles ziemliches Neuland. Das Ding ist bloß, ich muss ein Projekt bis zum Donnerstag vorliegen haben. :? Nun gut, nicht euer oder dein Bier :) Finds auch gut dass du mir Hilfestellungen gibst, und nicht "da bitte, fertig!" 8)
Nun gut, ich werd mich jetzt erst noch mal dem Code und deinen Anweisungen widmen:
Der Fenstername und der Name der Glade-Datei zum Beispiel müssen nicht an das Objekt gebunden werden.
Sprich:

Code: Alles auswählen

		# Fenstertitel festlegen
		windowname = "MainWindow"
		# Glade-XML-Datei einbinden
		gladefile = "taschenrechner.glade"
		self.wTree = gtk.glade.XML(gladefile, windowname)
?
`widget` in diesen Methoden nicht an das Objekt gebunden werden. Der Name wird nur lokal in der Methode benötigt.......Ein ``return`` ohne Rückgabewert am Ende einer Funktion oder Methode ist überflüssig.
Also:

Code: Alles auswählen

	def rechenmodus_changed(self, widget):
		widget = self.wTree.get_widget("rechenmodus")
		aktiv = widget.get_active()
	
	def zahl1_changed(self, widget):
		widget = self.wTree.get_widget("zahl1")
		changed_1 = widget.get_text()
	
	def zahl2_changed(self, widget):
		widget = self.wTree.get_widget("zahl2")
		changed_2 = widget.get_text()
?

Hab nochmal einen Button geadded:

Code: Alles auswählen

			   "on_button1_clicked" : self.ergebnis_box }
Alles funzt soweit auch (ich meine das Programm startet noch :) ), allerdings weiß ich nicht, wie ich die richtige Ausgabe ins Ergebnisfeld bekomme. Momentan printet er mir dort noch stumpf "Ergebnis" hinein :D


Zu guter letzt (und besseren Überschaubarkeit) hier einfach der geänderte Code wie er jetzt ausschaut:

Code: Alles auswählen

#!/usr/bin/env python

import sys
import pygtk
pygtk.require("2.0")
import gtk
import gtk.glade

class Rechner():
	def __init__(self):
		"""
		Startet das Hauptprogramm
		und das MainWindow
		"""
		# Fenstertitel festlegen
		windowname = "MainWindow"
		# Glade-XML-Datei einbinden
		gladefile = "taschenrechner.glade"
		self.wTree = gtk.glade.XML(gladefile, windowname)
		
		# Das dictionary erstellen und einbinden lassen
		dic = { "on_MainWindow_destroy" : gtk.main_quit,
		       "on_rechenmodus_changed" : self.rechenmodus_changed,
			     "on_zahl1_changed" : self.zahl1_changed,
			     "on_zahl2_changed" : self.zahl2_changed,
			   "on_button1_clicked" : self.ergebnis_box }
		self.wTree.signal_autoconnect(dic)
		
	
	def rechenmodus_changed(self, widget):
		widget = self.wTree.get_widget("rechenmodus")
		aktiv = widget.get_active()
	
	def zahl1_changed(self, widget):
		widget = self.wTree.get_widget("zahl1")
		changed_1 = widget.get_text()
	
	def zahl2_changed(self, widget):
		widget = self.wTree.get_widget("zahl2")
		changed_2 = widget.get_text()
		
	def rechnung(self, widget):
		if self.aktiv == "Addition":
			ergebnis = float(self.changed_1) + float(self.changed_2)
		elif self.aktiv == "Subtraktion":
			ergebnis = float(self.changed_1) - float(self.changed_2)
		elif self.aktiv == "Multiplikation":
			ergebnis = float(self.changed_1) * float(self.changed_2)
		elif self.aktiv == "Division":
			ergebnis = float(self.changed_1) / float(self.changed_2)
		return ergebnis
		
	def ergebnis_box(self, widget):
		widget = self.wTree.get_widget("ergebnis")
		eintrag = widget.set_text("ergebnis")


if __name__ == "__main__":
	main = Rechner()
	gtk.main()
(neu: http://www.siteupload.de/p344135-projek ... erzip.html)
BlackJack

deTTo hat geschrieben:
Der Fenstername und der Name der Glade-Datei zum Beispiel müssen nicht an das Objekt gebunden werden.
Sprich:

Code: Alles auswählen

		# Fenstertitel festlegen
		windowname = "MainWindow"
		# Glade-XML-Datei einbinden
		gladefile = "taschenrechner.glade"
		self.wTree = gtk.glade.XML(gladefile, windowname)
Ja genau.
`widget` in diesen Methoden nicht an das Objekt gebunden werden. Der Name wird nur lokal in der Methode benötigt.......Ein ``return`` ohne Rückgabewert am Ende einer Funktion oder Methode ist überflüssig.
Also:

Code: Alles auswählen

	def rechenmodus_changed(self, widget):
		widget = self.wTree.get_widget("rechenmodus")
		aktiv = widget.get_active()
	
	def zahl1_changed(self, widget):
		widget = self.wTree.get_widget("zahl1")
		changed_1 = widget.get_text()
Nein, `aktiv` und `changed_*` werden ja in anderen Methoden gebraucht. Und `widget` wird wie gesagt schon als Argument übergeben! Andererseits ist die Frage ob man an diese Widgets überhaupt Handler binden muss. Letztendlich könnte man auch einen an den "Ausrechnen"-Button binden, der dann alle nötigen Werte aus der GUI holt, die Berechnung durchführt und dann das Ergebnis in das entsprechende Widget schreibt.
deTTo
User
Beiträge: 7
Registriert: Freitag 22. Juni 2007, 20:04

Hey BlackJack, danke für die Infos! Ich denke bin nun etwas klarer über die self-Geschichte.
Hab das ganze Teil nun mal etwas umgekrempelt und eine einzige Funktion (def berechnung) erstellt, die beim Knopfdruck ausgeführt wird.

Code: Alles auswählen

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

# Einbinden aller benötigten Module
import pygtk, gtk, gtk.glade

class Rechner():
    def __init__(self):
        """
        Startet das Hauptprogramm
        und erstellt das MainWindow.
        """
        # Glade-XML-Datei und widget-Tree einbinden
        gladefile = "taschenrechner.glade"
        self.wTree = gtk.glade.XML(gladefile, "MainWindow")
        
        # Das dictionary erstellen und einbinden lassen
        dic = { "on_MainWindow_destroy_event" : gtk.main_quit,
                         "on_button1_clicked" : self.berechnung }
        self.wTree.signal_autoconnect(dic)
        
        
    def berechnung(self, widget):
        """
        Holt Zahl1 und Zahl2, sowie den Modus aus
        der Glade-XML und führt Berechnung durch.
        """
        # Widgeteinträge werden an Variablen gebunden
        modus = self.wTree.get_widget("rechenmodus")
        zahl1 = self.wTree.get_widget("zahl1")
        zahl2 = self.wTree.get_widget("zahl2")
        ergebnis_box = self.wTree.get_widget("ergebnis")
        # Welcher Rechenmodus muss durchgeführt werden? + Rückgabe des Ergebnis
        if modus.get_active() == "Addition":
            print "Addition"
            ergebnis = float(zahl1.get_text()) + float(zahl2.get_text())
        elif modus.get_active() == '1':
            ergebnis = float(zahl1.get_text()) - float(zahl2.get_text())
        elif modus.get_active() == '2':
            ergebnis = float(zahl1.get_text()) * float(zahl2.get_text())
        elif modus.get_active() == '3':
            ergebnis = float(zahl1.get_text()) / float(zahl2.get_text())
        else:
            print "if-Schleife scheint nicht zu funzen :("
        # Eintrag/Anzeige des Ergebnis
        ergebnis_box.set_text(ergebnis)
        
        
if __name__ == "__main__":
	main = Rechner()
	gtk.main()
Programm startet, er geht auch in die Funktion "berechnung". Allerdings stimmt nach wie vor etwas mit der if-Abfrage nicht. :( Hab das mal mit simplen prints überprüft, wie oben zu sehen.
Könntest du oder jmd andres hier mir denn erklären wie ich diese if-Abfrage gestalten sollte? Dachte eigentlich "SO" sollte es jetzt gehn, nunja... :?
BlackJack

Gib doch mal den Typ und den Rückgabewert von `get_active()` vor der Abfrage aus.

Code: Alles auswählen

    aktiv = modus.get_active()
    print type(aktiv), repr(aktiv)
Ich vermute mal (bin (auch) zu faul in die Doku zu schauen), dass es Zahlen sind.
deTTo
User
Beiträge: 7
Registriert: Freitag 22. Juni 2007, 20:04

OK, hinbekommen! :)
Hier der fertige Code:

Code: Alles auswählen

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

# Importieren aller benötigten Module
import pygtk, gtk, gtk.glade

class Rechner():
    def __init__(self):
        """
        Startet das Hauptprogramm
        und erstellt das MainWindow.
        """
        # Glade-XML-Datei einbinden
        self.wTree = gtk.glade.XML("taschenrechner.glade", "MainWindow")
        
        # Das dictionary erstellen und einbinden lassen
        dic = { "on_button1_clicked" : self.berechnung }
        self.wTree.signal_autoconnect(dic)
        
        
    def berechnung(self, widget):
        """
        Holt Zahl1 und Zahl2, sowie den Modus aus
        der Glade-XML und führt Berechnung durch.
        """
        # Aktuelle Widgeteinträge werden geholt und an Variablen gebunden
        modus = self.wTree.get_widget("rechenmodus").get_active()
        zahl1 = self.wTree.get_widget("zahl1").get_text()
        zahl2 = self.wTree.get_widget("zahl2").get_text()
        ergebnis_box = self.wTree.get_widget("ergebnis")
        # Welcher Rechenmodus muss durchgeführt werden? + Rückgabe des Ergebnis
        if modus == int("0"):
            ergebnis = float(zahl1) + float(zahl2)
        elif modus == int("1"):
            ergebnis = float(zahl1) - float(zahl2)
        elif modus == int("2"):
            ergebnis = float(zahl1) * float(zahl2)
        elif modus == int("3"):
            ergebnis = float(zahl1) / float(zahl2)
        elif modus == int("4"):
            ergebnis = float(zahl1) ** float(zahl2)
        # Eintrag/Anzeige des Ergebnis
        ergebnis_box.set_text(str(ergebnis))
        
        
if __name__ == "__main__":
	main = Rechner()
	gtk.main()
Hier noch einmal der Code + die Glade-XML: http://www.siteupload.de/p345237-projek ... erzip.html
Ist bestimmt für einige andere Interessenten von.. Interesse :roll: :wink:
BlackJack

Statt ``int('0')`` kannst Du auch gleich ``0`` schreiben.
Antworten