How to create a splash screen while loading (Phoenix)

Keywords : Splashscreen, Loading, Timeout.


Introduction

AdvancedSplash is an easy way to add a splash screen to a wxPython application, but its use is not without pitfalls.

One of those is that programmers typically include a splash screen when their program takes a long time to load. This gives the user some feedback so they know that something is happening and that they shouldn't try to launch the application again.

The problem you will face, however, is that a long load time typically means a long time before the program execution gets to the App.MainLoop() command; and until the MainLoop is entered, the splash screen will not display correctly.

There are a lot of ways to tackle this problem. The simplest is probably to do as much initialization as possible in a function launched with a wx.CallLater call (wx.CallAfter doesn't seem to work from an OnInit function). Unfortunately, you may still face delays (such as long loads during imports) and the user interface will not be responsive until your initializer completes (so the user will not be able to click on the splash to dismiss it).

Resist the temptation to put your intialization in a background thread! This is valid only if no wx calls are made in the initialization. Never put any wx call (besides wx.CallAfter) in a background thread.

Another solution is to fork the program's execution. Display the splash screen in one fork and launch the application in the other.

Note: The following example has been written for Windows. There's no fork command in the Windows version of Python, so I'll be using spawnl instead. Feel free to post a Linux/Mac version of the following.


Demonstrating :

Tested py3.x, wx4.x and Win10.

Are you ready to use some samples ? ;)

Test, modify, correct, complete, improve and share your discoveries ! (!)


Example

The following example simulates 1.3 seconds of delay in doing all of my imports and 6 seconds of delay in loading my objects from the xrc file. These numbers were taken from an actual application.

Note how I put the splash screen code at the top of the file. This lets me get it done when only a minimal set of libraries are loaded.

img_sample_one.png

ICON file : icon_wxWidgets.ico

