Matplotlib in Tkinter Gui einbetten

Fragen zu Tkinter.
stefanxfg
User
Beiträge: 85
Registriert: Sonntag 2. April 2017, 14:11

Hallo Alfonos,

bitte warte mal. Ich habe alles per Hand geschrieben. Ich denke, du hast recht. Ich werde deinen Designer verwenden. Ich poste dir dann den Code. Ich muss aber alles verstehen.
In meinem ursprünglichen Code habe ich nun für Matplotlib die Funktionen zeichnen lassen und mit Steuerkreuz einen mpl_connect verbunden. Es läuft, aber noch nicht perfekt.

Ich melde mich dann, wenn ich erst einmal etwas habe.

Vielen Dank für deine Hilfe.

Grüße von Stefan
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: ich fang mal mit meinen Bemerkungen von oben an. Sternchenimporte sind, Du weißt es schon, böse. Diese Kommentare mit den vielen = stören nur. Wenn die Struktur nicht aus dem Code an sich klar wird, dann hast Du ein Problem. Was Du da in create_menu löschen mußt, ist mir ein Rätsel. In einem neu erzeugten Widget sollte man eigentlich nichts löschen können/müssen. Zeile 77: das continue durch ein else ersetzen. Die variablen Teile von add_command übergibt man als Keywords und ändert nicht das Wörterbuch in jedem Schleifendurchgang.

In DynTkImports: es heißt shorten_dictionary, weil man Funktionen Tätigkeiten beschreiben. Einen Wörterbucheintrag erst zu lesen und dann zu poppen ist irgendwie umständlich. Da shorten_dictionary das übergebene Wörterbuch ändert, sollte es nicht auch noch Rückgabewert sein:

Code: Alles auswählen

def shorten_dictionary(dictionary):
    # reduce tuple to last entry
    for key, value in dictionary.items():
        dictionary[key] = value[-1]
 
    # erase doubles
    for short, long in [("bd", "borderwidth"), ("bg", "background"), ...]:
        if long in dictionary:
            dictionary[short] = dictionary.pop(long)

    for to_be_removed in ["colormap", ...]:
        if  to_be_removed in dictionary:
            del dictionary[to_be_removed]
get_entryconfig bekommt einen index, der aber gar nicht benutzt wird. communication.py enthält nur irgendwelche seltsamen globalen Variablen, das muß also weg. Was soll der hintere Unterstrich in `_dictionary_`? Und die letzte Datei möchte keiner haben. Gemisch aus globalen Anweisungen und Klassendefinitionen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: ich fang mal mit meinen Bemerkungen von oben an. Sternchenimporte sind, Du weißt es schon, böse.
Das sind Deine vorgefaßten Meinungen. Sternchenimporte sind in manchen Fällen notwendig.

Oder kann man so etwas anbieten?

Code: Alles auswählen

import DynTkInter as ttk

...
    self.mybutton = ttk.ttk.ttk.Button()
Das käme ohne Sternchenimporte heraus. Meinst Du nicht, das so etwas unsinnig ist?

Es ließe sich vielleicht vermeiden durch:

from DynTkInter import ttk.ttk as ttk

Ich weiß nicht, ob das so geht. und wenn es ginge, dann wäre es doch sehr unschön.
BlackJack

@Alfons Mittelmeyer: Du hast da eine vorgefasste Meinung, Sirius3 hat nur die allgemein anerkannte Vorgehensweise vorgetragen, das man keine Sternchen-Importe verwendet. Du bist der beratungsresistente Geisterfahrer hier. Wieder mal. Deine Importe zeigen das Du anscheinend auch Probleme hast den offensichtlichen Weg das passende `ttk` zu importieren zu verwenden. Deshalb zählt Deine vorgefasste Meinung auch weniger als die von jemanden der Python kann. Sie basiert halt auf Unwissenheit. Ist nicht weiter schlimm, dagegen kann man ja etwas machen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:Was Du da in create_menu löschen mußt, ist mir ein Rätsel. In einem neu erzeugten Widget sollte man eigentlich nichts löschen können/müssen.
Man kann natürlich etwas für eine Bestimmte Anzahl von Sprachen machen, etwa einige aus Europa. Es könnten aber auch mehr werden, eventuell kommt Asien auch dazu mit einer unterschiedlichen Sprachenanzahl.

