How to create a settings manager (Phoenix)
Keywords : Setting, Manager.
Contents
Demonstrating :
Tested py3.x, wx4.x and Win10.
Are you ready to use some samples ?
Test, modify, correct, complete, improve and share your discoveries !
First example
1 # sample_one.py
2
3 import os
4 import zlib
5 from xml.etree.ElementTree import ElementTree, Element, SubElement
6 import xml.etree.ElementTree as ETree
7 import wx
8
9 # def startupStringToPos
10 # def startupPosToString
11 # class MySettingsManager
12 # class MyDlgSettings
13 # class MyFrame
14 # class MyApp
15
16 #---------------------------------------------------------------------------
17
18 def startupStringToPos(value):
19 if value == 'CenterScreen':
20 return wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL
21 if value == 'LastKnownPos':
22 return wx.ALIGN_NOT
23 iHPos, iVPos = 0, 0
24 if 'Left' in value:
25 iHPos = wx.LEFT
26 elif 'Right' in value:
27 iHPos = wx.RIGHT
28 else:
29 iHPos = wx.ALIGN_CENTER_HORIZONTAL
30 if 'Top' in value:
31 iVPos = wx.TOP
32 elif 'Bottom' in value:
33 iVPos = wx.BOTTOM
34 else:
35 iVPos = wx.ALIGN_CENTER_VERTICAL
36
37 return iHPos|iVPos
38
39 #---------------------------------------------------------------------------
40
41 def startupPosToString(value):
42 if value == (wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL):
43 return 'CenterScreen'
44 if value == wx.ALIGN_NOT:
45 return 'LastKnownPos'
46 sHPos, sVPos = '', ''
47 if (value&wx.LEFT) == wx.LEFT:
48 sHPos = 'Left'
49 elif (value&wx.RIGHT) == wx.RIGHT:
50 sHPos = 'Right'
51 else:
52 sHPos = 'Center'
53 if (value&wx.TOP) == wx.TOP:
54 sVPos = 'Top'
55 elif (value&wx.BOTTOM) == wx.BOTTOM:
56 sVPos = 'Bottom'
57 else:
58 sVPos = 'Center'
59 return sVPos + sHPos
60
61 #---------------------------------------------------------------------------
62
63 class MySettingsManager(object):
64 _instance = None
65 # Default settings
66 _iStartPos = wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL
67 _ptStartPos = wx.DefaultPosition
68 _szStartSize = wx.Size(600, 400)
69 _bSingleInstanceOnly = False
70 _bCompSettings = False
71 # Other default values
72 _bModified = False
73 _sSettingsFName = 'settings'
74
75 def __new__(cls):
76 if cls._instance is None:
77 cls._instance = super(MySettingsManager, cls).__new__(cls)
78 cls._instance.Initialize()
79 return cls._instance
80
81 def Initialize(self):
82 self._bModified = False
83 sScriptDir = os.path.dirname(os.path.abspath(__file__))
84 if os.path.isfile(os.path.join(sScriptDir, self._sSettingsFName)):
85 self._sSettingsPath = sScriptDir
86 else:
87 self._sSettingsPath = wx.StandardPaths.Get().GetUserDataDir()
88
89 def _getDatasPath(self):
90 return self._sSettingsPath
91
92 def _isModified(self):
93 return self._bModified
94
95 def SetModified(self):
96 self._bModified = True
97
98 def _getMainWndStartupPos(self):
99 if self._iStartPos == wx.ALIGN_NOT:
100 return self._iStartPos, self._ptStartPos
101
102 return self._iStartPos, None
103
104 def _setMainWndStartupPos(self, mode, pos=None):
105 if mode != self._iStartPos:
106 self._iStartPos = mode
107 self._bModified = True
108 if not pos is None:
109 if pos != self._ptStartPos:
110 self._ptStartPos = pos
111 self._bModified = True
112
113 def _getMainWndStartupSize(self):
114 return self._szStartSize
115
116 def _setMainWndStartupSize(self, size):
117 if size != self._szStartSize:
118 self._szStartSize = size
119 self._bModified = True
120
121 def _getMainWndStartupRect(self):
122 return wx.Rect(self._ptStartPos, self._szStartSize)
123
124 def _setMainWndStartupRect(self, rect):
125 if not rect == wx.Rect(self._ptStartPos, self._szStartSize):
126 self._ptStartPos = rect.GetPosition()
127 self._szStartSize = rect.GetSize()
128 self._bModified = True
129
130 def _getMultipleInstancesAllowed(self):
131 return self._bSingleInstanceOnly is False
132
133 def _setMultipleInstancesAllowed(self, value):
134 if self._bSingleInstanceOnly == value:
135 self._bSingleInstanceOnly = not value
136 self._bModified = True
137
138 def _getCompressSettings(self):
139 return self._bCompSettings
140
141 def _setCompressSettings(self, value):
142 if not self._bCompSettings == value:
143 self._bCompSettings = value
144 self._bModified = True
145
146 def ReadSettings(self):
147 # Check whether the settings folder exists
148 if not os.path.isdir(self._sSettingsPath):
149 return
150 # Construct the settings file name and check if it exists
151 sFName = os.path.join(self._sSettingsPath, self._sSettingsFName)
152 if not os.path.isfile(sFName):
153 return
154 # Try to load the xml settings file
155 f = open(sFName, 'rb')
156 datas = f.read()
157 f.close()
158 if datas[0:5] != b'<?xml':
159 # Compressed file
160 datas = zlib.decompress(datas)
161
162 tree = ElementTree(ETree.fromstring(datas.decode()))
163 #tree.parse(sFName)
164 rootNode = tree.getroot()
165 #Read each xml entry
166 for childNode in rootNode:
167 nodeName = childNode.tag
168 if nodeName == 'StartupPos':
169 self._iStartPos = startupStringToPos(childNode.get('Value', 'CenterScreen'))
170 iX = int(childNode.get('X', '-1'))
171 iY = int(childNode.get('Y', '-1'))
172 self._ptStartPos = wx.Point(iX, iY)
173 iW = int(childNode.get('W', '-1'))
174 iH = int(childNode.get('H', '-1'))
175 self._szStartSize = wx.Size(iW, iH)
176 elif nodeName == 'MultiInstances':
177 self._bSingleInstanceOnly = (childNode.text != 'Allowed')
178 elif nodeName == 'CompressSettingsFile':
179 self._bCompSettings = (childNode.text == 'Yes')
180
181 def SaveSettings(self):
182 # Create the root node of the xml tree
183 rootNode = Element('Settings-file')
184 rootNode.set('Version', '1.0')
185
186 # Position of the main window at application startup
187 node = SubElement(rootNode, 'StartupPos')
188 node.set('Value', startupPosToString(self._iStartPos))
189 iX, iY = self._ptStartPos.Get()
190 iW, iH = self._szStartSize.Get()
191 node.set('X', str(iX))
192 node.set('Y', str(iY))
193 node.set('W', str(iW))
194 node.set('H', str(iH))
195
196 # Allowing or not multiple instances of the application
197 node = SubElement(rootNode, 'MultiInstances')
198 if self._bSingleInstanceOnly:
199 node.text = 'Not-Allowed'
200 else:
201 node.text = 'Allowed'
202
203 # Settings file compression
204 node = SubElement(rootNode, 'CompressSettingsFile')
205 if self._bCompSettings:
206 node.text = 'Yes'
207 else:
208 node.text = 'No'
209
210 # Check if the settings folder exists
211 if not os.path.isdir(self._sSettingsPath):
212 os.makedirs(self._sSettingsPath)
213 # Construct the settings file name and check if it exists
214 sFName = os.path.join(self._sSettingsPath, self._sSettingsFName)
215 if os.path.isfile(sFName):
216 os.remove(sFName)
217 # Save the new xml file
218 if self._bCompSettings:
219 datas = ETree.tostring(rootNode, encoding='UTF8', method='xml')
220 datas = datas.replace(b'UTF8', b'UTF-8')
221 compDatas = zlib.compress(datas, 9)
222 f = open(sFName, 'wb')
223 f.write(compDatas)
224 f.close()
225 else:
226 tree = ElementTree(rootNode)
227 tree.write(sFName, encoding='UTF-8', xml_declaration=True)
228
229 DatasPath = property(_getDatasPath)
230 Modified = property(_isModified)
231 MainWndStartupPos = property(_getMainWndStartupPos, _setMainWndStartupPos)
232 MainWndStartupSize = property(_getMainWndStartupSize, _setMainWndStartupSize)
233 MainWndStartupRect = property(_getMainWndStartupRect, _setMainWndStartupRect)
234 MultipleInstancesAllowed = property(_getMultipleInstancesAllowed, _setMultipleInstancesAllowed)
235 CompressSettingsFile = property(_getCompressSettings, _setCompressSettings)
236
237 #---------------------------------------------------------------------------
238
239 class MyDlgSettings(wx.Dialog):
240 def __init__(self, parent):
241 wx.Dialog.__init__(self, parent, title='Settings')
242
243 self._createInterface()
244 self._fillControls()
245 self._connectEvents()
246 self.CenterOnParent()
247
248 def _createInterface(self):
249 szrMain = wx.BoxSizer(wx.VERTICAL)
250
251 nBook = wx.Notebook(self, -1)
252
253 # Tab "General"
254 page = wx.Panel(nBook, wx.ID_STATIC)
255 pageszr = wx.BoxSizer(wx.VERTICAL)
256
257 box = wx.StaticBoxSizer(wx.VERTICAL, page, 'Position of the main window:')
258 label = wx.StaticText(page, wx.ID_STATIC, 'Position of the main window at application startup')
259 box.Add(label, 0, wx.ALL, 5)
260 lnszr = wx.BoxSizer(wx.HORIZONTAL)
261 self.optStartType = []
262 self.optStartType.append(wx.RadioButton(page, -1, 'Predefined position', style=wx.RB_GROUP))
263 lnszr.Add(self.optStartType[0], 0, wx.ALL, 5)
264 self.optStartType.append(wx.RadioButton(page, -1, 'Last registered position'))
265 lnszr.Add(self.optStartType[1], 0, wx.TOP|wx.BOTTOM|wx.RIGHT, 5)
266 box.Add(lnszr, 0, wx.ALL, 0)
267
268 box2 = wx.StaticBoxSizer(wx.VERTICAL, page, 'Predefined positions:')
269 self.stbPos = box2.GetStaticBox()
270 flxszr = wx.FlexGridSizer(3, 5, 5)
271 self.optDefPos = []
272 index = -1
273 for item in ['Top-Left', 'Top-Center', 'Top-Right', 'Middle-Left', 'Center-Screen', 'Middle-Right', 'Bottom-Left', 'Bottom-Center', 'Bottom-Right']:
274 index += 1
275 if index == 0:
276 self.optDefPos.append(wx.RadioButton(page, -1, item, style=wx.RB_GROUP))
277 else:
278 self.optDefPos.append(wx.RadioButton(page, -1, item))
279 flxszr.Add(self.optDefPos[index])
280 flxszr.AddGrowableCol(0, 1)
281 flxszr.AddGrowableCol(1, 1)
282 box2.Add(flxszr, 0, wx.ALL|wx.EXPAND, 5)
283 box.Add(box2, 0, wx.ALL|wx.EXPAND, 5)
284
285 pageszr.Add(box, 0, wx.LEFT|wx.RIGHT|wx.EXPAND, 5)
286
287 box = wx.StaticBoxSizer(wx.VERTICAL, page, 'Misc:')
288 self.chkSingleInstance = wx.CheckBox(page, -1, 'Allow only one instance of the application')
289 box.Add(self.chkSingleInstance, 0, wx.LEFT|wx.RIGHT|wx.BOTTOM, 5)
290 self.chkCompFile = wx.CheckBox(page, -1, 'Compress settings file (for size and privacy)')
291 box.Add(self.chkCompFile, 0, wx.LEFT|wx.RIGHT|wx.BOTTOM, 5)
292 pageszr.Add(box, 0, wx.ALL|wx.EXPAND, 5)
293
294 page.SetSizer(pageszr)
295 nBook.AddPage(page, 'General')
296
297 szrMain.Add(nBook, 1, wx.ALL|wx.EXPAND, 0)
298
299 btnSizer = self.CreateSeparatedButtonSizer(wx.OK|wx.CANCEL|wx.APPLY)
300
301 szrMain.Add(btnSizer, 0, wx.ALL|wx.EXPAND, 5)
302
303 self.btnApply = self.FindWindowById(wx.ID_APPLY)
304
305 self.SetSizer(szrMain)
306 szrMain.SetSizeHints(self)
307
308 def _connectEvents(self):
309 self.Bind(wx.EVT_BUTTON, self.OnBtnOkClicked, id=wx.ID_OK)
310 self.Bind(wx.EVT_BUTTON, self.OnBtnApplyClicked, id=wx.ID_APPLY)
311 for item in self.optStartType:
312 item.Bind(wx.EVT_RADIOBUTTON, self.OnStartupPosTypeChanged)
313 self.Bind(wx.EVT_CHECKBOX, self.OnSomethingHasChanged)
314 self.Bind(wx.EVT_RADIOBUTTON, self.OnSomethingHasChanged)
315
316 def _fillControls(self):
317 s = MySettingsManager()
318 iStartPos, _ = s.MainWndStartupPos
319 if iStartPos == wx.ALIGN_NOT:
320 self.optStartType[1].SetValue(True)
321 self.optDefPos[4].SetValue(True)
322 else:
323 self.optStartType[0].SetValue(True)
324 if iStartPos == (wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL):
325 self.optDefPos[4].SetValue(True)
326 else:
327 iX = 0
328 if (iStartPos&wx.RIGHT) == wx.RIGHT:
329 iX = 2
330 elif (iStartPos&wx.LEFT) == wx.LEFT:
331 iX = 0
332 else:
333 iX = 1
334 iY = 0
335 if (iStartPos&wx.BOTTOM) == wx.BOTTOM:
336 iY = 6
337 elif (iStartPos&wx.TOP) == wx.TOP:
338 iY = 0
339 else:
340 iY = 3
341 self.optDefPos[iX+iY].SetValue(True)
342 self.OnStartupPosTypeChanged(None)
343
344 self.chkSingleInstance.SetValue(s.MultipleInstancesAllowed is False)
345 self.chkCompFile.SetValue(s.CompressSettingsFile)
346
347 self.btnApply.Disable()
348
349 def _applySettings(self):
350 s = MySettingsManager()
351 iIndex = wx.NOT_FOUND
352 if self.optStartType[0].GetValue() is True:
353 for i, o in enumerate(self.optDefPos):
354 if o.GetValue() is True:
355 iIndex = i
356 break
357 iStartPos = wx.ALIGN_NOT
358 if iIndex > wx.NOT_FOUND:
359 iH = 0
360 if iIndex in [0, 3, 6]:
361 iH = wx.LEFT
362 elif iIndex in [1, 4, 7]:
363 iH = wx.CENTER
364 else:
365 iH = wx.RIGHT
366 iV = 0
367 if (iIndex > -1) and (iIndex < 3):
368 iV = wx.TOP
369 elif (iIndex > 2) and (iIndex < 6):
370 iV = wx.CENTER
371 else:
372 iV = wx.BOTTOM
373 if (iH == wx.CENTER) and (iV == wx.CENTER):
374 iStartPos = (wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
375 else:
376 iStartPos = iH | iV
377 s.MainWndStartupPos = iStartPos
378
379 s.MultipleInstancesAllowed = self.chkSingleInstance.IsChecked() is False
380 s.CompressSettingsFile = self.chkCompFile.IsChecked()
381 self.btnApply.Disable()
382
383 def OnBtnApplyClicked(self, evt):
384 self._applySettings()
385
386 def OnBtnOkClicked(self, evt):
387 self._applySettings()
388 self.EndModal(wx.ID_OK)
389
390 def OnStartupPosTypeChanged(self, evt):
391 bEnable = self.optStartType[0].GetValue()
392 self.stbPos.Enable(bEnable)
393 for item in self.optDefPos:
394 item.Enable(bEnable)
395 self.OnSomethingHasChanged(evt)
396
397 def OnSomethingHasChanged(self, evt):
398 self.btnApply.Enable()
399
400 #---------------------------------------------------------------------------
401
402 class MyFrame(wx.Frame):
403 def __init__(self):
404 wx.Frame.__init__(self, None, title=wx.GetApp().GetAppName())
405 self.SetIcon(wx.Icon('wxwin.ico'))
406
407 self._createControls()
408
409 # Minimum client size of the main window
410 self.SetMinClientSize(wx.Size(400, 300))
411
412 s = MySettingsManager()
413 mode, pos = s.MainWndStartupPos
414 sz = s.MainWndStartupSize
415 if mode == wx.ALIGN_NOT:
416 if sz == wx.DefaultSize:
417 if pos == wx.DefaultPosition:
418 self.Maximize()
419 else:
420 self.CenterOnScreen()
421 else:
422 self.Move(pos)
423 self.SetSize(sz)
424 else:
425 iWScr, iHScr = wx.GetDisplaySize()
426 if sz == wx.DefaultSize:
427 sz = self.GetSize()
428 pt = wx.DefaultPosition
429 if (mode & wx.LEFT) == wx.LEFT:
430 pt.x = 0
431 elif (mode & wx.RIGHT) == wx.RIGHT:
432 pt.x = iWScr - sz.GetWidth()
433 else:
434 pt.x = (iWScr - sz.GetWidth()) // 2
435 if (mode & wx.TOP) == wx.TOP:
436 pt.y = 0
437 elif (mode & wx.BOTTOM) == wx.BOTTOM:
438 pt.y = iHScr - sz.GetHeight()
439 else:
440 pt.y = (iHScr - sz.GetHeight()) // 2
441 self.Move(pt)
442 self.SetSize(sz)
443
444 self._connectControls()
445
446 def _createControls(self):
447 # A Statusbar in the bottom of the window
448 self.CreateStatusBar(1)
449 sMsg = 'wxPython ' + wx.version()
450 self.SetStatusText(sMsg)
451
452 # Add a panel to the frame (needed under Windows to have a nice background)
453 pnl = wx.Panel(self, wx.ID_ANY)
454
455 szrMain = wx.BoxSizer(wx.VERTICAL)
456 self._btnPrefs = wx.Button(pnl, wx.ID_ANY, 'Edit Settings')
457 szrMain.Add(self._btnPrefs, 0, wx.ALL, 5)
458 pnl.SetSizer(szrMain)
459
460 def _connectControls(self):
461 self.Bind(wx.EVT_SIZE, self._onWindowResized)
462 self.Bind(wx.EVT_MOVE, self._onWindowMoved)
463 self._btnPrefs.Bind(wx.EVT_BUTTON, self._onBtnEditPrefstClicked)
464
465 def _onWindowResized(self, evt):
466 if not self.IsShown():
467 return
468 s = MySettingsManager()
469 if self.IsMaximized():
470 s.MainWndStartupPos = wx.DefaultPosition
471 s.MainWndStartupSize = wx.DefaultSize
472 else:
473 s.MainWndStartupRect = self.GetRect()
474
475 evt.Skip()
476
477 def _onWindowMoved(self, evt):
478 if not self.IsShown():
479 return
480 MySettingsManager().MainWndStartupRect = self.GetRect()
481
482 evt.Skip()
483
484 def _onBtnEditPrefstClicked(self, evt):
485 dlg = MyDlgSettings(self)
486 dlg.ShowModal()
487 dlg.Destroy()
488
489 #---------------------------------------------------------------------------
490
491 class MyApp(wx.App):
492 _snglInstChecker = None
493
494 def OnInit(self):
495 print('Running wxPython ' + wx.version())
496 # Set Current directory to the one containing this file
497 os.chdir(os.path.dirname(os.path.abspath(__file__)))
498
499 self.SetAppName('SettingsManager')
500
501 s = MySettingsManager()
502 s.ReadSettings()
503 self._snglInstChecker = wx.SingleInstanceChecker()
504 self._snglInstChecker.CreateDefault()
505 if self._snglInstChecker.IsAnotherRunning() and not s.MultipleInstancesAllowed:
506 wx.MessageBox('Another instance of this application is already running!', 'Multiple instances not allowed', wx.OK | wx.ICON_EXCLAMATION | wx.CENTER)
507 return False
508
509 # Create the main window
510 frm = MyFrame()
511 self.SetTopWindow(frm)
512
513 frm.Show()
514 return True
515
516 def OnExit(self):
517 s = MySettingsManager()
518 if s.Modified is True:
519 s.SaveSettings()
520 return wx.App.OnExit(self)
521
522 #---------------------------------------------------------------------------
523
524 if __name__ == '__main__':
525 app = MyApp()
526 app.MainLoop()
Download source
Additional Information
Link :
- - - - -
https://wiki.wxpython.org/TitleIndex
Thanks to
Xaviou (sample_one.py coding), the wxPython community...
About this page
Date(d/m/y) Person (bot) Comments :
28/08/19 - Ecco (Created page for wxPython Phoenix).
14/01/23 - Ecco (Updated page and "source.zip" for Python 3.10.5).
Comments
- blah, blah, blah....