wxPython Fenstergröße und Position automatisch speichern

Code-Stücke können hier veröffentlicht werden.
Antworten
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Freitag 2. März 2007, 17:03

Hi!

Erbt man von der Klasse ``AutoSizeSave``, dann wird automatisch jede Größenänderung des Fensters in eine INI-Datei geschrieben. Die Größe des Fensters wird in der INI nur dann geändert, wenn das Fenster nicht maximiert ist. Ist das Fenster maximiert, dann wird diese Information natürlich auch in die INI-Datei geschrieben.

Erbt man von der Klasse ``AutoPositionSave``, dann wir automatisch jede Positionsänderung des Fensters in eine INI-Datei geschrieben. Existiert die INI-Datei noch nicht, dann wird das Fenster automatisch zentriert.

Um die Ressourcen zu schonen, wird nur alle 1,3 Sekunden geprüft, ob sich etwas geändert hat. Nur bei Änderung der Position oder der Größe wird die INI-Datei neu geschrieben.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
"""
Automatisches Speichern der Position und Größe von wxPython-Fenstern

Created:       2007-02-24 by Gerold -- http://gerold.bcom.at/
Requirements:  Python: http://www.python.org/
               wxPython: http://wxpython.org/

-----------------------------------------------------
Die Verwendung von AutoSizeSave and AutoPositionSave
-----------------------------------------------------

Wenn man von beiden Klassen erben möchte, dann muss man darauf achten, 
dass zuerst die AutoSizeSave-Klasse initialisiert wird und dann erst 
die AutoPositionSave-Klasse.
Es ist ebenfalls wichtig, dass der Anwendung ein "eindeutiger" Name 
gegeben wird, da dieser Name bestimmt, in welchem Ordner die 
Einstellungen gespeichert werden. Ein nicht eindeutiger Name
würde dazu führen, dass sich die Anwendungen die Einstellungen 
gegenseitig überschreiben.

::

    import wx
    wx.SetDefaultPyEncoding("iso-8859-1")
    
    ...
    
    class MainFrame(wx.Frame, AutoSizeSave, AutoPositionSave):
        
        def __init__(self, parent = None, id = -1, title = "My Application"):
            
            wx.Frame.__init__(self, parent, id, title)
            AutoSizeSave.__init__(self, autosave = True)
            AutoPositionSave.__init__(self, autosave = True)
            
            ...
    
    ...
    app = wx.PySimpleApp()
    app.SetAppName("MySaveDemo")
    f = MainFrame()
    f.Show()
    app.MainLoop()
  
"""

__all__ = ["AutoSizeSave", "AutoPositionSave"]

import os
import sys
import wx
import ConfigParser


