Gui-Anderung einer Eigenschaft in einem anderen Modul

Fragen zu Tkinter.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Das verkompliziert nur alles unnötig. `Queue` braucht man erst wenn man Threads verwendet und die braucht man erst wenn die Geschäftslogik Sachen machen muss die länger dauern und die GUI nicht blockieren. Und selbst dann würde ich die Geschäftslogik davon möglichst nichts wissen lassen von den Threads sondern das noch zum notwendigen GUI-Code zählen. Denn ohne GUI und deren Anforderungen bräuchte man die Threads ja nicht.
Naja, man kann auch mit einer Liste als Queue arbeiten, wenn man alles schön hintereinander haben will. Weiß nicht was jetzt schneller ist. Eine gut implementierte Queue ist schneller als eine Liste. Aber bei einer Queue mit Threadsicherheit weiß ich es nicht. Normalerweise spielt die Geschwindigkeit sowieso keine Rolle. So schnell Buttons drücken, wie das Programm arbeitet, kann sowieso keiner.

Und oftmals ich eine Queue oder Liste von Nutzen, wenn man den nächsten Schritt erst tun darf, nachdem der erste fertig ist. Da legt man die nachfolgenden Aktionen einfach in die Queue.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Dajosef & Flamez

Hier noch ein bisschen code zu den Anregungen von snafu.
Alle Teile in einer Datei:

Code: Alles auswählen

#!/usr/bin/env python
# coding: UTF-8

import sys, os
from functools import partial

try:
    #~~ For Python 2.x
    import Tkinter as tk
except ImportError:
    #~~ For Python 3.x
    import tkinter as tk
    
APP_TITLE = "MVC-Ansatz"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 400
APP_HEIGHT = 300
APP_BORDER = 10

TEXT_WIN_HEIGHT = 10
TEXT_WIN_WIDTH = 50

STORE_PATH = os.getcwd()
DATA_FILE = "MyData.txt"


class AppModel(object):
    """Model, Logik, Geschäftslogik usw..."""
    def __init__(self):
        pass
    
    def save_data(self, data=""):
        with open(os.path.join(STORE_PATH, DATA_FILE), 'w') as output_file:
            output_file.write(data.encode("utf-8"))
        
    def read_data(self):
        with open(os.path.join(STORE_PATH, DATA_FILE), 'r') as input_file:
            return input_file.read()

        
class AppGui(tk.Frame):
    
    def __init__(self, app, title=APP_TITLE):
        self.app = app
        self.app_win = tk.Tk()
        self.app_win.title(APP_TITLE)
        self.app_win.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
        self.app_win.protocol("WM_DELETE_WINDOW", self.close)
        
        tk.Frame.__init__(self, self.app_win)
        
        self.sub_frame = tk.Frame(self)
        self.sub_frame.pack()

        self.text = tk.Text(self.sub_frame, height=TEXT_WIN_HEIGHT,
            width=TEXT_WIN_WIDTH, highlightthickness=0)
        self.text.pack(side=tk.LEFT)
        
        self.button_frame = tk.Frame(self.sub_frame)
        self.button_frame.pack(side=tk.LEFT, padx=(4, 0), pady=4)
        
        self.Button_start = tk.Button(self.button_frame, text="Read Data",
            command=self.app.read_data)
        self.Button_start.pack(fill=tk.X)
 
        self.Button_pause = tk.Button(self.button_frame, text="Save Data",
            command=self.app.save_data)
        self.Button_pause.pack(fill=tk.X)
 
        self.Button_ende = tk.Button(self.button_frame, text="Close",
            command=self.close)
        self.Button_ende.pack(fill=tk.X)

        self.label = tk.Label(self, text="MyLabel")
        self.label.pack(anchor=tk.W, pady=(4, 0))
                
        self.pack(expand=True, padx=APP_BORDER, pady=APP_BORDER)
        
    def update_text(self, new_text):
        self.text.delete(1.0, tk.END)
        self.text.insert(tk.END, new_text )
        
    def get_text(self):
        return self.text.get(1.0, tk.END)
        
    def label_aendern(self, new_text):
        self.label.config(text=new_text)
    
    def run(self):
        self.app_win.mainloop()
    
    def close(self):
        print("GUI-Shutdown")
        self.app_win.destroy()
        self.app.close(args=None)


