Click here to Skip to main content
15,884,298 members
Articles / Programming Languages / C#
Article

Wallpaper Changer

Rate me:
Please Sign up or sign in to vote.
4.78/5 (15 votes)
27 Oct 20057 min read 112.3K   3.9K   60   14
Periodically change your wallpaper automatically from among chosen images.

Image 1

Introduction

This program allows you to choose pictures that will automatically be set as your wallpaper at specified intervals.

Running the demo

To run this demo you need .NET 2.0. which is available here. Once .NET 2.0 Framework is installed, simply double click the executable. It will display a new icon in your system tray. Double click the icon to open up the configuration window. You can add files to this which will be set as your wallpaper periodically. The time interval between the changes can be set in the Tools->Settings menu.

Program walkthrough

I shall attempt to highlight some of the interesting parts in the source code.

Changing the wallpaper

To set the wallpaper you need to perform a PInvoke call:

C#
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int SystemParametersInfo(
                        int uAction, int uParam, 
                        string lpvParam, int fuWinIni);
public const int SPI_SETDESKWALLPAPER = 20;
public const int SPIF_SENDCHANGE = 0x2;

...

int result = SystemParametersInfo(SPI_SETDESKWALLPAPER, 
                   1, tempImageFilePath, SPIF_SENDCHANGE);

To set the scaling style of the wallpaper, two values have to be set in the registry for the keys TileWallpaper and WallpaperStyle under HKEY_CURRENT_USER\Control Panel\Desktop.

Center : TileWallpaper=0, WallpaperStyle=1
Stretch : TileWallpaper=0, WallpaperStyle=2
Tile: TileWallpaper=1, WallpaperStyle=0

Setting the registry key is done through a simple method:

C#
using Microsoft.Win32;

...

private static void SetRegistryKeyForWallpaper(
                          string keyName, string value)
{
   RegistryKey deskTopKey = 
       Registry.CurrentUser.OpenSubKey(
              @"Control Panel\Desktop", true);
   deskTopKey.SetValue(keyName, value);
}

BestFit and saving the bitmap

I added a style called BestFit to the wallpaper styles. This style scales the image proportionately, so that the image is not skewed. Portions surrounding the image may be blank; the color for this area can be chosen. The file is saved as a bitmap before it is set as the wallpaper.

C#
private static void ConvertSourceFileToBmp(
          string sourceFilePath, string tempBmpFilePath, 
          ScaleStyles scaleStyle, Color backColor)
{
    Image sourceImg = Image.FromFile(sourceFilePath);
    if (scaleStyle != ScaleStyles.BestFit)
    {
        sourceImg.Save(tempBmpFilePath, 
             System.Drawing.Imaging.ImageFormat.Bmp);
    }
    else
    {
        //get the dimensions of the screen
        float H = 
          System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height;
        float W = 
          System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width;

        //get the image dimensions
        float h = sourceImg.Height;
        float w = sourceImg.Width;

        //dimensions of target
        float targetHeight = -1;
        float targetWidth = -1;

        //find the appropriate height and width
        if (H / h >= W / w)
        {
            targetWidth = w;
        }
        else
        {
            targetHeight = h;
        }
        if (targetHeight == -1)
        {
            targetHeight = (H / W) * targetWidth;
        }
        if (targetWidth == -1)
        {
            targetWidth = (W / H) * targetHeight;
        }

        //create a new image with the default back color 
        //with the scaled dimensions w.r.t. image and screen
        Bitmap bmpImage = new Bitmap((int)targetWidth, 
                                         (int)targetHeight);
        Graphics g = Graphics.FromImage(bmpImage);
        SolidBrush backgroundColorBrush = 
                                  new SolidBrush(backColor);
        g.FillRectangle(backgroundColorBrush, 0, 0, 
                           bmpImage.Width, bmpImage.Height);
        
        //layout this image in the center
        g.DrawImage(sourceImg, 
               Math.Abs(targetWidth-sourceImg.Width)/2, 
               Math.Abs(targetHeight - sourceImg.Height)/2, 
               sourceImg.Width, sourceImg.Height);

        //save it as bmp
        bmpImage.Save(tempBmpFilePath, 
                   System.Drawing.Imaging.ImageFormat.Bmp);

        //dispose stuff
        backgroundColorBrush.Dispose();
        g.Dispose();
    }
}