PNG file : splash.png

   1 # sample_one.py
   2 
   3 import os
   4 import sys
   5 import wx
   6 import wx.lib.agw.advancedsplash as AS
   7 
   8 SHOW_SPLASH = True
   9 SPLASH_FN = "splash.png"
  10 SPLASH_TIME = 5000
  11 
  12 # class MyFrame
  13 # class MyApp
  14 
  15 #-------------------------------------------------------------------------------
  16 
  17 # Test to see if we need to show a splash screen.
  18 # If the splash is enabled (and we're not the application fork),
  19 # then show a splash screen and relaunch the same application
  20 # except as the application fork.
  21 
  22 if __name__ == "__main__":
  23     AppFN = sys.argv[0]
  24 
  25     if SHOW_SPLASH and (len(sys.argv) == 1) and AppFN.endswith(".exe"):
  26         App = wx.App()
  27 
  28         #--
  29 
  30         bitmap = wx.Bitmap(SPLASH_FN, wx.BITMAP_TYPE_PNG)
  31         shadow = wx.RED
  32 
  33         frame = AS.AdvancedSplash(None,
  34                                   bitmap=bitmap,
  35                                   timeout=SPLASH_TIME,
  36                                   agwStyle=AS.AS_TIMEOUT |
  37                                            AS.AS_CENTER_ON_PARENT |
  38                                            AS.AS_SHADOW_BITMAP,
  39                                   shadowcolour=shadow)
  40 
  41         #--
  42 
  43         os.spawnl(os.P_NOWAIT,
  44                   AppFN,
  45                   '"%s"' % AppFN.replace('"', r'\"'),
  46                   "NO_SPLASH")
  47 
  48         #--
  49 
  50         App.MainLoop()
  51         sys.exit()
  52 
  53 #-------------------------------------------------------------------------------
  54 
  55 import time
  56 
  57 # Simulate 1.3s of time spent importing libraries and source files.
  58 time.sleep(1.3)
  59 
  60 #---------------------------------------------------------------------------
  61 
  62 class MyFrame(wx.Frame):
  63     """
  64     ...
  65     """
  66     def __init__(self):
  67         super(MyFrame, self).__init__(None,
  68                                       -1,
  69                                       title="")
  70 
  71         #------------
  72 
  73         # Return application name.
  74         self.app_name = wx.GetApp().GetAppName()
  75 
  76         #------------
  77 
  78         # Simplified init method.
  79         self.SetProperties()
  80         self.CreateCtrls()
  81         self.BindEvents()
  82         self.DoLayout()
  83 
  84         #------------
  85 
  86         self.CenterOnScreen()
  87 
  88     #-----------------------------------------------------------------------
  89 
  90     def SetProperties(self):
  91         """
  92         ...
  93         """
  94 
  95         self.SetTitle(self.app_name)
  96 
  97         #------------
  98 
  99         frameicon = wx.Icon("icon_wxWidgets.ico")
 100         self.SetIcon(frameicon)
 101 
 102 
 103     def CreateCtrls(self):
 104         """
 105         ...
 106         """
 107 
 108         # Create a panel.
 109         self.panel = wx.Panel(self, -1)
 110 
 111         #------------
 112 
 113         # Add a button.
 114         self.btnClose = wx.Button(self.panel,
 115                                   -1,
 116                                   "&Close")
 117 
 118 
 119     def BindEvents(self):
 120         """
 121         Bind some events to an events handler.
 122         """
 123 
 124         # Bind the close event to an event handler.
 125         self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
 126 
 127         # Bind the button event to an event handler.
 128         self.Bind(wx.EVT_BUTTON, self.OnCloseMe, self.btnClose)
 129 
 130 
 131     def DoLayout(self):
 132         """
 133         ...
 134         """
 135 
 136         # MainSizer is the top-level one that manages everything.
 137         mainSizer = wx.BoxSizer(wx.VERTICAL)
 138 
 139         # wx.BoxSizer(window, proportion, flag, border)
 140         # wx.BoxSizer(sizer, proportion, flag, border)
 141         mainSizer.Add(self.btnClose, 1, wx.EXPAND | wx.ALL, 10)
 142 
 143         # Finally, tell the panel to use the sizer for layout.
 144         self.panel.SetAutoLayout(True)
 145         self.panel.SetSizer(mainSizer)
 146 
 147         mainSizer.Fit(self.panel)
 148 
 149     #-----------------------------------------------------------------------
 150 
 151     def OnCloseMe(self, event):
 152         """
 153         ...
 154         """
 155 
 156         self.Close(True)
 157 
 158 
 159     def OnCloseWindow(self, event):
 160         """
 161         ...
 162         """
 163 
 164         self.Destroy()
 165 
 166 #---------------------------------------------------------------------------
 167 
 168 class MyApp(wx.App):
 169     """
 170     ...
 171     """
 172     def OnInit(self):
 173 
 174         #------------
 175 
 176         self.SetAppName("Main frame")
 177 
 178         #------------
 179 
 180         # Simulate 6s of time spent initializing wx components.
 181         time.sleep(6)
 182 
 183         #------------
 184 
 185         self.frame = MyFrame()
 186         self.frame.Show(True)
 187 
 188         return True
 189 
 190 #---------------------------------------------------------------------------
 191 
 192 def main():
 193     app = MyApp(False)
 194     app.MainLoop()
 195 
 196 #---------------------------------------------------------------------------
 197 
 198 if __name__ == "__main__" :
 199     main()


Distributing application

Here's a "setup.py" file I used to compile it to an ".exe" :

SETUP file : setup.py

   1 # setup.py
   2 
   3 #-------------------------------------------------------------------------------
   4 # Import cx_Freeze packages.
   5 #-------------------------------------------------------------------------------
   6 
   7 from cx_Freeze import setup, Executable
   8 
   9 
  10 # Dependencies are automatically detected, but it might need fine tuning.
  11 options = {"packages": [], "excludes": ["tkinter"]}
  12 
  13 #-------------------------------------------------------------------------------
  14 # Application Information - Create setup.
  15 #-------------------------------------------------------------------------------
  16 
  17 setup(name="sample_one" ,
  18       version="0.1" ,
  19       description="Test",
  20       author="wxPython",
  21       options={"build_exe": options},
  22       executables = [Executable(script="sample_one.py",
  23                                 base="Win32GUI",
  24                                 icon="icon_wxWidgets.ico")])


Download source

source.zip


Additional Information

Link :

https://wiki.wxpython.org/Splash%20screen%20%28Phoenix%29

https://wiki.wxpython.org/Customized%20splash%20screen%20%28Phoenix%29

- - - - -

https://wiki.wxpython.org/TitleIndex

https://docs.wxpython.org/


Thanks to

??? (sample_one.py coding), the wxPython community...


About this page

Date (d/m/y) Person (bot) Comments :

12/06/18 - Ecco (Updated page and modified example for wxPython Phoenix).


Comments

- blah, blah, blah....

How to create a splash screen while loading (Phoenix) (last edited 2020-12-13 17:09:57 by Ecco)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.