class App(object):
    
    def __init__(self, title=APP_TITLE):
        self.app_model = AppModel()
        self.app_gui = AppGui(self)
        self.app_gui.run()
    
    def save_data(self):
        self.app_model.save_data(self.app_gui.get_text())
        
    def read_data(self):
        data = self.app_model.read_data()
        self.app_gui.update_text(data)
                   
    def close(self, args):
        print("Application-Shutdown")
        sys.exit()
        
        
App()
Hier alles aufgeteilt in drei Dateien
Teil-1 Datei app_model.py:

Code: Alles auswählen

#!/usr/bin/env python
# coding: UTF-8

import os

STORE_PATH = os.getcwd()
DATA_FILE = "MyData.txt"


class AppModel(object):
    """Model, Logik, Geschäftslogik usw..."""
    def __init__(self):
        pass
    
    def save_data(self, data=""):
        with open(os.path.join(STORE_PATH, DATA_FILE), 'w') as output_file:
            output_file.write(data.encode("utf-8"))
        
    def read_data(self):
        with open(os.path.join(STORE_PATH, DATA_FILE), 'r') as input_file:
            return input_file.read()
Teil-2 Datei app_gui.py:

Code: Alles auswählen

#!/usr/bin/env python
# coding: UTF-8

import sys
from functools import partial

try:
    #~~ For Python 2.x
    import Tkinter as tk
except ImportError:
    #~~ For Python 3.x
    import tkinter as tk
    
APP_TITLE = "MVC-Ansatz"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 400
APP_HEIGHT = 300
APP_BORDER = 10

TEXT_WIN_HEIGHT = 10
TEXT_WIN_WIDTH = 50

class AppGui(tk.Frame):
    
    def __init__(self, app, title=APP_TITLE):
        self.app = app
        self.app_win = tk.Tk()
        self.app_win.title(APP_TITLE)
        self.app_win.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
        self.app_win.protocol("WM_DELETE_WINDOW", self.close)
        
        tk.Frame.__init__(self, self.app_win)
        
        self.sub_frame = tk.Frame(self)
        self.sub_frame.pack()

        self.text = tk.Text(self.sub_frame, height=TEXT_WIN_HEIGHT,
            width=TEXT_WIN_WIDTH, highlightthickness=0)
        self.text.pack(side=tk.LEFT)
        
        self.button_frame = tk.Frame(self.sub_frame)
        self.button_frame.pack(side=tk.LEFT, padx=(4, 0), pady=4)
        
        self.Button_start = tk.Button(self.button_frame, text="Read Data",
            command=self.app.read_data)
        self.Button_start.pack(fill=tk.X)
 
        self.Button_pause = tk.Button(self.button_frame, text="Save Data",
            command=self.app.save_data)
        self.Button_pause.pack(fill=tk.X)
 
        self.Button_ende = tk.Button(self.button_frame, text="Close",
            command=self.close)
        self.Button_ende.pack(fill=tk.X)

        self.label = tk.Label(self, text="MyLabel")
        self.label.pack(anchor=tk.W, pady=(4, 0))
                
        self.pack(expand=True, padx=APP_BORDER, pady=APP_BORDER)
        
    def update_text(self, new_text):
        self.text.delete(1.0, tk.END)
        self.text.insert(tk.END, new_text )
        
    def get_text(self):
        return self.text.get(1.0, tk.END)
        
    def label_aendern(self, new_text):
        self.label.config(text=new_text)
    
    def run(self):
        self.app_win.mainloop()
    
    def close(self):
        print("GUI-Shutdown")
        self.app_win.destroy()
        self.app.close(args=None)
Teil-3 app.py:

Code: Alles auswählen

#!/usr/bin/env python
# coding: UTF-8