Loading external resources

I needed icons for the main application. I used the same for the notify icon. As the coding progressed, I added another icon to notify when the wallpaper was changing. Initially, I added the icons from an external file using the code:

C#
ApplicationIcon = Icon.ExtractAssociatedIcon("App.ico");

However, I did not want the program to fail if somebody simply copied the EXE to another machine without the external files and tried to run it. So I used the resource manager. Creating a new resource and using it within your application was done in the following steps:

  • Add a new resource item to the project.
  • Name the file (say Resource1.resx).
  • Open the resource file.
  • Add the required icons - the resource name defaults to the name of the file without the extension.
  • To use the resources, create a ResourceManager object.
C#
ResourceManager resourceManager =
   new ResourceManager("WallpaperChanger.Resource1",
                      Assembly.GetExecutingAssembly());

The name of the resource file is a combination of the default namespace name for the project and the name of the resource file (without the extension). The resources can then be obtained using their names.

C#
ApplicationIcon = (Icon)resourceManager.GetObject("App");
C#
ResourceManager resourceManager = 
   new ResourceManager("WallpaperChanger.Resource1", 
                        Assembly.GetExecutingAssembly());
ApplicationIcon = (Icon)resourceManager.GetObject("App");
WallpaperCurrentlyChangingIcon = 
           (Icon)resourceManager.GetObject("Changing"); ;

Adding a system tray icon

Some other programs that I had written use a NotifyIcon but they were present in their form. I did not want the NotifyIcon to carry the extra baggage of an empty form this time. So, I coded a NotifyIcon instance in a formless class.

