ScreenSaver |
| Description: | |
| This sample code consists of three files. A form for the screen saver itself, that just displays a big "X" in the example. A class and module that provide the necessary support for all of the screen saver specific functionality. This is a very complete framework, it supports the basic screen saver functionality, as well as support for the standard password dialog under Win9x, and it displays a dummy settings message box, which can easily be replaced with a more complex settings screen. It even supports the small preview window in the display properties dialog. I've used this to create a new mail display, and have been quite pleased with the results. 4/20/2000: Added some logic to block SC_SCREENSAVE messages, to prevent problems on NT. 11/10/2003: Added support for multi-mon so the main window is the proper size to span all monitors. | |
| Code: | |
' ---------------------------------------------------------------------
' Begin: From frmScreenSaver
'This is the screen saver itself. Because all of the screen saver code
' is in mod/clsScreenSaver, this is just concerned with what to draw on
' the screen while it's displayed. A couple of notes however. First,
' this form should have the following properties set:
'
' BorderStyle = 0 - None
' BackColor = (Black)
' KeyPreview = True
'
' Also, you're responsible for tracking the mouse cursor in any child
' controls. Ideally, just put the following in any of the child's
' _MouseMove events:
'
' PostMessage Me.hwnd, WM_MOUSEMOVE, 0, 0
'
' From there the framework will close the screensaver if necessary
'
' Finally, make sure and set the start up to Sub Main()
Option Explicit
Private Sub Form_Paint()
'Just draw a stupid 'X' on the form to verify it's size and
' and position both full screen and in the display
' properties dialog.
Line (0, 0)-(Width, Height), vbWhite
Line (Width, 0)-(0, Height), vbWhite
End Sub
' End: From frmScreenSaver
' ---------------------------------------------------------------------
' ---------------------------------------------------------------------
' Begin: Module modScreenSaver
Option Explicit
'Mutex name to prevent multiple versions
Public Const MUTEX_NAME = "Scott's Screen Saver Mutex"
'ID of timer to dismiss password window after 30 seconds
Public Const TIMER_ID = 10
'Windows API
Type POINTAPI
X As Long
Y As Long
End Type
Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Type OSVERSIONINFO
dwOSVersionInfoSize As Long
dwMajorVersion As Long
dwMinorVersion As Long
dwBuildNumber As Long
dwPlatformId As Long
szCSDVersion As String * 128
End Type
Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal _
hWndInsertAfter As Long, ByVal X As Long, ByVal Y As Long, _
ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As _
Long
Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal _
hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal _
lParam As Long) As Long
Declare Function ShowCursor Lib "user32" (ByVal bShow As Long) As Long
Declare Function GetTickCount Lib "kernel32" () As Long
Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" _
(ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As _
Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
(ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As _
Long) As Long
Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" _
(ByVal hwnd As Long, ByVal nIndex As Long) As Long
Declare Function SetParent Lib "user32" (ByVal hWndChild As Long, ByVal _
hWndNewParent As Long) As Long
Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect _
As RECT) As Long
Declare Function CreateMutex Lib "kernel32" Alias "CreateMutexA" _
(lpMutexAttributes As Any, ByVal bInitialOwner As Long, ByVal _
lpName As String) As Long
Declare Function ReleaseMutex Lib "kernel32" (ByVal hMutex As Long) As _
Long
Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As _
Long
Declare Function PwdChangePassword Lib "mpr" Alias "PwdChangePasswordA" _
(ByVal lpcRegkeyname As String, ByVal hwnd As Long, ByVal _
uiReserved1 As Long, ByVal uiReserved2 As Long) As Long
Declare Function VerifyScreenSavePwd Lib "password.cpl" (ByVal hwnd As _
Long) As Long
Declare Function GetVersionEx Lib "kernel32" Alias "GetVersionExA" _
(lpVersionInformation As OSVERSIONINFO) As Long
Declare Function SystemParametersInfo Lib "user32" Alias _
"SystemParametersInfoA" (ByVal uAction As Long, ByVal uParam _
As Long, lpvParam As Long, ByVal fuWinIni As Long) As Long
Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, ByVal _
nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As _
Long) As Long
Declare Function KillTimer Lib "user32" (ByVal hwnd As Long, ByVal _
nIDEvent As Long) As Long
Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal bScan As _
Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) _
As Long
Public Const KEYEVENTF_KEYUP = &H2
Public Const VK_ESCAPE = &H1B
Public Const WM_TIMER = &H113
Public Const SPI_SCREENSAVERRUNNING = 97
Public Const VER_PLATFORM_WIN32_NT = 2
Public Const WS_CHILD = &H40000000
Public Const GWL_WNDPROC = (-4)
Public Const WM_MOUSEMOVE = &H200
Public Const WM_CLOSE = &H10
Public Const HWND_TOPMOST = -1
Public Const SWP_SHOWWINDOW = &H40
Public Const GWL_STYLE = (-16)
Public Const ERROR_ALREADY_EXISTS = 183&
Public Const WM_ACTIVATE = &H6
Public Const WM_ACTIVATEAPP = &H1C
Public Const WM_SYSCOMMAND = &H112
Public Const SC_SCREENSAVE = &HF140&
Public Const SM_XVIRTUALSCREEN = 76
Public Const SM_YVIRTUALSCREEN = 77
Public Const SM_CXVIRTUALSCREEN = 78
Public Const SM_CYVIRTUALSCREEN = 79
Private m_ScreenSaver As clsScreenSaver 'Screen saver class, mostly
' used to catch the form's events
Private m_hWnd As Long 'hWnd of main form
Private m_lpPrevProc As Long 'Previous window procedure
Private m_hMutex As Long 'Handle to mutex to prevent multiple instances
Sub Main()
DoEvents 'Gets rid of stray windows events
Dim rt As RECT
Dim hwnd As Long
Select Case LCase(Mid(Command$, 1, 2))
Case "/a" 'Password
'Trigger the change password dialog
PwdChangePassword "SCRSAVE", Val(Mid(Command$, 3)), 0&, 0&
Case "", "/s" 'Show screen saver
'Create a mutex to prevent two instances of the screen
' saver starting. This is used vs App.PrevInstance
' because I want to allow a preview window and the
' screen saver to co-exist
m_hMutex = CreateMutex(ByVal 0, True, MUTEX_NAME & _
"[Saver]")
'Did the mutex already exist?
If Err.LastDllError = ERROR_ALREADY_EXISTS Then
ReleaseMutex m_hMutex
CloseHandle m_hMutex
Exit Sub
End If
If m_hMutex = 0 Then
Exit Sub
End If
'Create the screensaver class and point it to our form
Set m_ScreenSaver = New clsScreenSaver
Set m_ScreenSaver.Form = frmScreenSaver
Load frmScreenSaver
'Display the form full-screen and topmost
Dim nWidth As Long
Dim nHeight As Long
Dim nLeft As Long
Dim nTop As Long
' Attempt to find Multi-mon information
nWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN)
nHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN)
nLeft = GetSystemMetrics(SM_XVIRTUALSCREEN)
nTop = GetSystemMetrics(SM_YVIRTUALSCREEN)
If nWidth <> 0 And nHeight <> 0 Then
SetWindowPos frmScreenSaver.hwnd, HWND_TOPMOST, nLeft, _
nTop, nWidth, nHeight, SWP_SHOWWINDOW
Else
'It looks like we couldn't find multi-mon info,
' just fall back to VB's intrinsic handlers
SetWindowPos frmScreenSaver.hwnd, HWND_TOPMOST, 0, 0, _
Screen.Width / Screen.TwipsPerPixelX, _
Screen.Height / Screen.TwipsPerPixelY, _
SWP_SHOWWINDOW
End If
Case "/p" 'Tiny preview window the display properties
'Create the screensaver class and let it know
' that it's in a preview window
Set m_ScreenSaver = New clsScreenSaver
Set m_ScreenSaver.Form = frmScreenSaver
m_ScreenSaver.IsPreview = True
'Make the main form a child of the display properties
SetWindowLong frmScreenSaver.hwnd, GWL_STYLE, _
GetWindowLong(frmScreenSaver.hwnd, GWL_STYLE) _
Or WS_CHILD
SetParent frmScreenSaver.hwnd, Val(Mid(Command$, 3))
'Get the size of the little display window
GetWindowRect Val(Mid(Command$, 3)), rt
Load frmScreenSaver
'Show the window, not topmost, and the size specified
SetWindowPos frmScreenSaver.hwnd, 0, 0, 0, rt.Right - _
rt.Left, rt.Bottom - rt.Top, SWP_SHOWWINDOW
Case "/c" 'Settings
'Use a mutex to prevent multiple display settings
' dialogs
m_hMutex = CreateMutex(ByVal 0, True, MUTEX_NAME & _
"[Setting]")
If Err.LastDllError = ERROR_ALREADY_EXISTS Then
ReleaseMutex m_hMutex
CloseHandle m_hMutex
Exit Sub
End If
If m_hMutex = 0 Then
Exit Sub
End If
' I was lazy, you'll want to replace this with your
' own form
MsgBox "No settings", vbInformation, "Sorry"
ReleaseMutex m_hMutex
CloseHandle m_hMutex
End Select
End Sub
Public Sub Hook(hwnd As Long)
'Procedure to subclass the main form
m_hWnd = hwnd
m_lpPrevProc = SetWindowLong(m_hWnd, GWL_WNDPROC, AddressOf WndProc)
End Sub
Public Sub Unhook()
'Cease subclassing of the main form
SetWindowLong m_hWnd, GWL_WNDPROC, m_lpPrevProc
ReleaseMutex m_hMutex
CloseHandle m_hMutex
End Sub
Public Function WndProc(ByVal hwnd As Long, ByVal uMsg As Long, ByVal _
wParam As Long, ByVal lParam As Long) As Long
' This is the subclassed window procedure, basically, look for
' loss of focus, and the timer to dismiss the password dialog
Dim bBlockMsg As Boolean 'Block this message from being sent
Select Case uMsg
Case WM_SYSCOMMAND
If wParam = SC_SCREENSAVE Then
'Stop WinNT from trying to re-enable the screen saver
' and disabling it if the password is on
bBlockMsg = True
End If
Case WM_ACTIVATE, WM_ACTIVATEAPP
If wParam = 0 Then
PostMessage m_hWnd, WM_MOUSEMOVE, 0, 0
End If
Case WM_TIMER
If m_ScreenSaver.IsPasswordVisible Then
If GetTickCount - m_ScreenSaver.PasswordShown > 30000 _
Then
keybd_event VK_ESCAPE, 0, 0, 0
keybd_event VK_ESCAPE, 0, KEYEVENTF_KEYUP, 0
End If
End If
End Select
'Pass the message to VB's window procedure if it's not blocked
If Not bBlockMsg Then
WndProc = CallWindowProc(m_lpPrevProc, hwnd, uMsg, _
wParam, lParam)
End If
End Function
' End: Module modScreenSaver
' ---------------------------------------------------------------------
' ---------------------------------------------------------------------
' Begin: Class Module clsScreenSaver
Option Explicit
Private WithEvents m_Form As Form 'The form that this class
' is watching
Private m_bPreview As Boolean 'Are we in the display properies?
Private m_ptLast As POINTAPI 'The last seen mouse position
Private m_bLast As Boolean 'Is the last seen mouse position valid
Private m_nTickLoad As Long 'Time that the form loaded
Private m_bPasswordVisible As Boolean 'Pwd dialog displayed?
Private m_nPasswordShown As Long 'Time pwd dialog shown
Public Property Get PasswordShown() As Long
PasswordShown = m_nPasswordShown
End Property
Public Property Get IsPasswordVisible() As Boolean
IsPasswordVisible = m_bPasswordVisible
End Property
Public Property Set Form(newForm As Form)
Set m_Form = newForm
End Property
Public Property Let IsPreview(bPreview As Boolean)
m_bPreview = bPreview
End Property
Public Property Get IsPreview() As Boolean
IsPreview = m_bPreview
End Property
Private Sub Class_Initialize()
'Just make note of the display time, that way we can ignore
' mouse and keyboard events for 1/2 a second, so the stray
' events don't trigger the unload
m_nTickLoad = GetTickCount
End Sub
Private Sub m_Form_KeyUp(KeyCode As Integer, Shift As Integer)
'Key press, call the generic unload procedure
TriggerUnload
End Sub
Private Sub m_Form_Load()
If Not m_bPreview Then
Hook m_Form.hwnd ' Subclass this form to watch for
' the timer and focus events
ShowCursor False 'Hide the mouse cursor
Dim nTemp As Long
' Let windows know we're running, primarily to
' prevent the system keys
SystemParametersInfo SPI_SCREENSAVERRUNNING, 1, nTemp, 0
End If
End Sub
Private Sub m_Form_MouseMove(Button As Integer, Shift As Integer, X As _
Single, Y As Single)
If Not m_bLast Then
'If the last mouse cursor position hasn't been used yet,
' save the current position
GetCursorPos m_ptLast
m_bLast = True
Exit Sub
End If
'Compare the last with the current, if they differ, trigger
' the generic unload procedure
Dim pt As POINTAPI
GetCursorPos pt
If pt.X <> m_ptLast.X Or pt.Y <> m_ptLast.Y Then
TriggerUnload
m_ptLast.X = pt.X
m_ptLast.Y = pt.Y
End If
End Sub
Private Sub m_Form_MouseUp(Button As Integer, Shift As Integer, X As _
Single, Y As Single)
'Mouse button pressed, trigger the generic unload procedure
TriggerUnload
End Sub
Private Sub m_Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
'Make sure we don't re-enter ourselves
Static bInSub As Boolean
If bInSub Then
Exit Sub
End If
bInSub = True
'This just displays the password dialog if the screen saver
' isn't being run in the display properties dialog.
If Not m_bPreview Then
If Not VerifyPassword Then
Cancel = True
Else
Exit Sub
End If
End If
' Make note of the time incase the password was wrong
m_nTickLoad = GetTickCount
bInSub = False
End Sub
Private Sub m_Form_Unload(Cancel As Integer)
'Undo what we did in Form_Load
If Not m_bPreview Then
Unhook
ShowCursor True
Dim nTemp As Long
SystemParametersInfo SPI_SCREENSAVERRUNNING, False, nTemp, 0
End If
End Sub
Private Sub TriggerUnload()
' Check to see if we should unload because of user actions,
' if so, then check to see if it's been atleast 1/2 a
' second since we started.
If m_bPreview Then
Exit Sub
End If
If GetTickCount - m_nTickLoad > 500 Then
'Just use postmessage to avoid the problems with
' forcing and unload this very instant
PostMessage m_Form.hwnd, WM_CLOSE, 0, 0
End If
End Sub
Public Function VerifyPassword() As Boolean
'Display and verify the users password
'Check to see if this is NT. If it is, let the os handle
' the password
Dim osv As OSVERSIONINFO
osv.dwOSVersionInfoSize = Len(osv)
GetVersionEx osv
If osv.dwPlatformId = VER_PLATFORM_WIN32_NT Then
VerifyPassword = True
Exit Function
End If
ShowCursor True 'The cursor should be visible for this
m_bPasswordVisible = True
m_nPasswordShown = GetTickCount
SetTimer m_Form.hwnd, TIMER_ID, 1000, 0 'Used to dismiss
'the password dialog after 30 seconds
'Call the DLL function and return it's result
VerifyPassword = (VerifyScreenSavePwd(m_Form.hwnd) <> 0)
'Kill the timer, and hide the cursor (if the password is
' right, the cursor will be redisplayed when the form is
' killed
KillTimer m_Form.hwnd, TIMER_ID
m_bPasswordVisible = False
ShowCursor False
End Function
' End: Class Module clsScreenSaver
' ---------------------------------------------------------------------
| |
| Sample Usage: | |
n/a | |