wx.Menu - Unterscheidung verschiedener Einträge

Plattformunabhängige GUIs mit wxWidgets.
Antworten
hmueller
User
Beiträge: 39
Registriert: Montag 27. November 2006, 16:07
Wohnort: Linz, Oberösterreich

Montag 27. November 2006, 16:32

Hallo,

Ich versuche gerade, für mein Programm ein "Zuletzt geöffnete Dateien" Untermenü umzusetzen.
Die Liste wird beim Programmstart geladen, soweit geht's mal.
Ich kann die einzelnen Einträge auch anzeigen, aber wie unterscheide ich dann im Eventhandler, welche Datei nun geöffnet werden soll? Hat da jemand eine elegante Lösung?

Code: Alles auswählen

    def createRecentMenu(self):
        menu = wx.Menu()
        recentlist = self.settings.getRecent() #lädt die Liste der Dateien
        if len(recentlist) == 0:
            item = menu.Append(-1, "no recent files", "no recent files")
            item.Enable(False)
        for filepath in reversed(recentlist):
            i = menu.Append(-1, filepath, "")
            self.Bind(wx.EVT_MENU, self.OnOpenRecent, i)
        return menu
JR
User
Beiträge: 286
Registriert: Montag 20. Februar 2006, 16:43
Wohnort: Berlin

Montag 27. November 2006, 19:55

Hi hmueller!

Habe mir zwar deinen Code jetzt nicht groß angeschaut, verrate dir aber auch warum.
wxPython bietet nämlich das Objekt wx.FileHistory()

Ich sage mir immer "Weshalb das Rad neu erfinden :-)

Guckst du hier:
1)
In der wxPython Demo unter "Miscellaneous -> FileHistory"

oder

2)
http://www.wxpython.org/onlinedocs.php
unter Alphabetical class reference -> wxFileHistory

Viel Glück!
JR
hmueller
User
Beiträge: 39
Registriert: Montag 27. November 2006, 16:07
Wohnort: Linz, Oberösterreich

Montag 27. November 2006, 22:31

Whoops, danke. Ich sollte mir wirklich diese Demos öfter ansehen, wenn ich versuche was zu lösen!
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Montag 27. November 2006, 23:38

EDIT: Ok, hab mich vertan.

die wxConfig Klasse scheint ja auch Super zu sein. Hatte mir extra selber ein config klasse gebaut, aber wie ich sehe ist das sogar noch besser. Muss ich mir mal genauer ansehen.
hmueller
User
Beiträge: 39
Registriert: Montag 27. November 2006, 16:07
Wohnort: Linz, Oberösterreich

Dienstag 28. November 2006, 09:28

Danke für den Tipp, das Problem ist nur, dass wir in unserem Programm schon eine config haben, in der andere Daten auch gespeichert werden.

Um das zu lesen, verwenden wir nicht wxConfig, sondern die "Erweiterung" hier: http://www.cs.wisc.edu/~param/software/cfgparse/
Das hat den Vorteil, dass die Reihenfolge und vor allem auch Kommentare erhalten bleiben.

Ich habe eine Lösung gebastelt, die beinahe das kann, was FileHistory auch bietet. Einzig das Menü ist (noch) nicht so dynamisch, sondern wird nur einmal initialisiert.

Meine Erfahrung rät mir allerdings, das Rad nicht neu zu erfinden und mich mit einer 2. Config Datei für die FileHistory abzufinden.

Gibt es noch irgendwelche Tipps zur wxFileHistory? das mit Load() und Save() hätte ich beinahe übersehen... :oops:
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Dienstag 28. November 2006, 09:52

Ja würde ich auch machen ne 2te Config Datei anzulegen für 'wxFileHistory()'. Naja, hängt aber auch davon ab ob du schon nen wrapper für die config klasse aus deinem link geschrieben hast.

Ich bin erst durch den Thread hier darauf aufmerksam geworden das wx selber was mitbringt für INI-Dateien. Leider zu spät, da ich schon nen Wrapper geschrieben habe der gut läuft und auch berücksichtigt ob der wert in einer INI, zu einer Variablen, auch vom Richtigen Datentype ist ;) Falls einer z.B. in der INI für z.B. 'window_size' nen string statt nen tuple Wert schreibt. Das würde dann zum beispiel, wenn ich nen Frame initialisieren will, ne exception werfen. In Grunde kümmert sich die Klasse (Wrapper) darum dass die Werte in den richtigen Datentype Konvertiert werden. Falls die Konvertierung fehl schlägt wird der entsprechende wert auf default gesetzt und gibt nen log aus (ohne Exception!!), den man sich anzeigen lassen kann oder auch nicht. Somit wird die Initialisierung nciht gestört udn der User erhält auch kein unnötigen Traceback mit den er höchst wahrscheinlich nichts anfangen kann ;)