C#
this.notifyIcon = new NotifyIcon();
this.notifyIcon.Text = "Wallpaper Changer";
this.notifyIcon.Icon = NotifyIconManager.ApplicationIcon;
this.notifyIcon.Visible = true;
this.notifyIcon.ShowBalloonTip(1, "Started " + 
   NotifyIconManager.APP_TITLE, "You can double click on this icon to 
   configure settings and choose pictures.", ToolTipIcon.Info);
this.notifyIcon.DoubleClick += 
                new EventHandler(this.configureMenuItem_Click);

I also added a BalloonTip. I tried this just to find out what it did but decided to leave it because it was a nice usability feature. With the other programs that I had written, the icon would quietly go into the system tray but the users wouldn't notice it, thus missing out the fact that the program has been loaded.

NotifyIcon disappears instantly

Since there was no continuous thread, the program would immediately exit as soon as it was run causing the NotifyIcon to disappear. To retain the NotifyIcon in the system tray, I started an application message loop after creating the NotifyIcon and displaying it.

C#
Application.Run();

Running a single instance of the program

I allow only a single instance of the program to run using the code given here.

ImageInfo and GroupInfo

There are two types that are required in the program, one contains information about the group called GroupInfo and the other called ImageInfo contains wallpaper settings for the image. Group information is applied by default to all images applied to that group. ImageInfo can be changed to override the default group settings that are initially applied to all newly added images.

  • Style: Defines how the image should be displayed on the screen.
    C#
    public enum ScaleStyles { BestFit, Center, Stretch, Tile };
  • BackColor: The color used for the background when the style is BestFit.
  • IncludeGroup: This is applicable only to groups. If true, then next time the application chooses a change of wallpaper, this group is included among the groups from which the image is chosen.

Reading and writing serialized XML

To allow me to read the ImageInfo and GroupInfo XML data from the file easily, I used the code that is available here.

Periodically setting the wallpaper

To periodically change the wallpaper, I started a timer. The interval of the timer is configurable through the user interface:

C#
//create timer and set time interval
this.periodicTimer = new Timer();
this.periodicTimer.Interval = 
  Int32.Parse(this.optionsDictionary["TimeIntervalInSeconds"])*1000;
this.periodicTimer.Tick += new EventHandler(periodicTimer_Tick);
this.periodicTimer.Start();

Every time the timer ticks, I choose a new file randomly from the list of available groups. This is then set as the wallpaper.

C#
private void periodicTimer_Tick(object sender, EventArgs e)
{
    this.periodicTimer.Stop();

    try
    {
        int failCount = 0;
        
        while (failCount < 3)
        {
            ImageInfo nextFileImageInfo = 
                     this.GetNextRandomImageFileInfo();    
            //if file is not present then try again
            if (nextFileImageInfo == null || 
                   !File.Exists(nextFileImageInfo.FilePath))
            {
                failCount++;
                continue;
            }
            else
            {
                this.SetAsWallpaperNow(nextFileImageInfo);
                break;
            }
        }
    }
    catch (Exception ex)
    {
        System.Windows.Forms.MessageBox.Show("Error! " + ex.Message + 
             "\r\n" + ex.StackTrace);
    }

    this.periodicTimer.Start();
}

As a fail guard measure, I allow three failed attempts while choosing the next file.

Parameterized thread

This one was easy to understand: with parameterized threads, you can pass parameters to a thread. But it was slightly odd while implementing it. The object that is specified as the parameter in the method definition has to be an 'object'. This was quirky because all the classes within the .NET Framework are derived from the object class. Yet, I could not replace the object type with any of the derived types.

C#
private void LoadThumbnails(object paramValue)
{
  Dictionary<string, ImageInfo> currentImageGroupDictionary =
                    (Dictionary<string, ImageInfo>) paramValue;

   ....
}


this.currentLoadThumbnailsThread = 
   new Thread(new ParameterizedThreadStart(this.LoadThumbnails));
this.currentLoadThumbnailsThread.Start(
   this.parentNotifyIconManager.imageInfoDictionary[selGroup.Name]);

Yet, I liked the parameterized thread because I did not have to have global/member variables to pass data through.

Threads and updating UI from non-UI thread

This program actually started off while I was trying out samples to update the UI from a separate worker thread. So here is the theory: the UI runs in one thread. All the user feedback and user input happen on this thread by default. When you start another thread, this new thread should not manipulate the controls in the UI thread directly.

The Invoke or BeginInvoke methods can be used for this. Invoke is synchronous and waits for the call to return. BeginInvoke is asynchronous and returns immediately. For my purposes BeginInvoke was the most appropriate.

To make a call to BeginInvoke, you cannot call a method directly - since that would be the same as accessing the control directly. Instead, one has to declare a delegate to which an appropriate method can be passed.

C#
public delegate void SetProgressBarDelegate(
                             object[] paramList);

Then the method is declared with the same parameters and return type as the delegate.

C#
private void SetProgressBarVisibility(
                              object[] paramList)
{
   this.thumbnailsLoadProgress.Visible = 
                              (bool)paramList[0];
}

Thereafter, the method can be passed through the delegate to BeginInvoke.

C#
BeginInvoke(
  new SetProgressBarDelegate(SetProgressBarValue), 
               new object[] { new object[] { 0 } });

Having a single delegate which took the object array as the parameter was convenient. I also found out that the actual parameters that I wanted to pass have to be nested in another object array since BeginInvoke passed out the objects in its array list one after another to the delegated method.

I used these methods to load up the thumbnails in another thread, while simultaneously updating the UI with the loaded images. I also updated the progress bar and a label that contains the count of the number of loaded images.

I did face an issue here when the user quickly moved between two groups. Some part of the previous thread that was not finished would load some images from the earlier group along with those of the current group. I tried Abort-ing the thread but that did not work out quite right. So, I used a boolean value that was checked and the previous thread was 'join'ed before starting a new thread.

C#
if (this.currentLoadThumbnailsThread != null
     && this.currentLoadThumbnailsThread.IsAlive)
{
     this.currentLoadThumbnailsCancelled = true;
     this.currentLoadThumbnailsThread.Join();
}

//clear whatever is there
this.thumbnailsPanel.Controls.Clear();
this.thumbnailsPanel.Invalidate();

this.currentLoadThumbnailsCancelled = false;
this.currentLoadThumbnailsThread = 
  new Thread(new ParameterizedThreadStart(this.LoadThumbnails));
this.currentLoadThumbnailsThread.Start(
  this.parentNotifyIconManager.imageInfoDictionary[selGroup.Name]);

Usability

I wanted this program to be usable. I thought the program UI was simple. The program title explicitly stated that it changes wallpapers and I assumed that the users would easily get the idea of what needs to be done. I guess I was wrong. I saw a couple of people using the program and I am convinced that the usability of a program is of the utmost importance. Some of the things that I did for the sake of usability:

  • Balloon popup that gave feedback when a new program was added to the system tray.
  • Menus and context menus on most controls.
  • Guide in the UI to indicate the user what needs to be done (like "You can drag and drop files here ...").

Things I couldn't do

  • Drag and drop thumbnail pics between groups.
  • Option to sequentially select the next file in the group.
  • Option to add a calendar to the wallpaper.
  • Animated icon when the wallpaper is changing.
  • Functionality to do basic image editing on the image - crop, brightness, contrast.
  • Screensaver from the pictures.
  • Run/remove at startup option.

Version update history

  • 0.3
    • Disposing memory resources after use.
    • Ability to turn off balloon tool tip.
    • Added error handling around load thumbnails thread. Closing the form while loading thumbnails was causing a crash.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
France France
~/sathishvj

Comments and Discussions

 
QuestionNice work Pin
Mike Hankey10-Aug-15 8:53
mveMike Hankey10-Aug-15 8:53 
GeneralChanged version Pin
slartus15-Apr-09 3:51
slartus15-Apr-09 3:51 
Generala little observation Pin
Member 531324310-Sep-08 3:21
Member 531324310-Sep-08 3:21 
the changer is absolutelu good and functional but...
when I launch it, it moves to the little, full traybar. Result
I did't see it so I had think the code was bad and almost delete it.
If I want change wallpapers in any case I open the main menu, I can't see way put it in the tray. It's better launch it directly in my personal view.
I hope it could be a nice suggest
good work
Generaladd a calendar to the wallpaper Pin
mircea13131329-Aug-06 23:23
mircea13131329-Aug-06 23:23 
GeneralUsing Resources in .NET 2.0 Pin
David J Hay13-Jun-06 20:16
David J Hay13-Jun-06 20:16 
GeneralProblems running on XP SP2 Pin
Will_See20-Oct-05 11:27
Will_See20-Oct-05 11:27 
GeneralNice 1 !But... Pin
Trance Junkie16-Oct-05 22:12
Trance Junkie16-Oct-05 22:12 
GeneralRe: Nice 1 !But... Pin
SathishVJ16-Oct-05 22:43
SathishVJ16-Oct-05 22:43 
GeneralRe: Nice 1 !But... Pin
Trance Junkie18-Oct-05 1:18
Trance Junkie18-Oct-05 1:18 
GeneralRe: Nice 1 !But... Pin
SathishVJ21-Oct-05 13:28
SathishVJ21-Oct-05 13:28 
GeneralRe: Nice 1 !But... Pin
rog10399-Nov-05 21:34
rog10399-Nov-05 21:34 
GeneralRe: Nice 1 !But... Pin
Greg Osborne11-Sep-07 7:23
Greg Osborne11-Sep-07 7:23 
GeneralRe: Nice 1 !But... Pin
Jun Du27-Oct-05 15:22
Jun Du27-Oct-05 15:22 
GeneralRe: Nice 1 !But... Pin
Trance Junkie27-Oct-05 21:41
Trance Junkie27-Oct-05 21:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.