Introduction
This is an introduction to my screensaver helper library. The aim was to offload, as much as possible, the minutiae around creating a screensaver. In essence, a screensaver is a regular exe with the extension changed to .scr. In practice, there are multiple components to making this work as expected. Processing various command line arguments, watching for keyboard & mouse events, allowing configuration, providing a win32 child window for preview mode, multiple monitor support, etc. this helper attempts to simplify as much of that as possible.
Background
This is my first contribution to code project but I have benefited so much from all the experts here I thought it was time to attempt to give something back to the community. Rather than a "how-to" this is more of a tool you may use to help in your development of screensavers. Believe it or not, yes, there is still a need for screensaver development. In my case, I had multiple customer-facing kiosks and to avoid screen burn-in I wanted a custom screensaver. In this case, I wanted to consume our company's Facebook page but that is another topic.
Using the code
Once you have a WPF application that you would like to make into a screensaver, reference the ScreenSaver.Helper.dll and log4net.dll and open your App.xml and remove/comment the existing StartupUri and add a Startup method.
<Application x:Class="WPF_App_Tester.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="App_OnStartup">
-->
...
</Application>
After that is complete, move to your App.xaml.cs and use the ScreensaverApp Attribute to link your application to to the ScreenSaver helper subsystem. The only required parameter is the main window (Your primary screensaver window) in the example below we named it "MainWindow". There are multiple optional parameters, ConfigWindow (Window) {This is the configuration window you want windows to use}, RestartOnUnhandled (bool), RepeatOnEachMonitor(bool), StretchAcrossMonitors(bool).
Inside the method you just created App_OnStartup you should pass the existing parameters (sender & e) to the Core.App_Startup method. This will handle multiple factors for you (covered later).
You may also wish to hook into the Core.UnhandledException event to deal with any unexpected exceptions. By default the library uses log4net and creates a rolling log file stored in AppData/AppName to log the exception prior to shutting down.
using System.Windows;
using ScreenSaver.Helper;
namespace WPF_App_Tester
{
[ScreensaverApp(typeof(MainWindow),typeof(ConfigWindow))]
public partial class App : Application
{
private void App_OnStartup(object sender, StartupEventArgs e)
{
Core.UnhandledException += Core_UnhandledException;
Core.App_Startup(sender, e);
}
private void Core_UnhandledException(object sender, ref System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
}
}
}
Custom Settings Provider
Screensavers have a quirk about how they access files/folders linked back to WinNT file/folder eight character naming limitations which fusses with the default settings provider. Instead of tweaking this we are going to use HKCU section of the registry to store our settings. The library provides a custom settings provider to enable this. To use it, navigate to your project's settings file, click the <>View Code button. Visual Studio should move you to your Settings.cs file.
Include a using reference to ScreenSaver.Helper.SettingsProviders and add two attributes. The first,
[SettingsProvider(typeof(SettingsProviderEx))]
this tells the settings system to use our alternative provider and the second,
[SettingsProviderEx.SettingsHelper(typeof(RegCurrentUserHelper))]
informs the SettingsProviderEx to use the HKCU key we discussed before.
using System.Configuration;
using ScreenSaver.Helper.SettingsProviders;
namespace WPF_App_Tester.Properties {
[SettingsProviderEx.SettingsHelper(typeof(RegCurrentUserHelper))]
[SettingsProvider(typeof(SettingsProviderEx))]
internal sealed partial class Settings {...}
...
}
Command Line Parameters
If you build your application at this point it should now accept multiple command line parameters.
/s - Is a required parameter for windows screensavers it will show the form you identified in the MainWindow attribute of your App.xaml.cs. Inside the IDE the forces the window to TopMost = false to allow for debugging breaks and exception handling. When the debugger is not attached TopMost is set to true.
In show mode, mouse and keyboard event listeners are injected into the MainWindow to allow for the traditional close events screensavers typically possess.
By default, if multiple monitors are present a 'shade' window is loaded on every monitor but the one identified as the main monitor in windows. The main monitor receives your MainWindow.
You can override this behavior with the RepeatOnEachMonitor(bool) or StretchAcrossMonitors(bool) as you see fit.
/p IntPtr Using the IntPtr passed typically from windows while selecting a screensaver, the helper makes your MainWindow a child window for the preview mode but does not inject any mouse/keypress closure events.
/c Opens the ConfigWindow you passed to the ScreenSaverApp attribute's ConfigWindow parameter.
/i Installs the screensaver by first changing the main file extension, if necessary, from an .exe to a .scr then updating the registry to set your screensaver as the default and then opens the Windows screen saver configuration window.
Additional Helper Classes
Logging
As mentioned previously the helper library used log4Net for its logging. You can provide your own additional logging by using the provided extension methods after adding a reference to the LogHelper class and log4net.
The first Log() simply logs your message with an optional exception object
this.Log().Warning("YOUR WARNING MESSAGE",ex);
the second LogP() also logs your message but also adds the public properties of the calling class to the logg
this.LogP().Error("YOUR ERROR MESSAGE",ex);
AnimationHelper
Is an await-able task based double animation helper, additionally, a Fade method is provided that adjusts the opacity of a UIElement or IEnumerable<UIElement> to fade in/out.
Usage:
await Image.Fade();
await Image.Fade(1);
await Image.Fade(1,6.Secs());
ListExtensions
Methods to randomize a list of objects
MyList.Shuffle();
And convert a List<string> to a CSV list and back again.
string myCsvString = MyList.ToCsvLine();
List<string> MyList = myCsvString.FromCsvLine();
BitmapExtensions
Converts a Bitmap to an ImageSource
Image.Source = Properties.Resources.MyResourceBitmap.ToImageSource();
History
11/8/2015 - Initial Post
7/9/2016 - Cleaned up some typos.