import sys, os
from app_model import AppModel
from app_gui import AppGui

class App(object):
    
    def __init__(self):
        self.app_model = AppModel()
        self.app_gui = AppGui(self)
        self.app_gui.run()
    
    def save_data(self):
        self.app_model.save_data(self.app_gui.get_text())
        
    def read_data(self):
        data = self.app_model.read_data()
        self.app_gui.update_text(data)
                   
    def close(self, args):
        print("Application-Shutdown")
        sys.exit()
        
        
App()
Startskript ist app.py

Gruss wuf :wink:
Take it easy Mates!
BlackJack

@Alfons Mittelmeyer: Du hast den Punkt anscheinend mal wieder nicht verstanden: Nicht die `Queue` als konkrete Datenstruktur ist unnötig sondern auch diese konkrete Datenstruktur durch eine andere schlechter geeignete Queue zu ersetzen. Man ruft einfach die Funktionen auf, ohne so eine Schicht dazwischen zu basteln. Diese Schicht braucht man nicht um GUI und Logik zu trennen. Die braucht man nur wenn Threads ins Spiel kommen. Und da dann auch wirklich nur für die Teile die Threads verwenden, alles weitere kann und sollte man auch weiterhin einfach aufrufen. Denn auch ein Aufruf ist semantisch Nachrichten zwischen Objekten austauschen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@wuf So würde ich es nicht impementieren:

Code: Alles auswählen

self.Button_start = tk.Button(self.button_frame, text="Read Data",
    command=self.app.read_data)
Ich würde es so machen:

Code: Alles auswählen

self.Button_start = tk.Button(self.button_frame, text="Read Data",
    command=(lambda: send('APP_READ_DATA')))
Wenn die App noch nicht da wäre, wenn die GUI definiert wird, kein Problem. Wenn sie allerdings nicht da ist, wenn man den Button drückt, dann liest sie auch keine Daten. Wird man schon merken, dass man vielleicht vergessen hat die App zu importieren oder den Callback nicht registriert hat.
Benutzeravatar
snafu
User
Beiträge: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Wobei ich den Vorschlag von wuf mit den 3 Dateien wirklich nur als modellhaftes Beispiel ansehen würde. Bei so wenig Modulcode macht man normalerweise noch keine Aufteilung über verschiedene Dateien.

Eine modulübergreifende Aufteilung sollte immer erst dann vollzogen werden, wenn man dadurch unmittelbar Vorteile erhält. Mit anderen Worten: Wenn man glaubt, dass man z.B. in 1 Jahr vielleicht mal einen Vorteil von einer Aufteilung haben könnte, weil der Code so umfangreich geworden ist, dann sollte man es besser auch erst in 1 Jahr tun.

Am Anfang eines Projektes würde ich zwar aufteilen, um eine Trennung nach Zuständigkeiten und Abhängigkeiten zu haben, aber ich würde die einzelnen Teile erstmal alle in eine einzige Datei schreiben.

Aber um es noch anzufügen: Diese Ausführungen gelten vor allem für 1-Mann-Projekte. Wenn mehrere Leute an einem Projekt beteiligt sind und man möglicherweise vorab schon eine konkrete Planung für ein umfangreiches Projekt erstellt hat, dann kann man natürlich schon früh mit einzelnen Modulen anfangen. In einem typischen Hobbyprojekt (oder generell bei einem in Eigenregie geführtes Projekt) geht man aber in der Regel nicht so vor, sondern da schreibt einfach drauf los und guckt, was sich entwickelt. So ist zumindest meine Erfahrung.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Mit der send Funktion kann man modulübergreifend arbeiten. Aber sie hat auch mehrere Zwecke. Etwa die hochkomplexe GUI. Wo etwas stehen wird, ist egal. Man kann Teile so oder so zusammenfügen oder trennen und braucht keinen Zugriff über Klassenmembervariablen. Wie man das strukturiert, völlig egal. Hauptsache der Button sendet die Message.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@wuf Du hast die App, welche GUI und MODEL verknüpft, und so Verknüpfungsprobleme gelöst. Dieses Bindeglied dazwischen gann man sich auch sparen. Hier ein Ausschnitt aus Deinem Model:

Code: Alles auswählen

def read_data(self):
    with open(os.path.join(STORE_PATH, DATA_FILE), 'r') as input_file:
        return input_file.read()
Das kann man mit 'send' so machen:

Code: Alles auswählen

def read_data(self):
    with open(os.path.join(STORE_PATH, DATA_FILE), 'r') as input_file:
        send('APP_GOT_DATA',input_file.read())

do_receive(self,'APP_READ_DATA',self.read_data)
Wenn der Button gedrückt wird und 'APP_READ_DATA' sendet, dann sendet read_data die Daten mit der Message ID 'APP_GOT_DATA' weiter. Wer das letztendlich bekommt kann der Memberfunktion egal sein.

Mit do_receive wird hier die Memberfunktion read_data als Callbackfunktion für die Message send('APP_READ_DATA') deklariert.
Zuletzt geändert von Alfons Mittelmeyer am Montag 31. August 2015, 14:25, insgesamt 1-mal geändert.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Alfons

Wir haben hier scheinbar nicht die gleiche Programmier-Philosophie.
Alfons Mittelmeyer hat geschrieben:

Code: Alles auswählen

self.Button_start = tk.Button(self.button_frame, text="Read Data",
    command=(lambda: send('APP_READ_DATA')))
Bei mir wurde lambda schon länger durch partial abgelöst.

Wo befindet sich bei dir die send() Funktion (Methode) und was beinhaltet sie? In der gleichen Klasse wie self.Button_start befindet sie sich scheinbar nicht sonst würde noch das self. zum send.(....) Call fehlen.
Alfons Mittelmeyer hat geschrieben:Wenn die App noch nicht da wäre, wenn die GUI definiert wird, kein Problem. Wenn sie allerdings nicht da ist, wenn man den Button drückt, dann liest sie auch keine Daten. Wird man schon merken, dass man vielleicht vergessen hat die App zu importieren oder den Callback nicht registriert hat.
Der ExceptinonHandler von Python würde dich hier in der Programmierphase in klar verständlichem Text informiert sollte da etwas fehlen oder nicht importiert worden sein. Ist dies nicht gewährleistet muss hierfür selbstverständlich eine eigene Exception-Abfangklasse konstruiert werden.

Gruss wuf :wink:
Take it easy Mates!
BlackJack

@Alfons Mittelmeyer: Man kann sich also das ”Bindeglied” eines direkten Methodenaufrufs ”sparen” indem man eine *zusätzliche* Schicht dazwischen setzt. Kreative Auslegung von Worten würde das nennen. Ist ja schön das man so etwas machen kann wenn man so etwas braucht, aber man sollte es halt auch nur dann machen. Ansonsten verkompliziert es nur alles und macht die Zusammenhänge undurchsichtiger ohne einen Mehrwert zu haben.
Benutzeravatar
snafu
User
Beiträge: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Meistens hat man den Code zum Anstoßen des GUI-Loops - sprich: den eigentlichen Aufruf der grafischen Anwendung - ganz in der Nähe vom restlichen GUI-Code (oft direkt in `main()`). Dann braucht es kein Extra-Modul für vermeintliche Verknüpfungen und auch keine umständlichen Übergaben an ein gesondertes Framework.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

wuf hat geschrieben:Hi Alfons

Wir haben hier scheinbar nicht die gleiche Programmier-Philosophie.
Alfons Mittelmeyer hat geschrieben:

Code: Alles auswählen

self.Button_start = tk.Button(self.button_frame, text="Read Data",
    command=(lambda: send('APP_READ_DATA')))
Bei mir wurde lambda schon länger durch partial abgelöst.