Ich denke mal das wxConfig nicht so flexible ist(?). z.B. gibt ja config.ConfigParser alles als string zurück, obwohl jede option in der section von einem bestimmten type sein soll. Man muss sich dann selber um ne Konvertierung kümmern, wenn man nen wert von ne option hohlt. Ich denke das sollte auch bei xwConfig nicht anders sein. Daher für mich zu unflexible.

Was ich damit sagen will ist, kuck dir erstmal an wie leistungsfähig die Klasse überhaupt ist von wxConfig und wxFileHistory. Wenn es nicht deinen Anforderungen entspricht, ist es mMn durchaus legitim das Rad neu zu erfinden, wenn das Rad dadurch noch runder wird! ;)

my 2 cents
hmueller
User
Beiträge: 39
Registriert: Montag 27. November 2006, 16:07
Wohnort: Linz, Oberösterreich

Dienstag 28. November 2006, 09:56

XtraNine hat geschrieben:Ich bin erst durch den Thread hier darauf aufmerksam geworden das wx selber was mitbringt für INI-Dateien. Leider zu spät, da ich schon nen Wrapper geschrieben habe der gut läuft und auch berücksichtigt ob der wert in einer INI, zu einer Variablen, auch vom Richtigen Datentype ist ;)
schau dir mal http://docs.python.org/lib/module-ConfigParser.html an, der bringt nette validierungssachen mit, ist allerdings nicht mehr 100% wxConfig kompatibel.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Dienstag 28. November 2006, 10:25

Hi!

``wx.Config()`` schreibt unter Windows alle Einstellungen in die Registry und nicht in eine INI-Datei. Was ``wx.Config()`` für mich in fast allen Fällen disqualifiziert. Ich will die Registry nicht vollmüllen und so einfach sichern lässt sie sich auch nicht.

Der ``ConfigParser`` http://docs.python.org/lib/module-ConfigParser.html ist zum Lesen und Schreiben von INI-Dateien gedacht. Ob er Kommentare unangetastet lässt? Kein Ahnung.

Unter Windows gibt es mehrere Ordner, die für die Einstellungen eines Benutzers zuständig sind. Es gibt deshalb mehrere, da man Windows so einrichten kann, dass Benutzer auf mehreren Computern mit ihren persönlichen Einstellungen arbeiten können.
--> Wandernde Benutzer.

``GetUserDataDir()`` gibt den Pfad zu den Einstellungen des Benutzers zurück. Dieser Ordner steht wandernden Benutzern immer zur Verfügung -- auf jedem Computer.

``GetUserLocalDataDir()`` gibt den Pfad zu den Einstellungen des Benutzers zurück, die nur auf dem lokalen Computer gelten. Dieser Ordner bleibt auf dem aktuellen Computer und wird nicht an andere Computer weitergereicht, wenn sich der Benutzer auf einem anderen Computer anmeldet. Ideal zum Zwischenspeichern von Fensterpositionen und - größen.

Die Ideallösung für Einstellungen: Mit ``wx.StandardPaths`` den Pfad zum korrekten Ordner für die Einstellungen ermitteln und dann dort eine INI-Datei ablegen.

Code: Alles auswählen

>>> wx.GetApp().SetAppName("AppName")
>>> sp = wx.StandardPaths.Get()
>>> sp.GetUserDataDir()
u'J:\\Dokumente und Einstellungen\\Gerold\\Anwendungsdaten\\AppName'
>>> sp.GetUserLocalDataDir()
u'J:\\Dokumente und Einstellungen\\Gerold\\Lokale Einstellungen\\Anwendungsdaten\\AppName'
>>> 
Welche Pfade unter Linux ausgegeben werden, das kann ich im Moment nicht testen.

lg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
hmueller
User
Beiträge: 39
Registriert: Montag 27. November 2006, 16:07
Wohnort: Linz, Oberösterreich

Dienstag 28. November 2006, 11:27

Welche Pfade unter Linux ausgegeben werden, das kann ich im Moment nicht testen.
also ich kanns grad nur am Mac testen, und da wird folgender pfad ausgegeben:
"/Users/hannes/Library/Application Support/AppName"

Scheint also zu funktionieren.

Nun hab ich aber ein kleines Problem noch: irgendwie werden die Daten nicht gespeichert... Ich vermute dass es daran liegt:

Code: Alles auswählen

   self.recentFileConfig = wx.FileConfig()
   sp = wx.StandardPaths.Get()
   self.recentFileConfig.SetPath(os.path.join(sp.GetUserDataDir(),"filehistory.cfg"))
   self.recentFileConfig.GetPath()