So ist das Menü voll flexibel. Es wird eine Liste übergeben, egal wie kurz oder lang und das Menü baut sich entsprechend der Liste neu auf. Und wenn man etwa activebackground oder sonst etwas an den commands ändern will, braucht man das nur beim ersten tun. Der erste bleibt fix, wobei sich auch der Text ändert, das Aussehen er anderen, die dynamisch erzeugt werden, richtet sich danach!
Sirius3 hat geschrieben: Zeile 77: das continue durch ein else ersetzen. Die variablen Teile von add_command übergibt man als Keywords und ändert nicht das Wörterbuch in jedem Schleifendurchgang
Wie willst Du das Wörterbuch durch Keywords von etwas ändern, das von etwas ändern, das gar nicht noch nicht existiert? Das Wörtebuch stamt vom ersten command und darin wird der label und der command geändert!
Sirius3 hat geschrieben:Einen Wörterbucheintrag erst zu lesen und dann zu poppen ist irgendwie umständlich.
Ja finde iich auch, bei entryconfig bekommt man dumme border objects, etwa so:

'background': ('background', '', '', '', <border object: 'white'>)
Wenn man das liest mit entryconfig, bekommt man 'white'.
Das shorten dagegen hate ich bei config und layouts gemacht, ist aber hier in diesem Fallö überflüssig, darauf hatte ich nicht geachtet, dass ich das hier gar nicht brauche.

Nö, ich brauche es dooch, weil ich dann so eine Fehlermeldung bekomme: bitmap "{}" not defined
Sirius3 hat geschrieben:get_entryconfig bekommt einen index, der aber gar nicht benutzt wird.
Das ist Unsinn, der ist ganz wichtig und wird benutzt bei: menu.entrycget(index,entry)
Sirius3 hat geschrieben:communication.py enthält nur irgendwelche seltsamen globalen Variablen, das muß also weg.
Nein das sind zwei Funktionen, und nur über die ist das Zusammenspiel von Teilen möglich alled andere muss sweg, außer den Widget und Namenlosen Klassen, für die es kein Objekt gibt, und auf die sonst nicht zugreifen kann.

Der Code sieht dann so aus:

def noname1:
def __init__(self,ref_to_widget,ref_to_widget..) # aber nur im selben container (Frame, oder Menu, oder App oder Toplevel, etc.)
...

noname1()

Und nach der Definition werden die sofort aufgerufen und das war es dann ohne die Funktionen subcribe und publish läuft fast gar nichts, höchstens dass eine Betätigung eines Buttons etwas anderes im selben container verändern könnte aber ohne jeglichen Bezug zu außen. Und genau das ist der Sinn, unabhängige Teile, die ganz egal irgenwo stehen könnte, die man einfach jederzeit umverlagern kann und bei denen man nicht verschiedenen Code zu einem Wirrwarr vermischen kann.
Sirius3 hat geschrieben:Und die letzte Datei möchte keiner haben. Gemisch aus globalen Anweisungen und Klassendefinitionen.
Nein es geht nicht um Klassendefinitionen, sondern nur um einen Aufruf, die Klassen sind innerhalb von Funktionen völlig unbekannt und dienen nur dazu, bei der Initialisiereung auch einen Code aufzurufen. Das ist mit einer Klasse einfacher, weil man bei einer Funktion nur auf rückwärts Bezug nehmen kann, bei einer Klasse aber auch vorwärts. Ansonsten wäre eine Klasse unnötig.

Na als Enresultat möchte wahrscheinlich niemand solchen Code haben. Aber als Zwischensfufe ist er ideal. Man ,kann den Code beliebig umverlagern und zerteilen, etwa das:

Code: Alles auswählen

config(myclass='BOStrab_Fahrzeugeinschraenkung')

Menu('MainMenu',myclass='MenuGUI')
goIn()

MenuItem('languages','cascade',label='Sprachen')
goIn()

Menu('language_submenu',myclass='LanguageSubmenu', tearoff=0)
goIn()

MenuItem('deutsch','command',label='deutsch')

widget('deutsch').layout(index=1)

### CODE ===================================================

class LanguageSubmenu:
    def __init__(self):

        self._start()
        self._dont_save_dynamically_created()

    def _start(self,container = container()):
        self.container = container
        self.deutsch_index = self.container['tearoff']
        self._create_menu()
               

    def _create_menu(self):
        current_selection = Selection()
        self.create_menu()
        setSelection(current_selection)

        # here should follow own code in tkinter style
        # later the GuiDesigner should be able to export this code

