Matplotlib in Tkinter Gui einbetten

Fragen zu Tkinter.
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