und die Ausgabe beginnt mit "/". ist das Normal? Ist das ein Bug?
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Dienstag 28. November 2006, 12:41

hmueller hat geschrieben:

Code: Alles auswählen

self.recentFileConfig.GetPath()
und die Ausgabe beginnt mit "/". ist das Normal?
Hi hmueller!

Ich habe mich ein wenig schlauer gemacht. ``wx.FileConfig`` ist gar nicht mal so schlecht. Es bietet Zugriff auf INI-Dateien, unterstützt zusätzlich aber mehrere Ebenen.
Innerhalb der INI-Datei kann man mehrere verschachtelte Ebenen (=Gruppen) erstellen und auslesen. Beim Zugreifen oder beim Schreiben, muss man nur den kompletten Pfad angeben.

Dieser Code:

Code: Alles auswählen

globconf = wx.FileConfig(localFilename = "irgendeindateiname")
globconf.WriteBool("/global/aa/boolean_value1", True)
Erstellt eine INI-Datei mit dieser Struktur:

Code: Alles auswählen

[global]
[global/aa]
boolean_value1=1
Diesen Wert kann man so wieder auslesen:

Code: Alles auswählen

globconf.ReadBool("/global/aa/boolean_value1")
Und hier mein Beispiel, mit dem ich das Ganze getestet habe:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-

import wx
import os

wx.SetDefaultPyEncoding("iso-8859-1")

CONFIG_FILENAME = "my_settings.ini"


def main():
    app = wx.PySimpleApp()
    app.SetAppName("HalloWelt")
    
    sp = wx.StandardPaths.Get()
    
    user_datadir = sp.GetUserDataDir()
    if not os.path.isdir(user_datadir):
        os.makedirs(user_datadir)
    
    config_filename = os.path.join(user_datadir, CONFIG_FILENAME)
    print config_filename

    # Schreiben
    globconf = wx.FileConfig(localFilename = config_filename)
    
    globconf.WriteBool("/global/aa/boolean_value1", True)
    globconf.WriteBool("/global/aa/boolean_value2", False)
    globconf.WriteBool("/global/aa/boolean_value3", True)
    
    del globconf
    
    # Lesen
    globconf = wx.FileConfig(localFilename = config_filename)
    print "Alle Eintraege:", globconf.GetNumberOfEntries(True)
    
    globconf.SetPath("/global/aa")
    print "Eintraege der Gruppe '/global/aa':", globconf.GetNumberOfEntries()
    for i in range(globconf.GetNumberOfEntries()):
        print globconf.GetNextEntry(i)[1], 
        print globconf.ReadBool(globconf.GetNextEntry(i)[1])
    
    
if __name__ == "__main__":
    main()
mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
hmueller
User
Beiträge: 39
Registriert: Montag 27. November 2006, 16:07
Wohnort: Linz, Oberösterreich

Dienstag 28. November 2006, 12:54

Danke gerold!
Jetzt ist mir das auch klar. Das was mir noch fehlt, ist die Persistierung der File History.

Ich benutze wx.FileHistory

Code: Alles auswählen

   self.recentFileConfig = wx.FileConfig(localFilename=os.path.join(sp.GetUserDataDir(),"filehistory.cfg"))
   self.filehistory = wx.FileHistory()
und dann (wie ich glaube korrekt aus der Doku http://www.wxpython.org/docs/api/wx.Fil ... class.html, die eher spärlich ist...)

Code: Alles auswählen

   self.filehistory.Load(self.recentFileConfig)
beim Programmende versuche ich die Daten zu speichern:

Code: Alles auswählen

   self.filehistory.Save(self.recentFileConfig)
   self.recentFileConfig.Flush()
Wenn ich dann das Programm neu starte, kommen jedoch keine Daten an. Was mache ich da verkehrt?
hmueller
User
Beiträge: 39
Registriert: Montag 27. November 2006, 16:07
Wohnort: Linz, Oberösterreich

Dienstag 28. November 2006, 13:20

Ha, es geht.

Beim Laden ist folgende Reihenfolge wichtig:

Code: Alles auswählen

        self.filehistory.UseMenu(menu)
        self.filehistory.Load(self.recentFileConfig)
Ich hatte es andersrum, da hat ers zwar geladen, aber ins Nirvana geschrieben.
Es schadet auch nicht, den ConfigPath zu setzen.

Code: Alles auswählen

self.recentFileConfig.SetPath('/RecentFiles')
Danke für die große Hilfsbereitschaft hier!

bis bald,
hmueller
Antworten