# EXPORT =============================

    def create_menu(self):    
        
        self.container.add_checkbutton()

        # now we delete the menu entries after deutsch
        after_deutsch = self.deutsch_index + 1
        while True:
            itemtype = self.container.type(after_deutsch)
            self.container.delete(after_deutsch)
            if itemtype == 'checkbutton':
                break
 
        command_config = get_entryconfig(self.container,self.deutsch_index)
        languages = ('deutsch','english','russisch','polnisch','italienisch',None,'spanisch','französisch',None,'dänisch')

        for index,language in enumerate(languages):

            if not language:
                self.container.add_separator()
                continue

            command_config['label'] = language
            command_config['command'] = partial(self.do_action,language)
                
            try:
                self.container.entryconfig(index+self.deutsch_index,**command_config)
            except IndexError:
                self.container.add_command(**command_config)
                 
    def do_action(self,language):
        publish("SELECT_LANGUAGE",language)
            
# /EXPORT =============================

    # we don't want to save dynamically created widgets after self.deutsch_index by the GuiDesigner
    def _dont_save_dynamically_created(self):
        start_index = self.deutsch_index - self.container['tearoff']
        for element in self.container.PackList[start_index+1:]:
            element.dontSave()
        
        
LanguageSubmenu()

### ========================================================

goOut()
select_menu()

goOut()


widget('languages').layout(index=1)

goOut()
select_menu()
Zerteilt man in das:

Code: Alles auswählen

config(myclass='BOStrab_Fahrzeugeinschraenkung')

Menu('MainMenu',myclass='MenuGUI')
goIn()

MenuItem('languages','cascade',label='Sprachen')
goIn()

Menu('language_submenu',link='Scripts/languages.py').select_menu()

goOut()


widget('languages').layout(index=1)

goOut()
select_menu()
Das interessiert aber nicht weiter, denn das sind nur die Widgets und Widget directories für den gui designer.

Und in das:

Code: Alles auswählen

config(myclass='LanguageSubmenu', tearoff=0)

MenuItem('deutsch','command',label='deutsch')

widget('deutsch').layout(index=1)

### CODE ===================================================

class LanguageSubmenu:
    def __init__(self):

        self._start()
        self._dont_save_dynamically_created()

    def _start(self,container = container()):
        self.container = container
        self.deutsch_index = self.container['tearoff']
        self._create_menu()
              

    def _create_menu(self):
        current_selection = Selection()
        self.create_menu()
        setSelection(current_selection)

# EXPORT =============================

    def create_menu(self):    


        # now we delete the menu entries after deutsch
        self.container.add_checkbutton()
        after_deutsch = self.deutsch_index + 1
        while True:
            itemtype = self.container.type(after_deutsch)
            self.container.delete(after_deutsch)
            if itemtype == 'checkbutton':
                break

        command_config = get_entryconfig(self.container,self.deutsch_index)
        languages = ('deutsch','english','russisch','polnisch','italienisch',None,'spanisch','französisch',None,'dänisch')

        for index,language in enumerate(languages):

            if not language:
                self.container.add_separator()
                continue

            command_config['label'] = language
            command_config['command'] = partial(self.do_action,language)
                
            try:
                self.container.entryconfig(index+self.deutsch_index,**command_config)
            except IndexError:
                self.container.add_command(**command_config)
                 
    def do_action(self,language):
        publish("SELECT_LANGUAGE",language)
            
# /EXPORT =============================

    # we don't want to save dynamically created widgets after self.deutsch_index by the GuiDesigner
    def _dont_save_dynamically_created(self):
        start_index = self.deutsch_index - self.container['tearoff']
        for element in self.container.PackList[start_index+1:]:
            element.dontSave()
        
        
LanguageSubmenu()

### ========================================================
Und auf diesen Code kann man sich, dann ohne alles Beiwerk voll konzentrieren, kann nichts anderes hineinmischen, und dann fällt einem ein, dass ja die language Liste empfangen werden muss und macht dann eine entsprechene Methode unter Benutzung von subscribe hinein. Wenn später ein Sender hierfür implementiert ist, dann tut sich auch etwas. Dem Programm fehlt nichts, es ist immer lauffähig, nur ohne Sender, tut es nicht viel.

Und genau darum geht es, immer ein lauffähiges Programm haben und die Funktionalität ohne Nebenwirkungen auf Anderes erweitern.

PS: Und dann muss die Sprachliste jeweils ein Tuple haben nämlich die Sprache, die angezeigt werden soll und die Sprachidentität, eventuell da bei Englisch bleiben.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@stafanxfg: ich habe Dir auf GitHub ein kleines Beispiel für Dein Menü angelegt. Dort siehst Du wie man die Teile gut trennen kann und nichts vermischt.

Das Startscript ist matlib.py

Code: Alles auswählen

import DynTkInter as tk
tk.Tk().mainloop('Scripts/app.py')
Hier wird die GUI gestartet und dabei die widgets aus der Hauptanwendung app.py angelegt.

