Welcome, guest | Sign In | My Account | Store | Cart

The following code implements a splash screen as typically found in windows applications. Using only API's available from win32gui, win32api and win32con, it avoids dependancy on MFC (wrapped by win32ui). This way the 600kb or so win32ui.pyd extension DLL is not needed when freezing your app with py2exe. Another 80kb could be squeezed out by not using win32con but to define the necessary constants directly in the code itself.

Python, 112 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
import win32gui
import win32api
import win32con
import struct

IDC_BITMAP = 1028

g_registeredClass = 0

class Splash:
    def __init__(self, bitmapPath):
        win32gui.InitCommonControls()
        self.hinst = win32api.GetModuleHandle(None)

        #retreive width and height from bitmap file, because GetObject does not work for bitmaps :-(
        f = open(bitmapPath, 'rb')
        hdrfm = '<18xii'
        self.bmWidth, self.bmHeight = struct.unpack(hdrfm, f.read(struct.calcsize(hdrfm)))
        f.close()

        self.hSplash = win32gui.LoadImage(self.hinst, bitmapPath, win32con.IMAGE_BITMAP, 
                                          0, 0, win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE)
        
            
    def _RegisterWndClass(self):
        className = "PythonSplash"
        global g_registeredClass
        if not g_registeredClass:
            message_map = {}
            wc = win32gui.WNDCLASS()
            wc.SetDialogProc() # Make it a dialog class.
            self.hinst = wc.hInstance = win32api.GetModuleHandle(None)
            wc.lpszClassName = className
            wc.style = 0
            wc.hCursor = win32gui.LoadCursor( 0, win32con.IDC_ARROW )
            wc.hbrBackground = win32con.COLOR_WINDOW + 1
            wc.lpfnWndProc = message_map # could also specify a wndproc.
            wc.cbWndExtra = win32con.DLGWINDOWEXTRA + struct.calcsize("Pi")
            classAtom = win32gui.RegisterClass(wc)
            g_registeredClass = 1
        return className

    def _GetDialogTemplate(self, dlgClassName):
        style = win32con.WS_POPUP

        dlg = [ ["", (0, 0, 0, 0), style, None, (8, "MS Sans Serif"), None, dlgClassName], ]

        dlg.append([130, "", IDC_BITMAP, (0, 0, 0, 0), win32con.WS_VISIBLE | win32con.SS_BITMAP])

        return dlg

    def CreateWindow(self):
        self._DoCreate(win32gui.CreateDialogIndirect)

    def DoModal(self):
        return self._DoCreate(win32gui.DialogBoxIndirect)

    def _DoCreate(self, fn):
        message_map = {
            win32con.WM_INITDIALOG: self.OnInitDialog,
            win32con.WM_CLOSE: self.OnClose,
        }
        dlgClassName = self._RegisterWndClass()
        template = self._GetDialogTemplate(dlgClassName)
        return fn(self.hinst, template, 0, message_map)


    def OnInitDialog(self, hwnd, msg, wparam, lparam):
        self.hwnd = hwnd

        desktop = win32gui.GetDesktopWindow()
        dt_l, dt_t, dt_r, dt_b = win32gui.GetWindowRect(desktop)
        centre_x, centre_y = win32gui.ClientToScreen( desktop, ( (dt_r-dt_l)/2, (dt_b-dt_t)/2) )

        bmCtrl = win32gui.GetDlgItem(self.hwnd, IDC_BITMAP)
        win32gui.SendMessage(bmCtrl, win32con.STM_SETIMAGE, win32con.IMAGE_BITMAP, self.hSplash)

        win32gui.SetWindowPos(self.hwnd, win32con.HWND_TOPMOST, 
                              centre_x-(self.bmWidth/2), centre_y-(self.bmHeight/2), 
                              self.bmWidth, self.bmHeight, win32con.SWP_HIDEWINDOW)
        win32gui.SetForegroundWindow(self.hwnd)
        
        
    def Show(self):
        win32gui.ShowWindow(self.hwnd, win32con.SW_SHOW)
      
    def Timer(self, timeOut):
        import time
        time.sleep(timeOut)
        self.EndDialog()

    def EndDialogAfter(self, timeOut):
        #thread needed because win32gui does not expose SetTimer API
        import thread
        thread.start_new_thread(self.Timer, (timeOut, ))
    
    def EndDialog(self):
        win32gui.EndDialog(self.hwnd, 0)
        
    def OnClose(self, hwnd, msg, wparam, lparam):
        self.EndDialog()
    
if __name__=='__main__':
    s = Splash("skins\\splash.bmp")
    s.DoModal()
    
    # or use:
    #s.CreateWindow()
    #s.Show()
    #s.EndDialogAfter([timeout in seconds])
    #
    # (then make sure you PumpMessages() elsewhere

I'm interested to know if the code works for the various win32 implementations (95, ME, XP etc) as I only have access to windows 2000 machines. Furthermore there are two 'warts' in this code. One is the fact that the GetObject win32 API as wrapped by python does not seem to work for bitmaps (GetObject could be used to retreive its width and height). Also the SetTimer API is not exposed by either win32gui or win32api, therefore an extra thread is needed to make the splash screen disappear after some timeout.