@sedi hier ist doch der Verweis auf das Menü mit dem parent: a = MenuCommand(parent,label="Menutext")
Allerdings wäre das etwas aufwändig, wenn wir auch remove zulassen. Denn dann müssten wir alle config options etwa für checkbuttons merken und die auch im removed zustand ändern lassen.
Man kann es ja am Anfang mal ohne den removed Zustand machen. Dann muss man eben gleich das Menüwidget positionieren und lassen, wo es ist - löschen darf man es natürlich.
Also:
a = MenuCommand(parent,label="Menutext").add()
oder
a = MenuCommand(parent,label="Menutext").insert(x)
Ich werde bei mir aber doch alles implementieren. Dann haben wir eine Gleichbehandlung aller widgets. Und ich brauche das sowieso auch für Stellvertreter Widgets.
Wie es bei Panes für PanedWindows aussieht, muss ich mir auch noch ansehen.
UndoManager mit Tkinter-GUI
Ich habe jetzt mal die Tkintererweiterung ohne die Menüfunktionalität der ``_MenuElement`` - Objekte gemacht:
Es fehlt jetzt noch Tkintermanipulationen in die ``_MenuElement`` - Objekte zu übernehmen...
@Alfons Mittelmeyer: stimmt
Code: Alles auswählen
# PYTHON 3.X
import tkinter as tk
class _MenuElement(object):
"""abstrakte Klasse"""
def __init__(self, menuobject, **atts):
"""
:param menu: {menu object}
:param atts: alle in Tkinter erlaubten Eigenschaften:
label, command, ...
"""
self.menu = menuobject
self._atts = atts
self.label = ""
for a in atts:
self.__dict__[a] = atts[a]
def __str__(self):
return self.__class__.__name__.lower()
@property
def atts(self):
return self._atts
@property
def pos(self):
return self.menu.entries.index(self)
# Typen: 'cascade', 'checkbutton', 'command', 'radiobutton', or 'separator'
# (infohost.nmt.edu/tcc/help/pubs/tkinter/web/menu.html)
class Command(_MenuElement):
pass
class Checkbutton(_MenuElement):
pass
class Radiobutton(_MenuElement):
pass
class Separator(_MenuElement):
pass
class Cascade(_MenuElement):
pass
class Menu(tk.Menu):
def __init__(self, master, label, **kw):
if "tearoff" not in kw: kw["tearoff"] = 0
tk.Menu.__init__(self, master, **kw)
self.label = label
self.entries = []
def add(self, element):
"""Ueberschreiben der add-Methode, da ja jetzt nur noch
Instanzen von Klassen ankommen, die von ``_MenuElement``
abgeleitet wurden.
"""
self.insert(len(self.entries), element)
def insert(self, pos, element):
if not isinstance(element, _MenuElement):
raise ValueError(...)
self.entries.insert(pos, element)
tk.Menu.insert(self, pos, str(element), **element.atts)
# adds ueberschreiben
def add_cascade(self, **kw):
self.add(Cascade(self, **kw))
def add_checkbutton(self, **kw):
self.add(Checkbutton(self, **kw))
def add_command(self, **kw):
self.add(Command(self, **kw))
def add_radiobutton(self, **kw):
self.add(Radiobutton(self, **kw))
def add_separator(self, **kw):
self.add(Separator(self, **kw))
# inserts ueberschreiben
def insert_cascade(self, index, **kw):
self.insert(index, Cascade(self, **kw))
def insert_checkbutton(self, index, **kw):
self.insert(index, Checkbutton(self, **kw))
def insert_command(self, index, **kw):
self.insert(index, Command(self, **kw))
def insert_radiobutton(self, index, **kw):
self.insert(index, Radiobutton(self, **kw))
def insert_separator(self, index, **kw):
self.insert(index, Separator(self, **kw))
# delete ueberschreiben
def delete(self, index1, index2=None):
last = index1
if index2 is not None:
last = index2
for i in range(index1, last+1):
del self.entries[i]
tk.Menu.delete(self, index1, last)
class RootMenu(Menu):
def __init__(self, master, **kw):
Menu.__init__(self, master, "ROOT", **kw)
@Alfons Mittelmeyer: stimmt
@sedi hier ist doch der Verweis auf das Menü mit dem parent: a = MenuCommand(parent,label="Menutext")
CU sedi
----------------------------------------------------------
Python 3.5; Python 3.6
LinuxMint18
----------------------------------------------------------
Python 3.5; Python 3.6
LinuxMint18
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
@sedi Da fehlt noch etwas:
Da musst Du noch tk.Menu.add_checkbutton(self,...) aufrufen
Wenn Du nur überschreibst, dann hast Du ja nichts
Code: Alles auswählen
def add_checkbutton(self, **kw):
self.add(Checkbutton(self, **kw))
Wenn Du nur überschreibst, dann hast Du ja nichts
Zuletzt geändert von Alfons Mittelmeyer am Mittwoch 9. September 2015, 18:26, insgesamt 1-mal geändert.
So nach ein paar ersten Tests scheint der Tkinter-Wrapper zu funktionieren:
Was sagt ihr dazu?
und vor allen Dingen: Wie verbindet man das jetzt mit dem UndoManager?
Code: Alles auswählen
# PYTHON 3.X
import tkinter as tk
class _MenuElement(object):
"""abstrakte Klasse"""
def __init__(self, menuobject, **atts):
"""
:param menu: {menu object}
:param atts: alle in Tkinter erlaubten Eigenschaften:
label, command, ...
"""
self._atts = {}
self.menu = menuobject
self.label = ""
self.atts = atts
def __str__(self):
return self.__class__.__name__.lower()
@property
def pos(self):
return self.menu.entries.index(self)
@property
def atts(self):
return self._atts
@atts.setter
def atts(self, atts):
self._atts.update(atts)
self.__dict__.update(atts)
# Typen: 'cascade', 'checkbutton', 'command', 'radiobutton', or 'separator'
# (infohost.nmt.edu/tcc/help/pubs/tkinter/web/menu.html)
class Command(_MenuElement):
pass
class Checkbutton(_MenuElement):
pass
class Radiobutton(_MenuElement):
pass
class Separator(_MenuElement):
pass
class Cascade(_MenuElement):
pass
Entries = dict([(str(e).capitalize(), e) for e in
[Command, Checkbutton, Radiobutton, Separator, Cascade]])
class Menu(tk.Menu):
def __init__(self, master, label, **kw):
if "tearoff" not in kw: kw["tearoff"] = 0
tk.Menu.__init__(self, master, **kw)
self.label = label
self.entries = []
def add(self, element):
"""Ueberschreiben der add-Methode, da ja jetzt nur noch
Instanzen von Klassen ankommen, die von ``_MenuElement``
abgeleitet wurden.
"""
self.insert(len(self.entries), element)
def insert(self, pos, element):
if not isinstance(element, _MenuElement):
err = "one of '{}' expected! ".format(list(Entries.keys()))
err += "got {} instead".format(str(element).capitalize())
raise ValueError(err)
self.entries.insert(pos, element)
tk.Menu.insert(self, pos, str(element), **element.atts)
# adds ueberschreiben
def add_cascade(self, **kw):
self.add(Cascade(self, **kw))
def add_checkbutton(self, **kw):
self.add(Checkbutton(self, **kw))
def add_command(self, **kw):
self.add(Command(self, **kw))
def add_radiobutton(self, **kw):
self.add(Radiobutton(self, **kw))
def add_separator(self, **kw):
self.add(Separator(self, **kw))
# inserts ueberschreiben
def insert_cascade(self, index, **kw):
self.insert(index, Cascade(self, **kw))
def insert_checkbutton(self, index, **kw):
self.insert(index, Checkbutton(self, **kw))
def insert_command(self, index, **kw):
self.insert(index, Command(self, **kw))
def insert_radiobutton(self, index, **kw):
self.insert(index, Radiobutton(self, **kw))
def insert_separator(self, index, **kw):
self.insert(index, Separator(self, **kw))
# delete ueberschreiben
def delete(self, index1, index2=None):
last = index1
if index2 is not None:
last = index2
for i in range(index1, last+1):
del self.entries[i]
tk.Menu.delete(self, index1, last)
def entryconfigure(self, index, **kw):
entry = self.entries[index]
entry.atts = kw
tk.Menu.entryconfigure(self, index, **kw)
class RootMenu(Menu):
def __init__(self, master, **kw):
Menu.__init__(self, master, "ROOT", **kw)
und vor allen Dingen: Wie verbindet man das jetzt mit dem UndoManager?
CU sedi
----------------------------------------------------------
Python 3.5; Python 3.6
LinuxMint18
----------------------------------------------------------
Python 3.5; Python 3.6
LinuxMint18
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
@sedi bitte noch meinen vorigen post beachten und dann solltest Du erläutern, welche Aktionen rückgängig gemacht und wiederholt werden sollen. Wohl nicht nur Klicks im Menü?
Und undo und redo haben fast gar nichts mit Deinem Menü zu tun, nur dass das vom Menü mit den Menüpunkten 'undo' und 'redo' aufgerufen wird.
Und undo und redo haben fast gar nichts mit Deinem Menü zu tun, nur dass das vom Menü mit den Menüpunkten 'undo' und 'redo' aufgerufen wird.
@Alfons Mittelmeyer:Alfons Mittelmeyer hat geschrieben:@sedi bitte noch meinen vorigen post beachten und dann solltest Du erläutern, welche Aktionen rückgängig gemacht und wiederholt werden sollen. Wohl nicht nur Klicks im Menü?
1) zum vorigen Post: bei mir funktioniert das so wie es ist - in ``add_XY`` wird das Objekt erzeugt, dann an die ``add`` weitergereicht und letztlich in der ``insert`` erzeugt. Tkinterspezifische Funktion wird ja in der ``insert`` aufgerufen - das genügt.
2) Das mit dem UndoManager habe ich noch gar nicht in angriff genommen - mein Gedanke ist so etwas in der Art wie ein ``AppCommand``, der drei Methoden ``do``, ``undo`` und ``redo`` implementieren muss, so wie ich es bereits weiter oben angedeutet habe (siehe auch UndoManager Thread http://www.python-forum.de/viewtopic.ph ... 37#p283437)
Will man nun einen Befehl ausführen, der den Undo-Mechanismus benützen soll, dann muss halt ein Objekt vom ``AppCommand`` erstellt werden.
CU sedi
----------------------------------------------------------
Python 3.5; Python 3.6
LinuxMint18
----------------------------------------------------------
Python 3.5; Python 3.6
LinuxMint18
@sedi: Was ist denn der Unterschied zwischen `do()` und `redo()`? Ein Beispiel wo die nicht beide das gleiche machen?
Qt erlaubt auch so einem Kommando andere Kommandos als Kinder mit zu geben, so dass man aus einfachen Kommandos komplexere zusammenbauen kann die man dann gemeinsam anwenden oder rückgängig machen kann. Und es gibt einen Mechanismus bei dem man Kommandos ”komprimieren” kann in dem mehrere vom gleichen ”Typ” zu einem verschmolzen werden können.
Qt erlaubt auch so einem Kommando andere Kommandos als Kinder mit zu geben, so dass man aus einfachen Kommandos komplexere zusammenbauen kann die man dann gemeinsam anwenden oder rückgängig machen kann. Und es gibt einen Mechanismus bei dem man Kommandos ”komprimieren” kann in dem mehrere vom gleichen ”Typ” zu einem verschmolzen werden können.
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
@BlackJack do() und redo() müssen ausführen. Redo entnimmt, was zu tun ist aus der redo Liste und muss nicht unbedingt wieder in diese Liste schreiben, weil es ja schon drin ist.
Außerdem ist auch nicht nötig, dass dafür eine undo Funktion nochmals erfasst wird, die ist ja auch schon in der undo Liste drin. Bei redo() muss der index beider Listen erhöht werden.
do() bekommt übergeben, was zu tun ist, schreibt es in die redo liste und muss mit diesem Eintrag auch das Ende der redo und undo liste kennzeichnen. Und natürlich wird was zu tun ist ausgeführt.
Außerdem muß bei do() auch die redo Aktion erzeugt - aber nicht ausgeführt - und in die redo Liste geschrieben werden. Normalerweise übernimmt die auszuführende Aktion das Schreiben in die redo liste, denn nur die kann wissen, was tun ist, oder es muss ein tuple aus Aktion und Undo Aktion existieren.
Bei undo() wird das undo Kommando aus der undo liste ausgeführt und der Index beider Listen um eins erniedrigt.
Außerdem ist auch nicht nötig, dass dafür eine undo Funktion nochmals erfasst wird, die ist ja auch schon in der undo Liste drin. Bei redo() muss der index beider Listen erhöht werden.
do() bekommt übergeben, was zu tun ist, schreibt es in die redo liste und muss mit diesem Eintrag auch das Ende der redo und undo liste kennzeichnen. Und natürlich wird was zu tun ist ausgeführt.
Außerdem muß bei do() auch die redo Aktion erzeugt - aber nicht ausgeführt - und in die redo Liste geschrieben werden. Normalerweise übernimmt die auszuführende Aktion das Schreiben in die redo liste, denn nur die kann wissen, was tun ist, oder es muss ein tuple aus Aktion und Undo Aktion existieren.
Bei undo() wird das undo Kommando aus der undo liste ausgeführt und der Index beider Listen um eins erniedrigt.
@Alfons Mittelmeyer: Du verwechselst da den `UndoManager` mit dem `AppCommand`. Auf letzteres bezog sich meine Frage weil sedi schrieb diese Objekte hätten diese drei Methoden.
Szenario: Bei einem Dokument muss ein Inhaltselement neu erstellt werden, dies geschieht im ``do``. Im ``redo`` wird es nicht neu erstellt, sondern man nimmt das bereits Erstellte!BlackJack hat geschrieben:@sedi: Was ist denn der Unterschied zwischen `do()` und `redo()`? Ein Beispiel wo die nicht beide das gleiche machen
Zur Klärung: Sowohl der ``AppCommand`` als auch der ``UndoManager`` sollten alle drei Methoden ``do``, ``undo`` und ``redo`` implementieren, da es Fälle geben kann, wo sich ``do`` und ``redo`` unterscheiden:
Code: Alles auswählen
class AppCommand:
def do(self): pass
def undo(self): pass
def redo(self): pass
class UndoManager:
def __init__(self):
self.undos, self.redos = [], []
def undo(self):
command = self.undos.pop()
self.redos.append(command)
command.undo()
def redo(self):
command = self.redos.pop()
self.undos.append(command)
command.redo()
def execute(self, command):
self.undos.append(command)
self.redos = []
# hier nicht ``redo`` sondern ``do``!
command.do()
Code: Alles auswählen
class _MenuElement(object):
"""abstrakte Klasse"""
def __init__(self, menuobject, **atts):
"""Die Klassen sollten nicht direkt initialisiert
werden, sondern ueber deren Menuemethoden ``add_XY``!
Den ``_MenuElement`` - Objekten kann jederzeit ein
Wert fuer den ``label`` zugewiesen werden, aber den
Tkinterattributen wird der Wert nur dann zugewiesen,
wenn bei der Initialisierung ein 'label'-Argument
uebergeben wurde!
:param menu: {menu object}
:param atts: alle in Tkinter erlaubten Eigenschaften:
label, command, ...
"""
self._atts = {}
# Label kann immer gesetzt werden, aber nicht immer ist
# es ein erlaubte Tkintereigenschaft -> Property
self._label = ""
self.menu = menuobject
self.atts = atts
def __str__(self):
return self.__class__.__name__.lower()
@property
def pos(self):
return self.menu.entries.index(self)
@property
def atts(self):
return self._atts
@atts.setter
def atts(self, atts):
self._atts.update(atts)
if "label" in atts:
self._label = atts.pop("label")
self.__dict__.update(atts)
@property
def label(self):
return self._label
@label.setter
def label(self, value):
self._label = value
if "label" in self._atts:
self._atts["label"] = value
def update(self, **atts):
self.atts = atts
self.menu.entryconfigure(self.pos, **self._atts)
# Typen: 'cascade', 'checkbutton', 'command', 'radiobutton', or 'separator'
# (infohost.nmt.edu/tcc/help/pubs/tkinter/web/menu.html)
class Command(_MenuElement):
pass
class Checkbutton(_MenuElement):
pass
class Radiobutton(_MenuElement):
pass
class Separator(_MenuElement):
pass
class Cascade(_MenuElement):
pass
Entries = dict([(str(e).capitalize(), e) for e in
[Command, Checkbutton, Radiobutton, Separator, Cascade]])
class Menu(tk.Menu):
def __init__(self, master, label, **kw):
if "tearoff" not in kw: kw["tearoff"] = 0
tk.Menu.__init__(self, master, **kw)
self.label = label
self.entries = []
def add(self, element):
"""Ueberschreiben der add-Methode, da ja jetzt nur noch
Instanzen von Klassen ankommen, die von ``_MenuElement``
abgeleitet wurden.
"""
self.insert(len(self.entries), element)
def insert(self, pos, element):
if not isinstance(element, _MenuElement):
err = "one of '{}' expected! ".format(list(Entries.keys()))
err += "got {} instead".format(str(element).capitalize())
raise ValueError(err)
self.entries.insert(pos, element)
tk.Menu.insert(self, pos, str(element), **element.atts)
# adds ueberschreiben
def add_cascade(self, **kw):
self.add(Cascade(self, **kw))
def add_checkbutton(self, **kw):
self.add(Checkbutton(self, **kw))
def add_command(self, **kw):
self.add(Command(self, **kw))
def add_radiobutton(self, **kw):
self.add(Radiobutton(self, **kw))
def add_separator(self, **kw):
self.add(Separator(self, **kw))
# inserts ueberschreiben
def insert_cascade(self, index, **kw):
self.insert(index, Cascade(self, **kw))
def insert_checkbutton(self, index, **kw):
self.insert(index, Checkbutton(self, **kw))
def insert_command(self, index, **kw):
self.insert(index, Command(self, **kw))
def insert_radiobutton(self, index, **kw):
self.insert(index, Radiobutton(self, **kw))
def insert_separator(self, index, **kw):
self.insert(index, Separator(self, **kw))
# delete ueberschreiben
def delete(self, index1, index2=None):
last = index1
if index2 is not None:
last = index2
for i in range(index1, last+1):
del self.entries[i]
tk.Menu.delete(self, index1, last)
def entryconfigure(self, index, **kw):
entry = self.entries[index]
entry.atts = kw
tk.Menu.entryconfigure(self, index, **entry.atts)
# und hier die Komfortzone ;)
def get_entry_by_fnmatch(self, entryname):
return [e for e in self.entries
if fnmatch(e.label, entryname)]
class RootMenu(Menu):
def __init__(self, master, **kw):
Menu.__init__(self, master, "ROOT", **kw)
CU sedi
----------------------------------------------------------
Python 3.5; Python 3.6
LinuxMint18
----------------------------------------------------------
Python 3.5; Python 3.6
LinuxMint18
@sedi: Das geht auch ohne die Methode in dem man das Element einfach cached und beim `redo()` (als einzige Methode) prüft ob bereits ein gecachetes Element existiert oder man ein neues erstellen muss. Also ungefähr so:
Falls es beides, `do()` und `redo()` gibt, würde ich in der Basisklasse aber zumindest `redo()` so implementieren das es `do()` aufruft, damit man `redo()` nur implementieren muss wenn man das wirklich braucht.
Code: Alles auswählen
class CreateElementCommand(Command):
def __init__(self, description, document):
self.description = description
self.document = document
self.element = None
self.position = None
def redo(self):
if self.element is None:
self.position = self.document.get_current_position()
self.element = self.document.create_element()
else:
self.document.insert_element(self.position, self.element)
def undo(self):
self.document.delete_element(self.position)
Habe nun einen ersten Entwurf des UndoManagers fertig, musste aber die Tkintermenüerweiterung etwas anpassen!
Die Tkintermenüerweiterung:
Der UndoManager
Und hier ein kleiner Test zu den beiden
Die Tkintermenüerweiterung:
Code: Alles auswählen
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
from fnmatch import fnmatch
# PYTHON 3.X
import tkinter as tk
Logger = logging.getLogger("tkme")
class _MenuElement(object):
"""abstrakte Klasse"""
def __init__(self, menuobject, **atts):
self._atts = {}
# Label kann immer gesetzt werden, aber nicht immer ist
# es ein erlaubte Tkintereigenschaft -> Property
self._label = ""
self.menu = menuobject
self.atts = atts
def __str__(self):
return self.__class__.__name__.lower()
@property
def pos(self):
return self.menu.entries.index(self)
@property
def atts(self):
return self._atts
@atts.setter
def atts(self, atts):
self._atts.update(atts)
if "label" in atts:
self._label = atts.pop("label")
self.__dict__.update(atts)
@property
def label(self):
return self._label
@label.setter
def label(self, value):
self._label = value
if "label" in self._atts:
self._atts["label"] = value
def update(self, **atts):
self.atts = atts
self.menu.entryconfigure(self.pos, **self._atts)
# Typen: 'cascade', 'checkbutton', 'command', 'radiobutton', or 'separator'
# (infohost.nmt.edu/tcc/help/pubs/tkinter/web/menu.html)
class Command(_MenuElement):
pass
class Checkbutton(_MenuElement):
pass
class Radiobutton(_MenuElement):
pass
class Separator(_MenuElement):
pass
class Cascade(_MenuElement):
pass
Entries = dict([(str(e).capitalize(), e) for e in
[Command, Checkbutton, Radiobutton, Separator, Cascade]])
class _Menu(tk.Menu):
def __init__(self, master, label, **kw):
if "tearoff" not in kw: kw["tearoff"] = 0
tk.Menu.__init__(self, master, **kw)
self.label = label
self.entries = []
# Die Tkintermethoden ``add`` und ``insert`` ganz
# bewusst mit verschiedener Signatur ueberschreiben
def add(self, element):
return self.insert(len(self.entries), element)
def insert(self, pos, element):
if not isinstance(element, _MenuElement):
err = "one of '{}' expected! ".format(list(Entries.keys()))
err += "got {} instead".format(str(element).capitalize())
raise ValueError(err)
self.entries.insert(pos, element)
tk.Menu.insert(self, pos, str(element), **element.atts)
return element
# adds ueberschreiben
def add_cascade(self, **kw):
return self.add(Cascade(self, **kw))
def add_checkbutton(self, **kw):
return self.add(Checkbutton(self, **kw))
def add_command(self, **kw):
return self.add(Command(self, **kw))
def add_radiobutton(self, **kw):
return self.add(Radiobutton(self, **kw))
def add_separator(self, **kw):
return self.add(Separator(self, **kw))
# inserts ueberschreiben
def insert_cascade(self, index, **kw):
return self.insert(index, Cascade(self, **kw))
def insert_checkbutton(self, index, **kw):
return self.insert(index, Checkbutton(self, **kw))
def insert_command(self, index, **kw):
return self.insert(index, Command(self, **kw))
def insert_radiobutton(self, index, **kw):
return self.insert(index, Radiobutton(self, **kw))
def insert_separator(self, index, **kw):
return self.insert(index, Separator(self, **kw))
# delete ueberschreiben
def delete(self, index1, index2=None):
last = index1
if index2 is not None:
last = index2
for i in range(index1, last+1):
del self.entries[i]
tk.Menu.delete(self, index1, last)
# Eintraege aendern
def entryconfigure(self, index, **kw):
entry = self.entries[index]
entry.atts = kw
tk.Menu.entryconfigure(self, index, **entry.atts)
return entry
change = entryconfigure
class Menu(_Menu):
def __init__(self, master, label, **kw):
_Menu.__init__(self, master, label, **kw)
def install(self, **kw):
kw["menu"] = self
kw["label"] = self.label
self.master.add_cascade(**kw)
###
# Auslagern der **Komfortzone** in eine eigene Klasse
class FnmatchMenu(Menu):
def __init__(self, master, label, **kw):
Menu.__init__(self, master, label, **kw)
def get_entries_by_fnmatch(self, entryname):
return [e for e in self.entries
if fnmatch(e.label, entryname)]
class RootMenu(_Menu):
def __init__(self, master, **kw):
_Menu.__init__(self, master, "ROOT", **kw)
def install(self):
tl = self.master.winfo_toplevel()
tl.config(menu=self)
Code: Alles auswählen
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
import enhancetkmenu as mnu
Logger = logging.getLogger("undomanager")
class AppCommand(object):
"""Jedes Kommando, dass den Undomanager benuetzt muss ein
``AppCommand`` sein. Von diesem muessen minimal die beiden
Methoden ``do`` und ``undo`` implementiert werden!
"""
def __init__(self, undomanager, info):
self.mngr = undomanager
self.info = str(info)
Logger.debug("AppCommand %s init done", self.info)
def do(self):
raise NotImplementedError("to implement in subclass")
def undo(self):
raise NotImplementedError("to implement in subclass")
def redo(self):
self.do()
Logger.debug("AppCommand %s redo done", self.info)
def __call__(self):
self.mngr.do(self)
class UndoManager(object):
def __init__(self, menu_undo=None, menu_redo=None):
"""
:param menu_undo: {mnu.Command object}
:param menu_redo: {mnu.Command object}
"""
self.menu_undo = None
self.menu_redo = None
self.undos = []
self.redos = []
self.setup(menu_undo, menu_redo)
Logger.debug("UndoManager init done")
def setup(self, menu_undo, menu_redo):
"""
:param menu_undo: {mnu.Command object}
:param menu_redo: {mnu.Command object}
"""
if menu_undo is not None and isinstance(menu_undo, mnu.Command):
self.menu_undo = menu_undo
if menu_redo is not None and isinstance(menu_redo, mnu.Command):
self.menu_redo = menu_redo
Logger.debug("UndoManager setup done")
def check_menu(self):
btn = self.menu_undo
if len(self.undos) > 0:
cmd = self.undos[-1]
btn.menu.change(btn.pos,
state="normal",
label="Rückgängig ({})".format(cmd.info),
command=self.undo)
else:
btn.menu.change(btn.pos,
state="disabled",
label="Rückgängig",
command=None)
btn = self.menu_redo
if len(self.redos) > 0:
cmd = self.redos[-1]
btn.menu.change(btn.pos,
state="normal",
label="Wiederholen ({})".format(cmd.info),
command=self.redo)
else:
btn.menu.change(btn.pos,
state="disabled",
label="Wiederholen",
command=None)
Logger.debug("UndoManager check_menu done")
def undo(self):
command = self.undos.pop()
command.undo()
self.redos.append(command)
self.check_menu()
Logger.debug("UndoManager undo of %s done", command.info)
def redo(self):
command = self.redos.pop()
command.redo()
self.undos.append(command)
self.check_menu()
Logger.debug("UndoManager redo of %s done", command.info)
def do(self, command):
Logger.debug("=== UndoManager: start command %s ===", command.info)
command.do()
self.undos.append(command)
self.redos = []
self.check_menu()
Logger.debug("UndoManager do of %s done", command.info)
Code: Alles auswählen
import logging
import enhancetkmenu as mnu
from undomanager import AppCommand, UndoManager
Logger = logging.getLogger("root")
class TestApp(mnu.tk.Frame):
def __init__(self, parent):
"""
:param args: {list}
:param kwargs: {dict}
"""
mnu.tk.Frame.__init__(self, parent, height=100, width=500, bg="red")
self.menu = None
self.view = None
self.undomanager = UndoManager()
self.setup()
Logger.debug("TestApp init done")
def setup(self):
self.setup_view()
self.setup_menu()
self.undomanager.setup(
self.menu.bearbeiten.undo,
self.menu.bearbeiten.redo)
Logger.debug("TestApp setup done")
def setup_view(self):
self.view = mnu.tk.StringVar()
mnu.tk.Label(
self,
bg="red",
height=5,
width=100,
textvariable=self.view).pack(expand=1, fill="x")
self.view.set(value="Aktionsbeschreibung")
Logger.debug("TestApp setup_view done")
def setup_menu(self):
self._setup_normal_menu()
self._setup_undo_menu()
Logger.debug("TestApp setup_menu done")
# === Protected ===
def _setup_normal_menu(self):
toplevel = self.winfo_toplevel()
class Callback(object):
"""
einfacher, aufrufbarer Callback - um Informationen
ueber das gesamte Menue ausgeben zu koennen.
"""
def __init__(self, info, mnu):
self.mnu = mnu
self.info = info
def __call__(self, *args, **kwargs):
fmt = " - {} [{}]"
print("=== Menü: {} ===".format(self.mnu.label))
for entry in self.mnu.entries:
print(str(entry).capitalize() + ":")
for att in entry.atts:
print(fmt.format(entry.atts[att], att))
###
# Menu Root
mn = self.menu = mnu.RootMenu(toplevel)
self.menu.install()
mn = mn.datei = mnu.FnmatchMenu(self.menu, "Datei")
mn.install()
mn.add_command(label="Schließen", command=toplevel.quit)
###
# Menu Bearbeiten
mn = self.menu.bearbeiten = mnu.FnmatchMenu(self.menu, "Bearbeiten")
mn.install()
for nr in range(5):
txt = "Bearbeiten " + str(nr)
mn.add_command(label=txt, command=Callback(txt, mn))
# Insert testen
self.menu.bearbeiten.undo = mn.insert_command(0,
label="Rückgängig",
state="disabled")
self.menu.bearbeiten.redo = mn.insert_command(1,
label="Wiederholen",
state="disabled")
# Separator
mn.add_separator()
# Untermenue klassisch erstellt ueber cascade
umn = mn.untermenue = mnu.FnmatchMenu(self.menu, "Untermenü")
mn.add_cascade(label="Untermenü", menu=umn)
for nr in range(5):
txt = "Untermenü " + str(nr)
umn.add_command(label=txt, command=Callback(txt, umn))
###
# Menue checkbuttons
mn = self.menu.check = mnu.FnmatchMenu(self.menu, "Checkbuttons")
mn.install()
opts = {"label": "",
"onvalue": 1,
"offvalue": 0,
"variable": None,}
for nr in range(5):
info = "chkbtn " + str(nr)
opts["label"] = info
opts["variable"] = mnu.tk.BooleanVar()
opts["command"] = Callback(info, mn)
mn.add_checkbutton(**opts)
###
# Menue radiobuttons
mn = self.menu.radio = mnu.FnmatchMenu(self.menu, "Radiobuttons")
mn.install()
# Radiobuttons benoetigen genau **eine** Variable,
# ueber die der Status der Radiobuttens abgefragt
# werden kann. Diese muss deshalb vorher erzeugt
# werden
opts = {"variable": mnu.tk.StringVar()}
for nr in range(5):
info = "Radio " + str(nr)
opts["label"] = info
opts["value"] = str(nr)
opts["command"] = Callback(info, mn)
mn.add_radiobutton(**opts)
Logger.debug("TestApp _setup_normal_menu done")
def _setup_undo_menu(self):
mnuundo = mnu.FnmatchMenu(self.menu, "UndoManager")
mnuundo.install()
###
# Aktionen
class _MenAction(AppCommand):
def __init__(self, app, info):
AppCommand.__init__(self,
undomanager=app.undomanager,
info=info)
self.view = app.view
def do(self):
self.view.set(value="do: {}".format(self.info))
def undo(self):
self.view.set(value="undo: {}".format(self.info))
def redo(self):
self.view.set(value="redo: {}".format(self.info))
def create_action(nr):
txt = "Aktion " + str(nr)
class Action(_MenAction):
def __init__(self, app=self, info=txt):
_MenAction.__init__(self, app, info=info)
mnuundo.add_command(label=txt, command=Action())
for i in range(5):
create_action(i)
Logger.debug("TestApp _setup_undo_menu done")
def test():
logging.basicConfig(level=logging.DEBUG,
name="root",
propagate=1)
# GUI fuer den Test erstellen
root = mnu.tk.Tk()
root.title("neues Menü")
app = TestApp(root)
app.pack()
app.mainloop()
test()
CU sedi
----------------------------------------------------------
Python 3.5; Python 3.6
LinuxMint18
----------------------------------------------------------
Python 3.5; Python 3.6
LinuxMint18