Das ist die Hauptanwendung app.py:

Code: Alles auswählen

config(myclass='BOStrab_Fahrzeugeinschraenkung')

Menu('MainMenu',link='Scripts/menu.py').select_menu()
Hier sieht man, wie die Klasse heißt, wenn man sie später exportiert.
Und Außerdem sieht man, dass die App bisher nur ein Menü enthält.
In das Menü werden die widgets aus menu.py geladen und dabei etwaiger Code ausgeführt.

Das ist nun das Menü menu.py

Code: Alles auswählen

config(myclass='MenuGUI')

MenuItem('languages','cascade',label='Sprachen')
goIn()

Menu('language_submenu',link='Scripts/languages.py').select_menu()

goOut()

MenuItem('test','command',label='Test')

widget('languages').layout(index=1)
widget('test').layout(index=2)

### CODE ===================================================


class MainMenu:

    def __init__(self,language = widget('languages'),test=widget('test')):
        self.language = language
        subscribe('MENU_LANGUAGE',self.receive_language)

        # for testing
        test['command'] = self.test

    def receive_language(self,languages):
        self.language['label'] = languages[0] # this should be written as entryconfig for tkinter
        publish('SUBMENU_LANGUAGES',languages[1:])

    def test(self):
        current_selection = Selection() # for GuiDesigner the selection shouldn't change
        Toplevel('test',title = 'Testing',link='Scripts/tests.py')
        setSelection(current_selection) # for GuiDesigner the selection shouldn't change

MainMenu()

### ========================================================

Das Menü hat eine Cascade und dieser ist ein Submenü zugeordnet. In diees Submenü werden die Widgets aus languages.py geladen und etwaiger Code ausgeführt.

Außerdem habe ich zum Testen einen Command Button namens 'test' angelegt. WEnn man diesen Button drückt, legt er ein Toplevel an und lädt dort test.py herein

Nun ein Blick auf test.py

Code: Alles auswählen

# -*- coding: utf-8 -*-
Button('english',text='english')
Button('german',text='german')

widget('german').pack(side='left')
widget('english').pack(side='left')

### CODE ===================================================

class TestApplication:

    def __init__(self,german = widget('german'), english = widget('english')):
        german['command'] = partial(self.send_language,0)
        english['command'] = partial(self.send_language,1)

    def send_language(self,index):
        language_lists = (
            ( 'Sprache',('deutsch','german'),None,('englisch','english'),('französisch','french')),
            ( 'Language',('english','english'),None,('french','french'),('german','german'))
        )
        publish('MENU_LANGUAGE',language_lists[index])

TestApplication()
            
### ========================================================
Nichts Besonderes nur zwei Buttons, wenn man sie drückt, senden sie eine Liste, entweder für deutsche oder englische Sprache. Mit MENU_LANGUAGE kommt diese Liste zum Menü.

Ein Blick zurück zum Menü zeigt, dass dort mit dem ersten Element dieser Liste der Text er Cascade geändert wird.Annchließend wird er Rest der Liste mittels SUBMENU_LANGUAGES zum Submenü gesandt

Und dort in languages.py wird dannn das Submenü entsprechend dieser Liste neu aufgebaut und mit ensprechenden Commands versehen

Code: Alles auswählen

config(myclass='LanguageSubmenu', tearoff=0)

MenuItem('deutsch','command',label='deutsch')

widget('deutsch').layout(index=1)

### CODE ===================================================

# this shall later be exported in another form from widgets in tkinter style
# later the GuiDesigner should be able to create such a code

# this name doesn't matter. Only for better recognition
class LanguageSubmenu:
    def __init__(self,container = container()):
        self.container = container
        self.deutsch_index = self.container['tearoff']
        subscribe('SUBMENU_LANGUAGES',self.receive_languages)
               
    def receive_languages(self,languages):
        current_selection = Selection() # for GuiDesigner the selection shouldn't change
        self.create_menu(languages) # create the submenu commands annd separators
        self._dont_save_dynamically_created() # the GUIDesigner shouldn't save dynamically created commands
        setSelection(current_selection) # for GuiDesigner the selection shouldn't change

        # here should follow own code in tkinter style
        # later the GuiDesigner should be able to export this code