class AutoSizeSave(object):
    """
    Speichert die Fenstergröße automatisch, wenn diese Klasse beerbt wird.
    """
    
    # Diese Werte werden beim Speichern neu gesetzt
    _ass_old_w = None
    _ass_old_h = None
    _ass_old_maximized = None
    
    AUTOSAVE_MILLISECONDS = 1300
    
    def __init__(self, appname = None, containername = None, autosave = True):
        """
        Init
        
        :param appname: Name der Anwendung. Dieser ist wichtig, um den richtigen
            Pfad zu ermitteln.
        
        :param containername: Name des Fensters. Dieser ist wichtig, um den 
            richtigen Pfad zu ermitteln.
        
        :param autosave: Wenn True, dann wird versucht, an ``self`` einen Timer
            anzuhängen, der alle paar Sekunden die aktuelle Größe speichert, falls
            sich diese inzwischen geändert hat.
        """
        
        assert(isinstance(self, wx.TopLevelWindow))
        
        # Standard-Pfade
        standard_paths = wx.StandardPaths.Get()
        
        # App-Name
        if appname is None:
            app = wx.GetApp()
            appname = app.GetAppName()
        
        # Container-Name
        if containername is None:
            containername = self.__class__.__name__
        
        # UserLocalDataDir
        if not sys.platform.startswith("win"):
            appname = "." + appname.rstrip(".")
        user_local_data_dir = os.path.join(
            standard_paths.GetUserConfigDir(), appname
        )
        
        # Einstellungsordner erstellen, falls dieser noch nicht existiert
        if not os.path.isdir(user_local_data_dir):
            os.makedirs(user_local_data_dir)
        
        # INI
        filename = "saved_size_%s.ini" % containername.replace(" ", "_")
        self._ass_ini_filename = os.path.join(user_local_data_dir, filename)
        self._ass_ini = ConfigParser.SafeConfigParser()
        self._ass_ini.read(self._ass_ini_filename)
        if not self._ass_ini.has_section("SIZE"):
            self._ass_ini.add_section("SIZE")
        
        # Alte Größe wieder herstellen
        if (
            self._ass_ini.has_option("SIZE", "W") and
            self._ass_ini.has_option("SIZE", "H") and
            self._ass_ini.has_option("SIZE", "MAXIMIZED")
        ):
            w = self._ass_ini.getint("SIZE", "W")
            h = self._ass_ini.getint("SIZE", "H")
            maximized = self._ass_ini.getboolean("SIZE", "MAXIMIZED")
            
            self._ass_old_w = w
            self._ass_old_h = h
            self._ass_old_maximized = maximized
            
            self.SetSize(wx.Size(w, h))
            wx.YieldIfNeeded()
            if maximized:
                # WICHTIG! Mit CallAfter aufrufen!
                wx.CallAfter(self.Maximize, (True))
        
        # Events binden
        self.Bind(wx.EVT_SIZE, self._ass_on_size)
        
        # Timer an "self" anhängen
        if autosave:
            self._ass_timer = wx.Timer()
            self._ass_timer.Bind(wx.EVT_TIMER, self.save_size)
            self._ass_timer.Start(self.AUTOSAVE_MILLISECONDS) # alle x sec.
    
    
    def _ass_on_size(self, event):
        
        event.Skip()
        
        size = self.GetSize()
        maximized = self.IsMaximized()
        
        self._ass_ini.set("SIZE", "MAXIMIZED", maximized and "TRUE" or "FALSE")
        
        if not maximized:
            self._ass_ini.set("SIZE", "W", str(size[0]))
            self._ass_ini.set("SIZE", "H", str(size[1]))


    def save_size(self, event = None, force = False):
        """
        Speichert die Größe in die INI-Datei
        """
        
        # Aktuelle Werte einlesen und evt. neu in INI setzen
        try:
            maximized = self._ass_ini.getboolean("SIZE", "MAXIMIZED")
        except ConfigParser.NoOptionError:
            maximized = self.IsMaximized()
            self._ass_ini.set("SIZE", "MAXIMIZED", maximized and "TRUE" or "FALSE")
        
        try:
            w = self._ass_ini.getint("SIZE", "W")
        except ConfigParser.NoOptionError:
            if not maximized:
                size = self.GetSize()
                w = size[0]
            else:
                w = "-1"
            self._ass_ini.set("SIZE", "W", str(w))
        
        try:
            h = self._ass_ini.getint("SIZE", "H")
        except ConfigParser.NoOptionError:
            if not maximized:
                size = self.GetSize()
                h = size[1]
            else:
                h = "-1"
            self._ass_ini.set("SIZE", "H", str(h))
        
        # Prüfen ob gespeichert werden muss
        if not force:
            if (
                (w == self._ass_old_w) and
                (h == self._ass_old_h) and
                (maximized == self._ass_old_maximized)
            ):
                # Es muss nichts gespeichert werden --> Abbruch
                return
        
        # Speichern
        f = file(self._ass_ini_filename, "w")
        self._ass_ini.write(f)
        f.close()
        
        # Alte Werte erneuern
        self._ass_old_w = self._ass_ini.getint("SIZE", "W")
        self._ass_old_h = self._ass_ini.getint("SIZE", "H")
        self._ass_old_maximized = self._ass_ini.getboolean("SIZE", "MAXIMIZED")


