Search code examples
pythonwxpythonsaverestart

Python - Saving state of a program between restarts?


I would like to know how to save a programs current settings so that it remains the same, unless specified otherwise, on program restart or computer restart. For example, windows default program sticky notes, where it saves the text, so that it can be used even after the computer is shut down.

Is there some sort of module that you can import? My program is basically a task list program, where you can add stuff to a list, and tick it off using wxPython check boxes. Is there any possible way to keep it's state even after program exit?

It would be appreciated if someone could show me an example with my code, and don't worry, I won't just simply copy it and be done with it. It will be considered a learning experience for me, so that I may use it in the future. Thanks.

Here is my program:

import wx, sys,os

mylist = []

class test(wx.Frame):

def __init__(self, parent, id):


    self.count = 1
    #Frame
    wx.Frame.__init__(self,parent,id,'List',size = (200,500))
    #Panel
    self.panel = wx.Panel(self)
    item = wx.TextEntryDialog(None, "List Title")
    if item.ShowModal() == wx.ID_OK:
        print 'here'
        answer = item.GetValue()
        mylist.append(answer)
        print mylist
        windtitle = wx.StaticText(self.panel, -1, answer, (10,10))
        windtitle.SetForegroundColour("blue")


    addButton = wx.Button(self.panel, label = "+ Add", pos=(40,450), size = (60,-1))
    finishButton = wx.Button(self.panel, label = "Finish", pos=(110,450), size = (60,-1))

    self.Bind(wx.EVT_BUTTON, self.addtomenu, addButton)
    self.Bind(wx.EVT_BUTTON, self.finish, finishButton)

def finish(self, event):
    self.Destroy()
    sys.exit()

def addtomenu(self,event):

    newitem = wx.TextEntryDialog(None, "New Item")
    if newitem.ShowModal() == wx.ID_OK:
        count = len(mylist)+1
        print count
        yaxis = 20*count
        if count == 21:
            wx.StaticText(self.panel, -1, "List To Full", (10, yaxis))
        else:
            answer = newitem.GetValue()
            mylist.append(answer)
            print mylist
            self.Bind(wx.EVT_CLOSE, self.closewindow)
            wx.CheckBox(self.panel, -1, answer, (10,yaxis), size = (200,-1)) 



def closewindow(self, event):
    self.Destroy()






if __name__ == "__main__":
    app=wx.PySimpleApp()  #Blood
    frame = test(parent=None, id = -1)  #Skin
    frame.Show()
    app.MainLoop()  #Heart