# EXPORT =============================

    # after GUI definition: the Code ===========================
    # this code may also be inserted in GuiDesigner Scripts
    

    def create_menu(self,languages):    


        # for calling more times, delete the menu, which existed before,
        # except the first command

        # for marking the end
        
        self.container.add_checkbutton()

        # now we delete the menu entries after deutsch
        after_deutsch = self.deutsch_index + 1
        while True:
            itemtype = self.container.type(after_deutsch)
            self.container.delete(after_deutsch)
            if itemtype == 'checkbutton':
                break
 
        # now we make a dynamic creation
        # first we get the style of the first command
        # this style should also be used for the other commands

        command_config = get_entryconfig(self.container,self.deutsch_index)
        
        # we dont't use some now not defined languagefile[i]
        # we can think later of this

        # now we create dynamic commands

        # Example
        # languages = (('Deutsch','german'),None,('English','english'),('Spanisch','spanish'),('Französisch','french'))

        for index,language in enumerate(languages):

            if not language:
                self.container.add_separator()
                continue

            command_config['label'] = language[0]
            command_config['command'] = partial(self.do_action,language[1])
                
            try:
                self.container.entryconfig(index+self.deutsch_index,**command_config)
            except IndexError:
                self.container.add_command(**command_config)
                 
    def do_action(self,language):
        publish("SELECT_LANGUAGE",language)
            
# /EXPORT =============================

    # we don't want to save dynamically created widgets after self.deutsch_index
    # by the GuiDesigner
    def _dont_save_dynamically_created(self):
        start_index = self.deutsch_index - self.container['tearoff']
        for element in self.container.PackList[start_index+1:]:
            element.dontSave()
        
        
LanguageSubmenu()

### ========================================================
Als Nächstes sollte mann dann wohl an den Canvasplot gehen. Es ist völlig egal, wo dieser später hin soll. Das kann man auf dieses Weise umbelegen, wie man will, denn alles ist voneinander unabhängig, nur verbunden durch die Messages.

Wenn Dir diieser Script Code nicht gefällt. Macht nichts, kann man am Ende, wenn alles fest steht, wieder in eine Normalform bringen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Jetzt steht auch der Labelframe mit dem matplotlib canvas fest.

Was hat sich geändert?

Imports für matplotlib und numpy ergänzt in DynTkImports.py

Der Labelframe wurde zunächst in das Anwendungsfenster gelegt.

app.py

Code: Alles auswählen

config(myclass='BOStrab_Fahrzeugeinschraenkung')

Menu('MainMenu',link='Scripts/menu.py').select_menu()
LabelFrame('plotframe',link='Scripts/plot.py')

widget('plotframe').pack()
Das ist dann plot.py

Code: Alles auswählen

config(text='Madplotlib')


### CODE ===================================================

class Plot_Frame:
    
    def __init__(self,container=container()):
        self.container = container

        self.fig = Figure (figsize=(5,4), dpi=100)
        self.ax = self.fig.add_subplot(111)
        self.ax = self.fig.add_subplot(111) #für 2d-Plot
        self.ax.set_title('Definition der LRUGL')
        self.ax.set_xlabel('Breite y [mm]')
        self.ax.set_ylabel('Hoehe z [mm]')
        self.ax.axis([-2000,2000,0, 5000])
        self.ax.grid(True)

        self.canvas = FigureCanvasTkAgg(self.fig,container)
        self.toolbar = NavigationToolbar2TkAgg(self.canvas,container)
        self.toolbar.update()
        self.plot_widget = self.canvas.get_tk_widget()
        self.plot_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
        self.toolbar.pack(side=tk.TOP, fill=tk.BOTH, expand=1)

        self.lineVal, = self.ax.plot(np.zeros(100))
        self.canvas.mpl_connect('motion_notify_event', self.onMouseMove)
        self.canvas.show()

        subscribe('REQUEST_FIG,AX',self.fig_ax_request)

    def onMouseMove(self, event):

        self.ax.lines = [self.ax.lines[0]]
        if event.xdata and event.ydata: # python3 often has None
            self.ax.axhline(y=event.ydata, color="k") #(y=event.ydata, color="k")
            self.ax.axvline(x=event.xdata, color="k") #(x=event.xdata, color="k")   

    def fig_ax_request(self):
        publish('FIG,AX',self.fig,self.ax)

Plot_Frame()

### ========================================================
Interessant dabei ist:

Code: Alles auswählen

        subscribe('REQUEST_FIG,AX',self.fig_ax_request)

    def fig_ax_request(self):
        publish('FIG,AX',self.fig,self.ax)
Die Komponente, die plotten oer zeichen will, sendet 'REQUEST_FIG,AX'
und bekommt dann per 'FIG,AX' fig und ax mitgeteilt.

Zum Testen wurde test.py entsprechend ergänzt.

Code: Alles auswählen

# -*- coding: utf-8 -*-
Button('english',text='english')
Button('german',text='german')
Button('plot',text='plot')

