|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThis program allows you to choose pictures that will automatically be set as your wallpaper at specified intervals. Running the demoTo 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 walkthroughI shall attempt to highlight some of the interesting parts in the source code. Changing the wallpaperTo set the wallpaper you need to perform a PInvoke call: [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: 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 bitmapI added a style called 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 resourcesI 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: 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:
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. ApplicationIcon = (Icon)resourceManager.GetObject("App");ResourceManager resourceManager =
new ResourceManager("WallpaperChanger.Resource1",
Assembly.GetExecutingAssembly());
ApplicationIcon = (Icon)resourceManager.GetObject("App");
WallpaperCurrentlyChangingIcon =
(Icon)resourceManager.GetObject("Changing"); ;
Adding a system tray iconSome other programs that I had written use a 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 instantlySince there was no continuous thread, the program would immediately exit as soon as it was run causing the Application.Run();
Running a single instance of the programI allow only a single instance of the program to run using the code given here. ImageInfo and GroupInfoThere are two types that are required in the program, one contains information about the group called
Reading and writing serialized XMLTo allow me to read the Periodically setting the wallpaperTo periodically change the wallpaper, I started a timer. The interval of the timer is configurable through the user interface: //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. 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 threadThis 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. 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 threadThis 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 To make a call to public delegate void SetProgressBarDelegate(
object[] paramList);
Then the method is declared with the same parameters and return type as the delegate. private void SetProgressBarVisibility(
object[] paramList)
{
this.thumbnailsLoadProgress.Visible =
(bool)paramList[0];
}
Thereafter, the method can be passed through the delegate to BeginInvoke(
new SetProgressBarDelegate(SetProgressBarValue),
new object[] { new object[] { 0 } });
Having a single delegate which took the 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. 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]);
UsabilityI 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:
Things I couldn't do
Version update history
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||