Wo befindet sich bei dir die send() Funktion (Methode) und was beinhaltet sie? In der gleichen Klasse wie self.Button_start befindet sie sich scheinbar nicht sonst würde noch das self. zum send.(....) Call fehlen.
Alfons Mittelmeyer hat geschrieben:Wenn die App noch nicht da wäre, wenn die GUI definiert wird, kein Problem. Wenn sie allerdings nicht da ist, wenn man den Button drückt, dann liest sie auch keine Daten. Wird man schon merken, dass man vielleicht vergessen hat die App zu importieren oder den Callback nicht registriert hat.
Der ExceptinonHandler von Python würde dich hier in der Programmierphase in klar verständlichem Text informiert sollte da etwas fehlen oder nicht importiert worden sein. Ist dies nicht gewährleistet muss hierfür selbstverständlich eine eigene Exception-Abfangklasse konstruiert werden.

Gruss wuf :wink:
Sie befindet sich in einem anderen Modul. Sorry für den Sternchenimport. Wenn Du eine Seite weiter zurückgehst, kannst Du sie sehen. Gehört also in ein Modul.
Exception gibt es hier keine. Send sendet an alle dafür registrierten Empfänger. Wenn es 0 Empfänger sind, dann bekommt eben die Message keiner.
Und Exceptionklasse braucht man auch keine. Wenn man etwa die GUI schon implementiert hat und die Geschäftslogik nicht oder nur teilweise, braucht man doch keine Exceptions. Man kann ja eine print Anweisung für 0 Empfänger implementieren, damit man sieht, ob man sich vielleicht vertippt hat.

Und dass man an mehrere Empfänger senden kann, ist auch sehr vorteilhaft. Wenn wie auch immer in meinem GUI Designer ein anderes Widget zur Bearbeitung ausgewählt wurde, müssen verschiedene GUI Teile darüber Bescheid wissen. Da sende ich einfach nur: send('SELECTION_CHANGED')
Benutzeravatar
snafu
User
Beiträge: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@wuf: Was sollen `save_data()` und `read_data()` in der `AppModel`-Klasse eigentlich bringen? Die sollten doch wohl als besser als Callbacks an konkrete Benutzeraktionen gebunden sein. Und dies kann man oft auch als Funktionen *der Geschäftslogik* auf Modulebene definieren und ihnen die notwendigen Parameter übergeben.

Dein `AppModel` vermischt bereits Geschäftslogik und GUI miteinander. Modelle an sich sind ja ganz nett, aber ein `AppModel` erscheint mir doch sehr ungewöhnlich.

Was man machen *könnte*, ist die besagte Implementierung der beiden Methoden innerhalb der Geschäftslogik und dazu noch ein Modell, welches den Pfad zur genutzten Datei in `__init__()` annimmt. Das Speichern und Laden könnte vom Modell unter Einbeziehung des Dateinamens zur Verarbeitung an die Geschäftslogik übergeben werden. Weiterhin könnte dieses Modell entsprechende Events auslösen. Dann wäre man in etwa bei dem, was Alfons vorgeschlagen hat.

Die Frage ist aber: Braucht man die Komplexität eines eventbasierten Modells als Zwischenschicht an der Stelle überhaupt? Reicht es nicht aus, einfach Callbacks durchzuführen und mit Rückgabewerten zu arbeiten? Wenn man mit einer einfachen Lösung genau so zum Ziel kommt und damit sogar Code einspart im Gegensatz zur modellbasierten Lösung, dann würde ich die einfache Lösung doch bevorzugen. IMHO sollte man als Programmierer auch mal ein Gefühl dafür bekommen, wann die einfache Lösung die bessere Lösung ist.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@snafu Da hat wuf das anders benannt. Was Du als App verstehst, hat er AppModel genannt und die Zwischenschicht, die Du als Model ansehen würdest, hat er App genannt.

Da stmme ich Dir bei, je einfacher desto besser. Eine Zwischenschicht für eine komplexe GUI und komplexe App gerät leicht unüberschaubar und diese Arbeit kann man sich durch direkte Callbacks sparen. Und wenn man Message IDs benutzt, braucht man sich um GUI und APP Aufbau überhaupt nicht kümmern. Wenn man in den professionellen Bereich bei komplexen Programmen schaut, etwa Steuergeräte und HMI im Auto, geht alles über Messages und das über verschiedene Bussysteme.