widget('german').pack(side='left')
widget('english').pack(side='left')
widget('plot').pack(side='left')

### CODE ===================================================

class TestApplication:

    def __init__(self,
                 german = widget('german'),
                 english = widget('english'),
                 plot = widget('plot')
                 ):

        german['command'] = partial(self.send_language,0)
        english['command'] = partial(self.send_language,1)
        plot['command'] = self.test_plot

        subscribe('FIG,AX',self.get_fig_ax)
        publish('REQUEST_FIG,AX')
        
    def get_fig_ax(self,fig,ax):
        self.fig = fig
        self.ax = ax
        
    def send_language(self,index):
        language_lists = (
            ( 'Sprache',('deutsch','german'),None,('englisch','english'),('französisch','french')),
            ( 'Language',('english','english'),None,('french','french'),('german','german'))
        )
        publish('MENU_LANGUAGE',language_lists[index])


    def test_plot(self):
        self.ax.imshow(np.random.normal(0.,1.,size=[1000,1000]),cmap="hot",aspect="auto")
        self.fig.canvas.draw()


TestApplication()
            
### ========================================================
Man beachte insbesonders:

Code: Alles auswählen

        subscribe('FIG,AX',self.get_fig_ax)
        publish('REQUEST_FIG,AX')
        
    def get_fig_ax(self,fig,ax):
        self.fig = fig
        self.ax = ax
Dabei ist zu beachten, dass das nur die richtige Implementierung für ein zur Laufzeit erzeugtes Objekt handelt. das Toplevel wird erst angelegt, wenn der User auf einen Button drückt.

Für ein Objekt, das bereits während der Initialisierung angelegt wird, muss man es anders tun, da nicht sicher ist, ob der Empfänger schon angelegt wurde oder nicht.

Da wäre dann am Ende der Initialisierung der Hauptanwendung von der Hauptanwendung aus 'REQUEST_FIG,AX' zu senden!

PS: Das ist jetzt wieder auf GitHub
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Es bleibt noch anzumerken, dass bei onMouseMove ein self.canvas.show() gefehlt hatte:

Code: Alles auswählen

    def onMouseMove(self, event):

        self.ax.lines = [self.ax.lines[0]]
        if event.xdata and event.ydata: # python3 often has None
            self.ax.axhline(y=event.ydata, color="k") #(y=event.ydata, color="k")
            self.ax.axvline(x=event.xdata, color="k") #(x=event.xdata, color="k")   
        self.canvas.show()
Damit dürfte dann dieses Thema erledigt sein
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Durch eine zusätzliche Option 'call Code(self)' kann man jetzt beim Export auch Funktions oder Klassenaufrufe zusätzlich generieren. Die dabei erzeugte Gui kann man dadurch vom Code in anderen Modulen trennen und den Code zur Gui in beliebigen Modulen haben. Der Export überschreibt dadurch nicht den Code zur Gui.

So sieht dann die Gui aus:

appgui.py

Code: Alles auswählen

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

import appcode

