Useful Windows Screensavers - Including Windows 10
Written by Mike James   
Thursday, 24 September 2015
Article Index
Useful Windows Screensavers - Including Windows 10
A Form Based Screensaver
Preview
Listing

Preview

The screensaver can also be run in /p – preview or /c – configuration mode.

The configuration mode is fairly easy to implement and it’s described in the documentation.

The /p mode is much more difficult and not described anywhere so this is the puzzle to solve next!

 

preview

The screensaver in miniature

 

When the /p option is passed via the command line the windows handle (hwnd) of the preview window is passed as a sequence of decimal digits on the command line.

For example, if the command line is /p 8324238 then the handle to the preview window is 8324238. You could simply retrieve the hwnd and use it to construct a graphics object to draw on the preview window.

This works but there is a fundamental problem with doing things in this way. The system expects the screensaver to attach a child window to the preview window and to draw on the child, not the preview.

When the system needs to stop the screensaver, for any reason, it simply closes the child window. If you draw directly on the preview window then there is no child window to close and your screensaver will not be terminated when the user looks at another screensaver or views a full screen preview. The result it that you end up with lots of copies of your screensaver running at the same time and all trying to draw to the preview window – the system then crashes.

There seems to be no easy way of making a window a child of another window using the .NET framework and once again we have to resort to using the Windows API.

First we need to change the switch to deal with the hwnd passed on the command line case "/p":

//preview
if (m_args.Length > 2)
{
 this.TopLevel = false;
 m_hwnd = (IntPtr)Int32.Parse(m_args[2]);
 ShowScreenSaver();
}
break;

and we need to add the global variable:

private IntPtr m_hwnd;

If m_hwnd has a value then we need to display using the preview screen and the ShowScreenSaver function has to be modified to initialise the form either for full screen or preview display:

void ShowScreenSaver()
{
 if (this.m_hwnd == IntPtr.Zero)
 {
  All of the code
  already in ShowScreenSaver
 }

The else part of the if handles the preview and uses a number of API calls, definitions of which are given later. We need to make the form’s window a child window of the preview window. To do this we need to retrieve and modify the form’s window style to make it a child window:

else
{
 int style = GetWindowLong(this.Handle,
                                 GWL_STYLE);
 style = style | WS_CHILD;
 SetWindowLong(this.Handle,GWL_STYLE, style);

Next we tell the system that the preview window is the parent of the form’s window and tell the form’s window that its parent is the preview window:

SetParent(this.Handle,m_hwnd);
SetWindowLong(this.Handle, GWL_HWNDPARENT,
                             (int) m_hwnd);

Now everything is set up for the form to display within the preview window except for the fact that it’s the wrong size and still has a border. To resize it we need to know the size of the preview window:

this.FormBorderStyle = FormBorderStyle.None;
RECT r=new RECT();
GetClientRect(m_hwnd, out r);

To display the window a final API call is used do the resizing and place the form’s window in front of the preview window:

SetWindowPos(this.Handle,
             HWND_TOP, 0, 0,
             r.Right, r.Bottom,
             SWP_NOZORDER |
             SWP_NOACTIVATE |
             SWP_SHOWWINDOW);
}

and finally we start the timer running as for a standard display:

 this.timer1.Interval = 50;
 this.timer1.Enabled = true;

For all of this to work we need to add:

using System.Runtime.InteropServices;

at the start of the file and add the following declarations for all of the Win32 API calls we use:

public struct RECT
{
 public int Left;
 public int Top;
 public int Right;
 public int Bottom;
}

[DllImport("user32.dll")]
static extern bool GetClientRect(
 IntPtr hWnd,
 out RECT lpRect);

[DllImport("user32.dll",
            SetLastError = true)]
static extern int GetWindowLong(
 IntPtr hWnd,
 int nIndex);

const int GWL_STYLE = (-16);
const int WS_CHILD = 0x40000000;

[DllImport("user32.dll")]
static extern int SetWindowLong(
 IntPtr hWnd,

 int nIndex,

 int dwNewLong);

[DllImport("user32.dll")]
static extern IntPtr SetParent(
 IntPtr hWndChild,

 IntPtr hWndNewParent);

const int GWL_HWNDPARENT = (-8);

[DllImport("user32.dll")]
static extern bool SetWindowPos(
 IntPtr hWnd,
 IntPtr hWndInsertAfter,
 int X,int Y,
 int cx, int cy,
 uint uFlags);

static readonly IntPtr HWND_TOP
                   = new IntPtr(0);


const UInt32 SWP_NOZORDER = 0x0004;
const UInt32 SWP_NOACTIVATE = 0x0010;
const UInt32 SWP_SHOWWINDOW = 0x0040;

To see the preview in action you have to install the screensaver. Notice that you don’t have to display the same graphics in the tiny preview window that you do full screen. You could, for example, show a few lines of explanation of the purpose of your screensaver.

Screensavers still have a lot of potential left in them as a way of delivery information and as a way of using otherwise idle processor time.



Last Updated ( Tuesday, 06 October 2015 )