class AutoPositionSave(object):
    """
    Speichert die Fensterposition automatisch, wenn diese Klasse beerbt wird.
    """
    
    # Diese Werte werden beim Speichern neu gesetzt
    _aps_old_x = None
    _aps_old_y = None
    
    AUTOSAVE_MILLISECONDS = 1300
    
    def __init__(self, appname = None, containername = None, autosave = True):
        """
        Init
        
        :param appname: Name der Anwendung. Dieser ist wichtig, um den richtigen
            Pfad zu ermitteln.
        
        :param containername: Name des Fensters. Dieser ist wichtig, um den 
            richtigen Pfad zu ermitteln.
        
        :param autosave: Wenn True, dann wird versucht, an ``self`` einen Timer
            anzuhängen, der alle paar Sekunden die aktuelle Position speichert, falls
            sich diese inzwischen geändert hat.
        """
        
        assert isinstance(self, wx.TopLevelWindow)
        
        # Standard-Pfade
        standard_paths = wx.StandardPaths.Get()
        
        # App-Name
        if not appname:
            app = wx.GetApp()
            appname = app.GetAppName()
        
        # Container-Name
        if containername is None:
            containername = self.__class__.__name__
        
        # UserLocalDataDir
        if not sys.platform.startswith("win"):
            appname = "." + appname.rstrip(".")
        user_local_data_dir = os.path.join(
            standard_paths.GetUserConfigDir(), appname
        )
        
        # Einstellungsordner erstellen, falls dieser noch nicht existiert
        if not os.path.isdir(user_local_data_dir):
            os.makedirs(user_local_data_dir)
        
        # INI
        filename = "saved_position_%s.ini" % containername.replace(" ", "_")
        self._aps_ini_filename = os.path.join(user_local_data_dir, filename)
        self._aps_ini = ConfigParser.SafeConfigParser()
        self._aps_ini.read(self._aps_ini_filename)
        if not self._aps_ini.has_section("POS"):
            self._aps_ini.add_section("POS")
        
        # Alte Position wieder herstellen
        if (
            self._aps_ini.has_option("POS", "X") and
            self._aps_ini.has_option("POS", "Y")
        ):
            x = self._aps_ini.getint("POS", "X")
            y = self._aps_ini.getint("POS", "Y")
            
            self._aps_old_x = x
            self._aps_old_y = y
            
            self.SetPosition(wx.Point(x, y))
        else:
            # Zentrieren, da nichts in der INI steht
            if not self.IsMaximized():
                self.Center()
        
        # Event(s) binden
        self.Bind(wx.EVT_MOVE, self._aps_on_move)
        
        # Timer an "self" anhängen, der alle paar Sekunden ein
        # ``save_position`` auslöst.
        if autosave:
            self._aps_timer = wx.Timer()
            self._aps_timer.Bind(wx.EVT_TIMER, self.save_position)
            self._aps_timer.Start(self.AUTOSAVE_MILLISECONDS) # alle x sec.
    
    
    def _aps_on_move(self, event):
        
        event.Skip()
        
        pos = self.GetPosition()
        maximized = self.IsMaximized()
        
        if not maximized:
            self._aps_ini.set("POS", "X", str(pos[0]))
            self._aps_ini.set("POS", "Y", str(pos[1]))
    

    def save_position(self, event = None, force = False):
        """
        Speichert die Positionseinstellungen in die INI-Datei
        """
        
        # Aktuelle Werte einlesen und evt. neu in INI setzen
        try:
            x = self._aps_ini.getint("POS", "X")
        except ConfigParser.NoOptionError:
            pos = self.GetPosition()
            x = pos[0]
            self._aps_ini.set("POS", "X", str(pos[0]))
        
        try:
            y = self._aps_ini.getint("POS", "Y")
        except ConfigParser.NoOptionError:
            pos = self.GetPosition()
            y = pos[1]
            self._aps_ini.set("POS", "Y", str(pos[1]))
        
        # Prüfen ob gespeichert werden muss
        if not force:
            if (
                (x == self._aps_old_x) and
                (y == self._aps_old_y)
            ):
                # Es muss nichts gespeichert werden --> Abbruch
                return
        
        # Speichern
        f = file(self._aps_ini_filename, "w")
        self._aps_ini.write(f)
        f.close()
        
        # Alte Werte erneuern
        self._aps_old_x = self._aps_ini.getint("POS", "X")
        self._aps_old_y = self._aps_ini.getint("POS", "Y")


class TestFrame(wx.Frame, AutoSizeSave, AutoPositionSave):
    
    def __init__(self, parent = None, id = -1, title = "Testfenster"):
        
        wx.Frame.__init__(self, parent, id, title)
        
        AutoSizeSave.__init__(self)
        AutoPositionSave.__init__(self)


def main():
    """
    Hauptprozedur
    """
    
    app = wx.PySimpleApp()
    app.SetAppName("Python AutoSave-Example")
    f = TestFrame()
    f.Show()
    app.MainLoop()


if __name__ == "__main__":
    main()
lg
Gerold
:-)

Edit: Code ein wenig verbessert.
Edit: AUTOSAVE_MILLISECONDS auf 1300 gesetzt.
Edit: Auf NICHT-Windows-Systemen wird ein Punkt vor dem Anwendungsnamen gesetzt.
Zuletzt geändert von gerold am Montag 9. April 2007, 20:24, insgesamt 2-mal geändert.
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Francesco
User
Beiträge: 824
Registriert: Mittwoch 1. Dezember 2004, 12:35
Wohnort: Upper Austria

Samstag 3. März 2007, 01:18

Hi Gerold, wieder ein super Code snippet.
Danke für Deine Mühe!
Antworten