Hallo
ich bin gerade dabei, meinen Wortschatz um Python zu werweitern und bin deswegen dabei, mir ein kleines MineSweeper zu bauen.
Jetzt stecke ich gerade bei der GUI. Hierfür habe ich mir eine Klasse geschrieben, die meinem Fenster die passende Anzahl an Buttons hinzufügt. Zur Positionierung benutze ich den grid-Befehl.
Jetzt will ich nachher aber auch alle Button ansprechen können, so dass z.B. alle Buttons anzeigen, ob sie Minen sind, oder nicht.
Bei Delphi z.B. kann ich da einfach einen Namen festlegen und den Button dann über diesen Namen identifizieren.
Nur habe ich keine passende Eigenschaft gefunden. Alternativ war mein Ansatz, eine 2D-Liste an Button anzulegen, wobei ich aber davon ausgehe, dass es da eine schönere Lösung gibt.
Nikolas
MineSweeper: wie spreche ich Buttons im Grid an?
-
- User
- Beiträge: 419
- Registriert: Sonntag 3. September 2006, 15:11
- Wohnort: in den weiten von NRW
- Kontaktdaten:
Hi!
Zeig mal deinen Code!
Prinzipiell solltest du schon in einer schleife Buttons erzeugen und diese nicht an Namen binden sondern in eine Liste packen. Wie du die Buttons ordnest, ob über ne 2d-Liste oder eine eigene erweiterte Buttonklasse oder sonst wie hängt, wie gesagt, vom rest ab.
Und warum glaubst du, das das in Python nicht geht? Wie hast du das denn bis jetzt immer gemacht? Was für eine Eigenschaft suchst du?Nikolas hat geschrieben: Bei Delphi z.B. kann ich da einfach einen Namen festlegen und den Button dann über diesen Namen identifizieren. Nur habe ich keine passende Eigenschaft gefunden.
Zeig mal deinen Code!
Kommt drauf an, was du meinst und wie der rest deines Programms aussieht.Nikolas hat geschrieben: Alternativ war mein Ansatz, eine 2D-Liste an Button anzulegen, wobei ich aber davon ausgehe, dass es da eine schönere Lösung gibt.
Prinzipiell solltest du schon in einer schleife Buttons erzeugen und diese nicht an Namen binden sondern in eine Liste packen. Wie du die Buttons ordnest, ob über ne 2d-Liste oder eine eigene erweiterte Buttonklasse oder sonst wie hängt, wie gesagt, vom rest ab.
-
- User
- Beiträge: 102
- Registriert: Dienstag 25. Dezember 2007, 22:53
- Wohnort: Freiburg im Breisgau
Ich habe ja nicht gesagt, dass ich glaube dass es nicht geht, ich weiss nur nicht wie
bei (1) wird der gleiche Name in jedem Durchlauf benutzt, was mir etwas ungesund vorkommt.
Wenn ich die Button dann in eine Liste schreibe, die dann eine Eigenschaft der Klasse ist, kann ich die dann einfach über a.ButtonListe ansprechen?
Code: Alles auswählen
class addButtons:
def __init__(self,master,x,y):
self.newGame = Button(master,text="Neues Spiel")
self.newGame.pack(side = TOP)
self.entry_x = Entry(master)
self.entry_x.pack(side = ???
frame = Frame(master)
frame.pack() # TODO: vielleicht anpassen
for j in range(y):
for i in range(x):
self.button = Button(frame,text=i) # (1)
self.button.grid(row=j, column = i)
# self.button.command = printName( self.button["name"] )
root = Tk()
a = addButtons(root,2,5)
root.mainloop()
bei (1) wird der gleiche Name in jedem Durchlauf benutzt, was mir etwas ungesund vorkommt.
Wenn ich die Button dann in eine Liste schreibe, die dann eine Eigenschaft der Klasse ist, kann ich die dann einfach über a.ButtonListe ansprechen?
Erwarte das Beste und sei auf das Schlimmste vorbereitet.
Hallo,
ich denke, du bist auf dem richtigen weg.
Gruss
pyStyler
ich denke, du bist auf dem richtigen weg.
Code: Alles auswählen
def return_XY_button(xb, yb):
print xb, yb
class addButtons:
def __init__(self, master, x, y):
self.newGame = Button(master, text="Neues Spiel")
self.newGame.pack(side = TOP)
self.entry_x = Entry(master)
self.entry_x.pack(fill=X, padx=2, pady=2)
frame = Frame(master)
# packen mit ohne side angaben,
#wird immer auf der obersten ebene des frames gepackt!
frame.pack() # TODO: vielleicht anpassen
for j in range(1, y):
for i in range(1,x):
self.button=Button(frame,
width=2, height=2,
text=i,
command=lambda x=i, y=j: return_XY_button(y, x) )# (1)
self.button.grid(row=j, column = i)
# self.button.command = printName( self.button["name"] )
root = Tk()
a = addButtons(root, 10+1, 10+1)
root.mainloop()
pyStyler
Zuletzt geändert von pyStyler am Freitag 28. Dezember 2007, 19:40, insgesamt 1-mal geändert.
-
- User
- Beiträge: 419
- Registriert: Sonntag 3. September 2006, 15:11
- Wohnort: in den weiten von NRW
- Kontaktdaten:
1) Du solltest die Klasse umbenennen. "addButtons" klingt nach einer Methode, entspricht beiden Fällen aber nicht PEP. Hast du OOP verstanden bzw. weißt du, was du da tust? ZZ macht eine Klasse bei dir nämlich auch keinen Sinn.
2) Was soll das in l. 9 sein? Falls du es nicht weißt, side muss right, left, top oder button sein, das bekommst du sogar gesagt, wenn du da was falsches hinschreibst.
3) Such mal im Forum nach dem Grund, *-Imports zu vermeiden. (Auch wenn er fehlt, nutzt du ihn ja.)
4)hier mal eine minimalistische und daher eher schlechte Möglichkeit:
Jetzt bist du hoffentlich in der Lage, es besser zu machen!
2) Was soll das in l. 9 sein? Falls du es nicht weißt, side muss right, left, top oder button sein, das bekommst du sogar gesagt, wenn du da was falsches hinschreibst.
3) Such mal im Forum nach dem Grund, *-Imports zu vermeiden. (Auch wenn er fehlt, nutzt du ihn ja.)
4)hier mal eine minimalistische und daher eher schlechte Möglichkeit:
Code: Alles auswählen
import Tkinter as tk
import random
class BeispielFeld(tk.Frame):
def __init__(self):
tk.Frame.__init__(self)
self.feld = [[random.choice([0,1]) for x in xrange(10)] for y in xrange(10)]
print self.feld
for y in xrange(10):
for x in xrange(10):
b=tk.Button(text="?", width=4, height=2)
b["command"] = lambda x=x, y=y, b=b: b.config(text=self.test(x, y))
b.grid(row=y, column=x)
def test(self, x, y):
if self.feld[y][x] == 1:
return "X"
else:
return " "
if __name__ == "__main__":
BeispielFeld()
tk.mainloop()
-
- User
- Beiträge: 102
- Registriert: Dienstag 25. Dezember 2007, 22:53
- Wohnort: Freiburg im Breisgau
Danke für eure Hinweise.
2) Ich wollte den Button und zwei Entries ganz nach oben legen und habe nach einer Möglichkeit wie z.B. side = (TOP,LEFT) gesucht, um ein Objekt möglichs weit im Nordwesten zu platzieren. (Wobei ich dafür wohl einfach ein Grid nehmen kann...)
3) Werde ich mal machen. Ich habe die grundstruktur (imports und Klassenschreibweise) aus einem Tutorial übernommen, mir kam es auch etwas komisch vor, aber gleich von einer vorgegebenen Methode abzuweichen wollte ich nicht beim ersten Versuch.
4) Mit einem lambda habe ich in Python noch nicht gearbeitet. Entspricht es hier einem lambda-Kalkül wie in Scheme?
Ich wollte Logik und Anzeige möglichst klar trennen, so dass ich z.B. später auch mal eine Canvas zur Anzeige nutzen kann.
Deswegen suche ich nach einer Möglichkeit dieser Beispiel-feld Klasse von aussen einen Befehl zu geben, der dann z.B. alle Felder deaktiviert, weil man gerade das Spiel verloren hat. Und dafür bräuchte ich eben eine Schleife über die angelegten Buttons.
Was ich also versuchen werde, ist eine 2dimensionale Liste an Buttons anzulegen, die dann eine Eigenschaft (oder wie heisst das in Python?) der Klasse ist.
Ist das erfolgversprechend oder soll ich meine Zeit eher nicht damit verschwenden?
Bei deinem Code verstehe ich noch ein paar Sachen nicht: Die Schreibweise vom ersten import ist mir neu, und so ganz verstehe ich die untersten Zeilen auch nicht. Könntest du vielleicht noch mal kurz beschreiben, was das tk genau ist?
2) Ich wollte den Button und zwei Entries ganz nach oben legen und habe nach einer Möglichkeit wie z.B. side = (TOP,LEFT) gesucht, um ein Objekt möglichs weit im Nordwesten zu platzieren. (Wobei ich dafür wohl einfach ein Grid nehmen kann...)
3) Werde ich mal machen. Ich habe die grundstruktur (imports und Klassenschreibweise) aus einem Tutorial übernommen, mir kam es auch etwas komisch vor, aber gleich von einer vorgegebenen Methode abzuweichen wollte ich nicht beim ersten Versuch.
4) Mit einem lambda habe ich in Python noch nicht gearbeitet. Entspricht es hier einem lambda-Kalkül wie in Scheme?
Ich wollte Logik und Anzeige möglichst klar trennen, so dass ich z.B. später auch mal eine Canvas zur Anzeige nutzen kann.
Deswegen suche ich nach einer Möglichkeit dieser Beispiel-feld Klasse von aussen einen Befehl zu geben, der dann z.B. alle Felder deaktiviert, weil man gerade das Spiel verloren hat. Und dafür bräuchte ich eben eine Schleife über die angelegten Buttons.
Was ich also versuchen werde, ist eine 2dimensionale Liste an Buttons anzulegen, die dann eine Eigenschaft (oder wie heisst das in Python?) der Klasse ist.
Ist das erfolgversprechend oder soll ich meine Zeit eher nicht damit verschwenden?
Bei deinem Code verstehe ich noch ein paar Sachen nicht: Die Schreibweise vom ersten import ist mir neu, und so ganz verstehe ich die untersten Zeilen auch nicht. Könntest du vielleicht noch mal kurz beschreiben, was das tk genau ist?
Erwarte das Beste und sei auf das Schlimmste vorbereitet.
-
- User
- Beiträge: 419
- Registriert: Sonntag 3. September 2006, 15:11
- Wohnort: in den weiten von NRW
- Kontaktdaten:
Also wenn du "from Tkinter import *" schreibst, holst du dir alle Sachen des Tkintermoduls in den eigenen Namensraum. Dabei kannst du Sachen überschreiben, es gibt aber noch andere Gründe. Besser ist, das Modul mit "import Tkinter" zu importieren. Dann musst du aber jedesmal ein "Tkinter." vor alle Sachen schreiben wzB "Tkinter.Button(...)". Das ist recht umständlich, daher steht in Tutorial oft der *-Import. Durch das "... as tk" kannst du das Modul nun tk nennen, sodass du nurnoch "tk.Button" schreiben musst. Du kannst stattdessen auch "import Tkinter as meinGanz_Personal_nameDenSonstKeiner__BenutzT__HAHA" schreiben, doch das würde den Sinn verfehlen.
Die untersten Zeilen sind dazu da um sicherzustellen, das der Code im if-Block nur ausgeführt wird, wenn das modul nicht importiert wurde. So kannst du es so nutzen und zum testen ausführen oder importieren, ohne dass dein Test ausgeführt wid.
Mit "lambda" kannst du annonyme Funktionen schreiben. Du musst dem Button eine Funktion/Methode übergeben, die beim Klick ausgeführt wird. Wenn du aber der Funktion noch Argumente mitgeben willst, musst du das in ein Lambdakonstrukt packen.
Daten und Darstellung zu Trennen ist eine sehr gute Überlegung und sollte normal auch gemacht werden. Bei meinem Beispiel wollte ich einfach eine Minimallösung zeigen. Da noch zu trennen wäre dann doch zu viel gewesen.
Deine sonstigen Überlegungen sind auch gut. Mache am Besten eine Data-Klasse mit einer 2d-liste (in etwa so wie mein BeispielFeld.feld) und einige nützliche Methoden wie befüllen, erneuern, Stelle x/y prüfen, ...
Dann Schreibst du die Darstellung in eine weitere Klasse die entweder von der ersten erbt oder eine Instanz dieser erzeugt. Da dann eine ähnliche 2d-liste, nur mit Buttons und der Rest ist dann auch schnell getippt.
Die untersten Zeilen sind dazu da um sicherzustellen, das der Code im if-Block nur ausgeführt wird, wenn das modul nicht importiert wurde. So kannst du es so nutzen und zum testen ausführen oder importieren, ohne dass dein Test ausgeführt wid.
Mit "lambda" kannst du annonyme Funktionen schreiben. Du musst dem Button eine Funktion/Methode übergeben, die beim Klick ausgeführt wird. Wenn du aber der Funktion noch Argumente mitgeben willst, musst du das in ein Lambdakonstrukt packen.
Daten und Darstellung zu Trennen ist eine sehr gute Überlegung und sollte normal auch gemacht werden. Bei meinem Beispiel wollte ich einfach eine Minimallösung zeigen. Da noch zu trennen wäre dann doch zu viel gewesen.
Deine sonstigen Überlegungen sind auch gut. Mache am Besten eine Data-Klasse mit einer 2d-liste (in etwa so wie mein BeispielFeld.feld) und einige nützliche Methoden wie befüllen, erneuern, Stelle x/y prüfen, ...
Dann Schreibst du die Darstellung in eine weitere Klasse die entweder von der ersten erbt oder eine Instanz dieser erzeugt. Da dann eine ähnliche 2d-liste, nur mit Buttons und der Rest ist dann auch schnell getippt.
-
- User
- Beiträge: 102
- Registriert: Dienstag 25. Dezember 2007, 22:53
- Wohnort: Freiburg im Breisgau
Danke. Das mit dem Import wird schon deutlicher
nur das
verschließt sich mir noch. Was ist dieses __name__? Und dann das tk.mainloop(). Das tk steht doch jetzt für das gesamte Tkinter-Modul, ist also eigentlich nur ein Bezeichner. Warum hat das Teil dann eine mainloop? Diese Loop ist doch so etwas wie ein Dämon, der auf Eingaben vom Benutzer reagiert, oder?
Ich habe mir jetzt eine ähnliche Klasse geschrieben:
Dieses Modul werde ich dann in mein eigentliches Programm einbinden und über die app.setDimensions(10,20) dann steuern.
Ist das jetzt halbwegs sauber?
nur das
Code: Alles auswählen
if __name__ == "__main__":
BeispielFeld()
tk.mainloop()
Ich habe mir jetzt eine ähnliche Klasse geschrieben:
Code: Alles auswählen
import Tkinter as tk
class MineApp(tk.Frame):
def __init__(self):
self.x = -1
self.y = -1
tk.Frame.__init__(self)
self.grid()
def setDimensions(self,x,y):
self.x = x
self.y = y
self.createWidgets()
def returnID(self,x,y):
print [x,y]
def createWidgets(self):
# print self.x
# self.B = tk.Button(self)#command = lambda x=x, y=y : self.returnID(x,x))
if self.y< 1 or self.x<1:
print "createWidgets: x oder y kleiner 1!"
return 0
L = []
for i in range( self.y):
b = []
for j in range( self.x ):
but = tk.Button(self,
command = lambda x=j,y=i :
self.returnID(x,y))
but.grid(row=i, column = j)
b.append( but )
L.append(b)
app = MineApp()
app.setDimensions(3,5)
app.mainloop()
Ist das jetzt halbwegs sauber?
Erwarte das Beste und sei auf das Schlimmste vorbereitet.
`__name__` ist an in jedem Modul an den Namen des Moduls gebunden, ausser wenn man das Modul nicht ``import``\iert sondern als Programm ausführt, dann ist der Name an die Zeichenkette '__main__' gebunden.
Richtig, `tk` ist an das `Tkinter`-Modul gebunden. Und das enthält eine Funktion mit dem Namen `mainloop`. Das ist *die* Hauptschleife. Die Funktion ist auch über jedes Widget über den gleichen Namen erreichbar.
Das Programm hat kein Hauptfenster. Es muss ein Fenster geben, dass von `Tkinter.Tk()` erzeugt wird.
Üblicherweise sollte die `__init__()`-Methode ein Objekt in einen benutzbaren Zustand versetzen. Wenn man dort doch einmal Attribute mit "Nichts" vorbelegen möchte, sollte man nicht unbedingt Objekte vom Typ benutzen, der später verwendet wird, sondern `None`. Sonst kann es bei Zahlen zum Beispiel passieren, dass man auf einem nicht vollständig "gebrauchsfertigen" Objekt Methoden aufruft, die dann mit "Sonderwerten" wie 0 oder -1 rechnen, ohne dass man den Fehler sofort bemerkt.
Wenn Du die Dimensionen überprüfst, solltest Du das am besten in der Methode machen, in der sie auch übergeben werden. Und dort dann eine Ausnahme und kein ``return`` mit einem Fehlerwert benutzen.
Nach Abarbeitung von `createWidgets()` geht die Liste mit den Referenzen auf die erzeugten `Button`\s verloren. Die sollte man vielleicht unter einem aussagekräftigerem Namen an das Objekt binden. Du willst da ja später noch drauf zugreifen.
Richtig, `tk` ist an das `Tkinter`-Modul gebunden. Und das enthält eine Funktion mit dem Namen `mainloop`. Das ist *die* Hauptschleife. Die Funktion ist auch über jedes Widget über den gleichen Namen erreichbar.
Das Programm hat kein Hauptfenster. Es muss ein Fenster geben, dass von `Tkinter.Tk()` erzeugt wird.
Üblicherweise sollte die `__init__()`-Methode ein Objekt in einen benutzbaren Zustand versetzen. Wenn man dort doch einmal Attribute mit "Nichts" vorbelegen möchte, sollte man nicht unbedingt Objekte vom Typ benutzen, der später verwendet wird, sondern `None`. Sonst kann es bei Zahlen zum Beispiel passieren, dass man auf einem nicht vollständig "gebrauchsfertigen" Objekt Methoden aufruft, die dann mit "Sonderwerten" wie 0 oder -1 rechnen, ohne dass man den Fehler sofort bemerkt.
Wenn Du die Dimensionen überprüfst, solltest Du das am besten in der Methode machen, in der sie auch übergeben werden. Und dort dann eine Ausnahme und kein ``return`` mit einem Fehlerwert benutzen.
Nach Abarbeitung von `createWidgets()` geht die Liste mit den Referenzen auf die erzeugten `Button`\s verloren. Die sollte man vielleicht unter einem aussagekräftigerem Namen an das Objekt binden. Du willst da ja später noch drauf zugreifen.
-
- User
- Beiträge: 102
- Registriert: Dienstag 25. Dezember 2007, 22:53
- Wohnort: Freiburg im Breisgau
Danke für die Hilfe.
So langsam bin ich aber zu müde, um mich da noch reinzudenken. Ich les es mir morgen durch und melde mich dann wieder
So langsam bin ich aber zu müde, um mich da noch reinzudenken. Ich les es mir morgen durch und melde mich dann wieder
Erwarte das Beste und sei auf das Schlimmste vorbereitet.
-
- User
- Beiträge: 419
- Registriert: Sonntag 3. September 2006, 15:11
- Wohnort: in den weiten von NRW
- Kontaktdaten:
Das ist nicht ganz richtig. Es sollte ein Hauptfenster haben, es geht aber auch ohne, bzw. es wird sonst automatisch eins erzeugt.BlackJack hat geschrieben:Das Programm hat kein Hauptfenster. Es muss ein Fenster geben, dass von `Tkinter.Tk()` erzeugt wird.
Code: Alles auswählen
import sys, Tkinter as tk
tk.Button(text="test", command=lambda:sys.stdout.write("test")).pack()
tk.mainloop() # in der Konsole gehts sogar ohne den mainloop
Wenn Du möchtest, dass das Programm überall läuft, musst Du ein `Tk`-Fenster erzeugen. Das ist ähnlich wie mit unterschiedlichen Threads auf die GUI zugreifen, das kann gut gehen, muss es aber nicht immer.
Ohne `mainloop()` geht's soweit ich weiss auch nur unter Linux/Unix.
Ohne `mainloop()` geht's soweit ich weiss auch nur unter Linux/Unix.
-
- User
- Beiträge: 419
- Registriert: Sonntag 3. September 2006, 15:11
- Wohnort: in den weiten von NRW
- Kontaktdaten:
Also bei mir gehts auch unter Windows, allerdings nur in der "Eingabeaufforderung". Mit fehlenden Tk-Fenstern hatte ich noch keine Probleme, aber das mach ich auch nur zum Testen. Bei richtigen Sachen hab ich schon ein Tk-Fenster.
-
- User
- Beiträge: 102
- Registriert: Dienstag 25. Dezember 2007, 22:53
- Wohnort: Freiburg im Breisgau
Das mit der falschen Einschiebung ist nur beim kopieren passiert, im Code ists richtig, danke für den Hinweis. Das mit dem Einrücken ist wirklich etwas merkwürdig, mit einem schönen begin...end oder wenigstens {...}
könnten solche Fehler nicht passieren...
Die Buttons sind jetzt auch etwas haltbarer.
die self.x ist jetzt mit None initialisiert.
Ich habe jetzt für die gesamte GUI so umgestellt: (mit Hauptfenster)
Und als Logik dann
Damit habe ich jetzt die Darstellung komplett von der Logik getrennt und muss nur (1) und (3) anpassen, wenn ich eine andere Darstellung nutzen will.
Was haltet ihr jetzt von dieser Version?
könnten solche Fehler nicht passieren...
Die Buttons sind jetzt auch etwas haltbarer.
die self.x ist jetzt mit None initialisiert.
Ich habe jetzt für die gesamte GUI so umgestellt: (mit Hauptfenster)
Code: Alles auswählen
import Tkinter as tk
import tkFont
class MineWindowWithButtons():
def __init__(self,master):
self.master = master
self.root = tk.Tk()
self.x = None
self.y = None
self.root.newGameButton = tk.Button(text="Neues Spiel")
self.root.newGameButton.grid(row=0,columnspan=2)
# reicht die übergebenen Element als Liste weiter
def returnID(self,x,y):
print [x,y]
self.master.foo(x,y)
Code: Alles auswählen
import Tkinter as tk
import MineGUI as GUI
class App():
def foo(self,x,y):
print "Hallo"
print x
def __init__(self):
a = GUI.MineWindowWithButtons(self) # (1)
a.setDimensions(3,3)
a.root.mainloop() # (3)
App()
Was haltet ihr jetzt von dieser Version?
Erwarte das Beste und sei auf das Schlimmste vorbereitet.
-
- User
- Beiträge: 419
- Registriert: Sonntag 3. September 2006, 15:11
- Wohnort: in den weiten von NRW
- Kontaktdaten:
Das mit dem "master" hast du jetzt etwsa durcheinander gebracht. Das bezog sich auf die Variante, in der du von tk.Frame geerbt hast. Da musst/solltest du der "tk.Frame.__init__" "master" als 2. Argument übergeben, wobei "master" dann eine tk.Frame oder tk.Tk Instanz sein muss (oder ein anderes geeignetes tk-Widget, wzB tk.Canvas). Da du jetzt aber eine eigene Klasse hast, die selbst ein tk.Tk-Fenster erzeugt, brauchst du das nicht. Ansonsten ist das aber eine Möglichkeit.
-
- User
- Beiträge: 102
- Registriert: Dienstag 25. Dezember 2007, 22:53
- Wohnort: Freiburg im Breisgau
Den "master"-Eintrag in der GUI Klasse nutze ich dazu, eine Schnittstelle zwischen Darstellung und Logik zu haben. Jetzt kann ich recht einfach den Buttons sagen, dass sie eine Funktion der Klasse oben drüber aufrufen sollen. Oder gibt es da einen einfacheren Weg, also hat eine Klasse immer schon die Information, von wem sie erschaffen wurde?
Erwarte das Beste und sei auf das Schlimmste vorbereitet.
-
- User
- Beiträge: 419
- Registriert: Sonntag 3. September 2006, 15:11
- Wohnort: in den weiten von NRW
- Kontaktdaten:
Achso, das ist extra so. Na dann ist's ok, ich dachte nur, weil's vorher auch um master und so ging.
Im Grunde ist es ok was du machst, meistens ist es aber einfacher, die GUI-Klasse oben zu haben, denn dort hast du Events, die du verarbeitest ect. Die Datengeschichten kannst du meist in Methoden packen, die du von der Graphik aufrufst. Und wenns nicht ganz so einfach ist, kannst du deine GUI von der Data-Class erben lassen. Dann hast du alles in einem und doch getrennt.
Im Grunde ist es ok was du machst, meistens ist es aber einfacher, die GUI-Klasse oben zu haben, denn dort hast du Events, die du verarbeitest ect. Die Datengeschichten kannst du meist in Methoden packen, die du von der Graphik aufrufst. Und wenns nicht ganz so einfach ist, kannst du deine GUI von der Data-Class erben lassen. Dann hast du alles in einem und doch getrennt.