Da sind dann die Messages noch weiter unterteilt, nämlich statt nur Message ID, Funktionsblock und Message ID, wobei der Funktionsblock dann für ein Teilsystem - etwa Steuergerät - steht.
Benutzeravatar
snafu
User
Beiträge: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Alfons Mittelmeyer: Ist dir eigentlich bewusst, dass TKinter nicht nur ein eigenes Event-System eingebaut hat, sondern auch die Möglichkeit bietet, über sogenannte Virtual Events eigene Events zu erzeugen? Mittels event_add() erstellt man solche Events. Will man ein Event erzeugen, welches nicht mit einer direkten Benutzeraktion in der grafischen Oberfläche verbunden ist, dann gibt es außerdem noch `event_generate()`. Im Prinzip kannst du dir somit deinen eigenen Event-Mechanismus sparen und besser auf die Funktionalität von TKinter zurückgreifen.
Benutzeravatar
snafu
User
Beiträge: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Alfons Mittelmeyer hat geschrieben:@snafu Da hat wuf das anders benannt. Was Du als App verstehst, hat er AppModel genannt und die Zwischenschicht, die Du als Model ansehen würdest, hat er App genannt.
Nein. Mir ging es darum, dass ein Modell von einer Anwendung merkwürdig ist. Modelle sind üblicherweise an GUI-Widgets gebunden, nicht aber an die eigentliche Anwendung.
BlackJack

@snafu: `Tkinter`\s benutzerdefinierte Ereignisse haben allerdings den Nachteil das man keine Daten damit übermitteln kann. Tk selbst kann das, aber die `Tkinter`-Anbindung hat das nicht implementiert. Man kann damit also nur mitteilen *das* irgendein Ereignis eingetreten ist. Will man zusätzlich noch Informationen übermitteln dann braucht man noch eine Queue oder einen ähnlichen Mechanismus über den sich der Empfänger dann die Zusatzinformationen abholt. Was bei mehreren Empfängern dann problematisch werden kann.

Wenn eine Anwendung so komplex wird das sich ein Nachrichtendienst mit Publisher-Subscriber-Modell lohnt, dann würde ich das aber auch nicht selber basteln. Da gibt's ja schon Lösungen. Von kleinen ”in-app”-Lösungen bis hin zu welchen die allen möglichen Schnickschnack bis hin zu Interprozesskommunikation und Persistenz bieten. Vieles auch interoperabel mit anderen Programmiersprachen wenn man eine der zahlreichen Messagequeues verwendet (RabbitMQ, ZMQ, …).
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi sanfu
snafu hat geschrieben:@wuf: Was sollen `save_data()` und `read_data()` in der `AppModel`-Klasse eigentlich bringen? Die sollten doch wohl als besser als Callbacks an konkrete Benutzeraktionen gebunden sein. Und dies kann man oft auch als Funktionen *der Geschäftslogik* auf Modulebene definieren und ihnen die notwendigen Parameter übergeben.

Dein `AppModel` vermischt bereits Geschäftslogik und GUI miteinander. Modelle an sich sind ja ganz nett, aber ein `AppModel` erscheint mir doch sehr ungewöhnlich.

Was man machen *könnte*, ist die besagte Implementierung der beiden Methoden innerhalb der Geschäftslogik und dazu noch ein Modell, welches den Pfad zur genutzten Datei in `__init__()` annimmt. Das Speichern und Laden könnte vom Modell unter Einbeziehung des Dateinamens zur Verarbeitung an die Geschäftslogik übergeben werden. Weiterhin könnte dieses Modell entsprechende Events auslösen. Dann wäre man in etwa bei dem, was Alfons vorgeschlagen hat.