class BOStrab_Fahrzeugeinschraenkung(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        # widget definitions ===================================
        self.MainMenu = MenuGUI(self)
        self['menu'] = self.MainMenu
        self.plotframe = PlotFrame(self,text='Madplotlib')
        self.plotframe.pack()

class MenuGUI(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.language_submenu = LanguageSubmenu(self,tearoff=0)
        self.add_cascade(menu=self.language_submenu,label='Sprachen')
        self.add_command(label='Test')
        # indexes for entryconfig later
        self.languages_index = 1
        self.test_index = 2
        # call Code ===================================
        appcode.MenuGUI(self)

class LanguageSubmenu(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.add_command(label='deutsch')
        # indexes for entryconfig later
        self.deutsch_index = 0
        # call Code ===================================
        appcode.LanguageSubmenu(self)

class PlotFrame(tk.LabelFrame):

    def __init__(self,master,**kwargs):
        tk.LabelFrame.__init__(self,master,**kwargs)
        # call Code ===================================
        appcode.PlotFrame(self)

BOStrab_Fahrzeugeinschraenkung().mainloop()
Und das ist der Code zur Gui:

appcode.py

Code: Alles auswählen

from appimports import *
import testgui

def MenuGUI(self):

    def test():
        testgui.TestGui(self)

    # for testing
    self.entryconfig(self.test_index,command = test)
    
    def receive_language(languages):
        
        self.entryconfig(self.languages_index,label = languages[0]) 
        publish('SUBMENU_LANGUAGES',languages[1:])

    subscribe('MENU_LANGUAGE',receive_language)

def LanguageSubmenu(self):
           

    def do_action(language):
        publish("SELECT_LANGUAGE",language)

    def create_menu(languages):

        self.add_checkbutton()

        # now we delete the menu entries after deutsch
        after_deutsch = self.deutsch_index + 1
        while True:
            itemtype = self.type(after_deutsch)
            self.delete(after_deutsch)
            if itemtype == 'checkbutton':
                break
 
        # now we make a dynamic creation
        # first we get the style of the first command
        # this style should also be used for the other commands

        command_config = get_entryconfig(self,self.deutsch_index)
        
        # now we create dynamic commands

        # Example
        # languages = (('Deutsch','german'),None,('English','english'),('Spanisch','spanish'),('Franzoesisch','french'))

        is_not_end = True
        for index,language in enumerate(languages):

            if not language:
                self.add_separator()
                continue

            command_config['label'] = language[0]
            command_config['command'] = partial(do_action,language[1])
               
            if is_not_end:
                try:
                    self.entryconfig(index+self.deutsch_index,**command_config)
                except (IndexError,tk.TclError):
                    is_not_end = False
                    self.add_command(**command_config)
            else:
                self.add_command(**command_config)
                 
                 
    subscribe('SUBMENU_LANGUAGES',create_menu)

def PlotFrame(self):

    self.fig = Figure (figsize=(5,4), dpi=100)
    self.ax = self.fig.add_subplot(111)
    self.ax = self.fig.add_subplot(111) #fuer 2d-Plot
    self.ax.set_title('Definition der LRUGL')
    self.ax.set_xlabel('Breite y [mm]')
    self.ax.set_ylabel('Hoehe z [mm]')
    self.ax.axis([-2000,2000,0, 5000])
    self.ax.grid(True)

    self.canvas = FigureCanvasTkAgg(self.fig,self)
    self.toolbar = NavigationToolbar2TkAgg(self.canvas,self)
    self.toolbar.update()
    self.plot_widget = self.canvas.get_tk_widget()
    self.plot_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
    self.toolbar.pack(side=tk.TOP, fill=tk.BOTH, expand=1)

    self.ax.plot(np.zeros(100))

    def onMouseMove(event):

        self.ax.lines = [self.ax.lines[0]]
        if event.xdata and event.ydata: # python3 often has None
            self.ax.axhline(y=event.ydata, color="k") #(y=event.ydata, color="k")
            self.ax.axvline(x=event.xdata, color="k") #(x=event.xdata, color="k")   
        self.canvas.show()

    self.canvas.mpl_connect('motion_notify_event', onMouseMove)
    self.canvas.show()

    def fig_ax_request():
        publish('FIG,AX',self.fig,self.ax)

    subscribe('REQUEST_FIG,AX',fig_ax_request)
Wenn ich in appgui.py nicht das als tk importierte: import DynTkInter as tk
bekam ich im Bereich der Zeilen 59 bis 63 einen unerklärlichen Fehler. Nach dem ersten add erfolge dann plötzlich wieder ein entryconfig und außerdem mußte ich einen tk.TclError hinzufügen. Das Resultat war, dass dann Einträge fehlten. Daher der Workaround mit 'is_not_end'

Wenn man in MenuGui in appcode.py den command mit dem index self.test_index drückt, wird ein Toplevel Window erzeugt mit einem eigenständigen Gui File:

testgui.py

Code: Alles auswählen

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk


import testcode

class TestGui(tk.Toplevel):

    def __init__(self,master,**kwargs):
        tk.Toplevel.__init__(self,master,**kwargs)
        self.title('Testing')
        # widget definitions ===================================
        self.english = tk.Button(self,text='english')
        self.german = tk.Button(self,text='german')
        self.plot = tk.Button(self,text='plot')
        self.german.pack(side='left')
        self.english.pack(side='left')
        self.plot.pack(side='left')
        # call Code ===================================
        testcode.TestGUI(self)
Und das ist der Code dazu:

testcode.py

Code: Alles auswählen

# -*- coding: utf-8 -*-

from functools import partial
from appimports import publish,subscribe,np


def TestGUI(self):

    def send_language(index):
        language_lists = (
            ( 'Sprache',('deutsch','german'),None,('englisch','english'),('französisch','french')),
            ( 'Language',('english','english'),None,('french','french'),('german','german'))
        )
        publish('MENU_LANGUAGE',language_lists[index])


    self.german['command'] = partial(send_language,0)
    self.english['command'] = partial(send_language,1)


    def test_plot():
        self.ax.imshow(np.random.normal(0.,1.,size=[1000,1000]),cmap="hot",aspect="auto")
        self.fig.canvas.draw()

    self.plot['command'] = test_plot

    def get_fig_ax(fig,ax):
        self.fig = fig
        self.ax = ax
        
    subscribe('FIG,AX',get_fig_ax)
    publish('REQUEST_FIG,AX')
Das komplette Beispiel mit den zusätzlichen imports befindet sich auf GitHub: https://github.com/AlfonsMittelmeyer/py ... -messaging

Und zwar im Ordner matlib/

Wenn man keine Parameter außer dem self und den config optionen übergibt, kann man die Gui auch später beliebig umstellen. Und sie auch generieren.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Etwas ist allerdings noch nicht richtig: layout gehört zum entsprechenden Container aber Conig gehört zur ensprechenden Klasse.

Das ist daher nicht ganz richtig:

Code: Alles auswählen

        self.language_submenu = LanguageSubmenu(self,tearoff=0)

# in Verbindung mit

class LanguageSubmenu(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.add_command(label='deutsch')
        # indexes for entryconfig later
        self.deutsch_index = 0
Dass self.deutsch_index 0 ist, kommt von
self.language_submenu = LanguageSubmenu(self,tearoff=0)
aber woanders als in diesem Submenü.

Richtig wäre wohl:

Code: Alles auswählen

        self.language_submenu = LanguageSubmenu(self)

# in Verbindung mit

class LanguageSubmenu(tk.Menu):

    def __init__(self,master,**kwargs):
        kwargs.update({ 'tearoff' : 0 })
        tk.Menu.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.add_command(label='deutsch')
        # indexes for entryconfig later
        self.deutsch_index = 0
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Das war jetzt etwas unsinig:

Code: Alles auswählen

def LanguageSubmenu(self):
           
 
    def do_action(language):
        publish("SELECT_LANGUAGE",language)

 
    def create_menu(languages):

        self.add_checkbutton()
 
        # now we delete the menu entries after deutsch
        after_deutsch = self.deutsch_index + 1
        while True:
            itemtype = self.type(after_deutsch)
            self.delete(after_deutsch)
            if itemtype == 'checkbutton':
                break
 
        # now we make a dynamic creation
        # first we get the style of the first command
        # this style should also be used for the other commands
 
        command_config = get_entryconfig(self,self.deutsch_index)
       
        # now we create dynamic commands
 
        # Example
        # languages = (('Deutsch','german'),None,('English','english'),('Spanisch','spanish'),('Franzoesisch','french'))
 
        is_not_end = True
        for index,language in enumerate(languages):
 
            if not language:
                self.add_separator()
                continue
 
            command_config['label'] = language[0]
            command_config['command'] = partial(do_action,language[1])
               
            if is_not_end:
                try:
                    self.entryconfig(index+self.deutsch_index,**command_config)
                except (IndexError,tk.TclError):
                    is_not_end = False
                    self.add_command(**command_config)
            else:
                self.add_command(**command_config)
                 
                 
    subscribe('SUBMENU_LANGUAGES',create_menu)
Denn wir wissen ja, wieviel Einträge wir zu Beginn haben und wenn wir uns merken, wieviel wir jeweils später haben, dann ist es einfach:

Code: Alles auswählen

class LanguageSubmenu:

    def __init__(self,container):
        self.container = container
        self.deutsch_index = container.deutsch_index

        self.entry_count = self.deutsch_index + 1
        subscribe('SUBMENU_LANGUAGES',self.create_menu)
                  
    
    def do_action(self,language):
        publish("SELECT_LANGUAGE",language)


    def create_menu(self,languages):

        # now we delete the menu entries after deutsch
        after_deutsch = self.deutsch_index + 1
        for entries in range(after_deutsch,self.entry_count):
            self.container.delete(after_deutsch)
            
        # now we make a dynamic creation
        # first we get the style of the first command
        # this style should also be used for the other commands

        command_config = get_entryconfig(self.container,self.deutsch_index)
        
        # now we create dynamic commands

        # Example
        # languages = (('Deutsch','german'),None,('English','english'),('Spanisch','spanish'),('Franzoesisch','french'))

        entry_index = self.deutsch_index - 1 

        for language in languages:
            entry_index += 1

            if not language:
                self.container.add_separator()
                continue

            command_config['label'] = language[0]
            command_config['command'] = partial(self.do_action,language[1])
               
            if entry_index == self.deutsch_index :
                self.container.entryconfig(entry_index,**command_config)
            else:
                self.container.add_command(**command_config)

        self.entry_count = entry_index + 1
Antworten