Solution

  • Here is an example of how you might save the state of the program in a JSON file. You already have a finish method which is called when the program exits or when the Finish button is closed. We can use it now to also call a save method which saves the state to a JSON file.

    def finish(self, event):
        self.save()
        self.Destroy()
        sys.exit()
    
    def save(self):
        windtitle = self.windtitle.GetLabelText()
        checkboxes = [{'checked': child.IsChecked(),
                       'label': child.GetLabel()}
                      for child in self.panel.GetChildren()
                      if isinstance(child, wx.CheckBox)]
        data = {
            'windtitle':windtitle,
            'checkboxes':checkboxes,
            }
        with open(CONFIGFILE, 'w') as f:
            json.dump(data, f)
    

    And here is how you could read the JSON data to reconstitute the GUI:

    def load(self):
        if os.path.exists(CONFIGFILE):
            with open(CONFIGFILE, 'r') as f:
                data = json.load(f)
            title = data['windtitle']
            self.windtitle = wx.StaticText(self.panel, -1, title)
            self.vbox.Add(self.windtitle)
            for checkbox in data['checkboxes']:
                label = checkbox['label']
                cb = wx.CheckBox(
                    self.panel, -1, checkbox['label'])
                self.vbox.Add(cb)                
                cb.SetValue(checkbox['checked'])
        else:
            self.create_windtitle()
        self.create_buttons()
    

    For example:

    import wx, sys, os
    import json
    
    CONFIGFILE = os.path.expanduser('~/tasklist.json')
    class test(wx.Frame):
        def __init__(self, parent, id):
            frame = wx.Frame.__init__(self, parent, id, 'List', size = (200,500))
            self.panel = wx.Panel(self)
            self.panelbox = wx.BoxSizer(wx.VERTICAL)                
    
            self.vbox = wx.BoxSizer(wx.VERTICAL)        
            self.load()
    
            self.panelbox.Add(self.vbox)        
            self.panelbox.Add(self.buttonbox)
    
            self.panel.SetSizer(self.panelbox)
            self.panelbox.Fit(self)
    
            self.Bind(wx.EVT_BUTTON, self.addtomenu, self.addButton)
            self.Bind(wx.EVT_BUTTON, self.finish, self.finishButton)
            self.Bind(wx.EVT_CLOSE, self.finish)
    
        def create_buttons(self):
            self.buttonbox = wx.BoxSizer(wx.VERTICAL)                
            self.addButton = wx.Button(
                self.panel, label = "+ Add")
            self.finishButton = wx.Button(
                self.panel, label = "Finish")
            self.buttonbox.Add(self.addButton)
            self.buttonbox.Add(self.finishButton)
    
        def create_windtitle(self):
            item = wx.TextEntryDialog(None, "List Title")
            if item.ShowModal() == wx.ID_OK:
                answer = item.GetValue()
                self.windtitle = wx.StaticText(self.panel, -1, answer)
                self.windtitle.SetForegroundColour("blue")
    
        def addtomenu(self, event):
            newitem = wx.TextEntryDialog(None, "New Item")
            if newitem.ShowModal() == wx.ID_OK:
                if len(self.mylist) > 5:
                    wx.StaticText(self.panel, -1, "List To Full")
                else:
                    answer = newitem.GetValue()
                    cb = wx.CheckBox(self.panel, -1, answer)
                    self.vbox.Add(cb)
            self.panelbox.Fit(self)
    
        def finish(self, event):
            self.save()
            self.Destroy()
            sys.exit()
    
        @property
        def mylist(self):
            return [ child.GetLabel()
                     for child in self.panel.GetChildren()
                     if isinstance(child, wx.CheckBox) ]
    
        def save(self):
            windtitle = self.windtitle.GetLabelText()
            checkboxes = [{'checked': child.IsChecked(),
                           'label': child.GetLabel()}
                          for child in self.panel.GetChildren()
                          if isinstance(child, wx.CheckBox)]
            data = {
                'windtitle':windtitle,
                'checkboxes':checkboxes,
                }
            with open(CONFIGFILE, 'w') as f:
                json.dump(data, f)
    
        def load(self):
            if os.path.exists(CONFIGFILE):
                with open(CONFIGFILE, 'r') as f:
                    data = json.load(f)
                title = data['windtitle']
                self.windtitle = wx.StaticText(self.panel, -1, title)
                self.vbox.Add(self.windtitle)
                for checkbox in data['checkboxes']:
                    label = checkbox['label']
                    cb = wx.CheckBox(
                        self.panel, -1, checkbox['label'])
                    self.vbox.Add(cb)                
                    cb.SetValue(checkbox['checked'])
            else:
                self.create_windtitle()
            self.create_buttons()
    
    if __name__ == "__main__":
        app = wx.PySimpleApp()  #Blood
        frame = test(parent = None, id = -1)  #Skin
        frame.Show()
        app.MainLoop()  #Heart
    

    By the way, do not use explicit positions for placing widgets in your GUI. That road leads to madness. If you use positions (e.g. pos = (10,yaxis)), as your GUI grows, it becomes more and more difficult to modify your layout. Every element's position becomes dependent on some other element's position and it soon becomes unmanageable.

    Every GUI framework provides some saner way to achieve nice looking layouts. I'm not very familiar with wxpython, but it seems to use BoxSizers. The layout I used above is very rudimentary. I'm sure a much nicer layout can be achieved with some study of wxpython layout design patterns.


    Sometimes I needed to find out what all the attributes and methods of a widget are. For example, I didn't know how to ask a Panel what Checkboxes it contains. I found it using this function:

    def describe(obj):
        for key in dir(obj):
            try:
                val = getattr(obj, key)
            except AttributeError:
                continue
            if callable(val):
                help(val)
            else:
                print('{k} => {v}'.format(k = key, v = val))
            print('-'*80)
    
    describe(self.panel)
    

    This is the function I had in utils_debug.