Die Frage ist aber: Braucht man die Komplexität eines eventbasierten Modells als Zwischenschicht an der Stelle überhaupt? Reicht es nicht aus, einfach Callbacks durchzuführen und mit Rückgabewerten zu arbeiten? Wenn man mit einer einfachen Lösung genau so zum Ziel kommt und damit sogar Code einspart im Gegensatz zur modellbasierten Lösung, dann würde ich die einfache Lösung doch bevorzugen. IMHO sollte man als Programmierer auch mal ein Gefühl dafür bekommen, wann die einfache Lösung die bessere Lösung ist.
Was sollen `save_data()` und `read_data()` in der `AppModel`-Klasse eigentlich bringen?
Diese Methoden machen genau das wonach sie benannt sind.
Dein `AppModel` vermischt bereits Geschäftslogik und GUI miteinander. Modelle an sich sind ja ganz nett, aber ein `AppModel` erscheint mir doch sehr ungewöhnlich.
Da wird nichts mit der GUI vermischt. Die GUI ruft mittel den Events der Buttons über die App-Klasse (Controller) die Methode in der AppModel-Klasse auf und greift nicht direkt darauf zu. Gut diese Methoden können natürlich auch in der App-Klasse oder irgendwo stehen. Ist mir natürlich sonnenkar, dass mein oben gezeigtes MVC Konstrukt in dieser Grösse ein Witz ist! So kann ich mir aber MVC auch im grossen vorstellen. Eventuell kannst du mein geschriebenes so anpassen wie du es für richtig findest.

Gruss wuf :wink:
Take it easy Mates!
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

snafu hat geschrieben:@Alfons Mittelmeyer: Ist dir eigentlich bewusst, dass TKinter nicht nur ein eigenes Event-System eingebaut hat, sondern auch die Möglichkeit bietet, über sogenannte Virtual Events eigene Events zu erzeugen? Mittels event_add() erstellt man solche Events. Will man ein Event erzeugen, welches nicht mit einer direkten Benutzeraktion in der grafischen Oberfläche verbunden ist, dann gibt es außerdem noch `event_generate()`. Im Prinzip kannst du dir somit deinen eigenen Event-Mechanismus sparen und besser auf die Funktionalität von TKinter zurückgreifen.
Dieses virtuelle event hätte doch fast brauchen können. Ich hätte gedacht, ich könnte es für die Auswahl auch einer Listbox nehmen. Die brauche ich nämlich auf <Return> und auf <Button-1> und es ist dieselbe Funktion. Leider ist dabei ein Parameter unterschiedlich. Schade, kann ich dann nicht nehmen. Hab leider keine Verwendung für dieses virtuelle event.

Aber dieser Fall kommt öfters vor, dass man dieselbe Funktion braucht, wenn man in einem Entry Return drückt und wenn auf einen Button drückt, der auf das Entry zugreift. Unterschiedliche Widgets mit selber Funktion ist wohl auch nichts für das virtuelle Event. Also zu besonders viel ist das virtuelle event nicht nutze.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Alfons

Habe deine Funktion buildGui. Muss die Sache noch näher anschauen da ich mit den Modulen proxy und threading noch nicht so viel gearbeitet habe.

Danke. Gruss wuf :wink:
Take it easy Mates!
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

wuf hat geschrieben:Hi Alfons

Habe deine Funktion buildGui. Muss die Sache noch näher anschauen da ich mit den Modulen proxy und threading noch nicht so viel gearbeitet habe.

Danke. Gruss wuf :wink:
buildGui taugt nichts - geht auf Ubuntu aber nicht unter Windows , habe ich vermutlich im Thread 'spazieren' richtig gemacht. proxy.py musst Du nehmen.

Richtig für Zugriff aus einem anderen Thread wäre execute_gui.py von: http://www.python-forum.de/viewtopic.ph ... 8&start=30

Aber wie gesagt, proxy.py ist die Lösung. Inzwischen habe ich es mit execute_gui.py kombiniert, sodass man nach Start von mainloop() auch von einem anderen Thread aus senden kann. Aber wann braucht man das schon.
Zuletzt geändert von Alfons Mittelmeyer am Montag 31. August 2015, 15:57, insgesamt 1-mal geändert.
Antworten