4

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
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
GoodPie
  • 967
  • 3
  • 23
  • 41

3 Answers3

4

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.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • There might be a wxpython specific app destructor – Jakob Bowyer Jan 06 '13 at 15:03
  • Thankyou, this was my first program in wxpython, and i was just going off what I could figure out. I am aware of the .pack() function in Tkinter, and I was hoping for something similar in wxpython. I'll certainly look into the BoxSizers, and look into your code and see how it works, thankyou again :) – GoodPie Jan 07 '13 at 09:28
  • Also this module utils_debug, how do i get this? I've been searching for the last 10 minutes, and can't find anything on it at all. I do not have this module. – GoodPie Jan 07 '13 at 09:32
  • You can remove utils_debug -- it is not used anywhere in code I posted. You won't find it on the web. However, I edited my post to show how I used it's `describe` function to search for widget attributes. – unutbu Jan 07 '13 at 11:00
  • I like the #Blood, #Skin, #Heart comments - very cute. – Marcel Stör Feb 18 '17 at 22:47
1

Your probably gonna want to store the state of the buttons in a data collection like a list or a dict, then you can marshal or serialize the data

e.g.

my_button_states = {"button1":True}
import json
with open("states.json", 'w') as output:
    json.dump(my_button_states, output)

Later on just re-load the json back into the program and set the buttons by looping over the dict

e.g.

with open("states.json") as fp:
    my_button_states = json.loads(fp)
# inside your frame or something.... idk where you put them.
for button, state in my_button_states.iteritems():
    setattr(self, button, state)

You can use json or some people might suggest pickle but json might be the better choice for this.

Using your code you probably want to do something like

json.dump(mylist, output)

In my example of course

Jakob Bowyer
  • 33,878
  • 8
  • 76
  • 91
  • all my buttons are already stored in a list, so basically just write some more program to go over that list and convert them into checkboxes? Seems like a good idea, however what is this json module and how would i store this list, so that it is saved even when the computer is shut down? I am fairly new to python. – GoodPie Jan 06 '13 at 15:00
  • @user1952975 what is it about the button you want to store? – Jakob Bowyer Jan 06 '13 at 15:05
  • I have uploaded my code, as you can see, it is very simple at the moment. How would i implement what you were talking about into my code? Sorry about my last comment, i meant "inputs" not "buttons" :D – GoodPie Jan 06 '13 at 15:14
  • @Jokob i have run into an error, and considering i'm new to this whole json thing, i am a bit lost. I understand json save's what you ask it to save into a file.json. You can then call it back using that example that you showed, however i'm getting an error with this: mylist = json.loads(fp), it is saying that it's expecting a string... :c Please Help – GoodPie Jan 06 '13 at 15:51
  • More generally, if this particular solution isn't what you are looking for, Google for "persist state". A lot of programs use a database of some sort for this, but for small amounts of data, JSON or one of the "noDB" offerings is perfect. – tripleee Jan 06 '13 at 16:33
0

Just a short notice. I wondered if there is a built-in solutiuon - after all, most of us faces this problem sooner or later.

Turns out, there is a thing called PersistenceManager that can be used to simply save and resore the state.

Note, I did not use it just came across.

jake77
  • 1,892
  • 2
  • 15
  • 22