Programming     Travel Logs     Life Is Good     Surfing Online     About Me
If you can’t code, write books and blogs, record videos and podcasts.
-Naval Ravikant
2018-05-03 20:46:06

Copy this link when reproducing:
http://www.casperlee.com/en/y/blog/53

You may have noticed that there is a Resource Type which is named "Picture". Since we can store images in the system, why not building an application to change the wallpaper for the Windows from time to time?

Just like before, let's take it easy and see some photos first. Since the film "Great Wall" is playing in cities, let's look at some photos of the leading actress in the film "Great Wall".

/Images/20161227/01.jpg

/Images/20161227/02.jpg

/Images/20161227/03.jpg

/Images/20161227/04.jpg

/Images/20161227/05.jpg

/Images/20161227/06.jpg

/Images/20161227/07.jpg

1. Right click on the solution in the Solution Explorer, select "Add -> New Project..." from the popped menu to create a "Windows Forms Application".

/Images/20170103/01.jpg

2. Right click on the newly created project, select "Add -> Reference..." from the popped menu to add the following references.

        com.casperlee.Library
        com.casperlee.ResourceManager.Bll

3. Copy the settings for the Entity Framework from the App.config in the com.casperlee.ResourceManager project to the one in the com.casperlee.WallpaperSwitcher project.

4. Change the Form1.cs to MainForm.cs and design the form as below.

/Images/20170103/02.jpg

5. Add a "System.Windows.Forms.NotifyIcon" to the main form.

6. Add a "System.Windows.Forms.Timer" to the main form.

7. Here are some key properties of the controls on the main form.

 Control Name   Property Name   Property Value 
 Main Form  Name  MainForm
 ShowInTaskBar  False
 StartPosition  CenterScreen
 Text  Wallpaper Switcher
 Interval Edit Box  Name  tbInterval
 Password Edit Box  Name  tbPassword
 PasswordChar  *
 Apply Button  Name  btnApply
 Text  Apply
 Notify Icon  Name  niMain
 BalloonTipIcon  Info
 BalloonTipText  Wallpaper Switcher
 Text  niMain
 Visible  False
 Timer  Name  timer
 Enabled  False

 

8. When changing the wallpaper, we certainly don't want to see any forms on the top. So we need a way to hide the main form.

    a. We have added a "System.Windows.Forms.NotifyIcon" to the main form and set the "ShowInTaskBar" property of the main form to "False".

    b. Add a handler function for the SizeChanged event of the main form.

        private void MainForm_SizeChanged(object sender, EventArgs e)
{
if (this.WindowState == FormWindowState.Minimized)
{
this.Hide();
this.niMain.Visible = true;
}
}

    c. Add a handler function for the Click event of the NotifyIcon.

        private void niMain_Click(object sender, EventArgs e)
{
this.Visible = true;
this.WindowState = FormWindowState.Normal;
this.niMain.Visible = false;
}

9. Let's create several classes to implement the main logic of this application. Here is the class diagram.

/Images/20170103/03.jpg

10. Here is the source code of the ImageGetter class.

namespace com.casperlee.WallpaperSwitcher
{
public abstract class ImageGetter
{
public abstract string GetImageFile();
}
}

11. Here is the source code of the FolderImageGetter class.

using System;

namespace com.casperlee.WallpaperSwitcher
{
public class FolderImageGetter : ImageGetter
{
public override string GetImageFile()
{
throw new NotImplementedException();
}
}
}

12. Here is the source code of the ResourceImageGetter class.

using com.casperlee.ResourceManager.Bll;

namespace com.casperlee.WallpaperSwitcher
{
public class ResourceImageGetter : ImageGetter
{
private string password;

public ResourceImageGetter(string aPassword)
{
this.password = aPassword;
}

public override string GetImageFile()
{
return ResourceBll.Instance.ExportRandomImage(this.password);
}
}
}

13. Open the ResourceBll.cs in the Bll project and add a function ExportRandomImage.

        public string ExportRandomImage(string aPassword)
{
CreateHandler(false);
return handler.ExportRandomImage(aPassword);
}

14. Open the ResourceHandler.cs in the Bll project and add the following source code.

        private List<ResourceEx> allImages;

private List<ResourceEx> subImages;

private string lastPasswordForImage;

public ResourceHandler()
{
this.allImages = new List<ResourceEx>();
this.subImages = new List<ResourceEx>();
this.lastPasswordForImage = string.Empty;
}

private bool NeedRetrieveImages(string aPassword)
{
return (allImages.Count == 0)
|| (aPassword != lastPasswordForImage);
}

private class ResourceComparer : IComparer<ResourceEx>
{
public int Compare(ResourceEx x, ResourceEx y)
{
return x.Name.CompareTo(y.Name);
}
}

private void RetrieveImages(string aPassword)
{
this.allImages.Clear();

string encryptedPassword;
ResourceEx rx;

using (ResourceManageEntities rme = new ResourceManageEntities())
{
foreach (Resource r in rme.Resources.Where<Resource>(
r => r.ResourceType.Key == "PIC"))
{
encryptedPassword = CEncoding.GetMD5String(aPassword + r.ID.ToString());
if (string.Compare(r.Password, encryptedPassword) != 0)
{
continue;
}

rx = new ResourceEx();
rx.CopyFrom(r);
rx.EntityType = ResourceEntityType.retDB;
rx.Encrypted = true;
rx.Decrypt(aPassword);
this.allImages.Add(rx);
}
}

this.allImages.Sort(new ResourceComparer());
lastPasswordForImage = aPassword;
}

private bool IsSubImage(ResourceEx aResource)
{
for (int i = 1; i <= 9; i++)
{
if (aResource.Name.EndsWith(
"Sub" + i.ToString() + Path.GetExtension(aResource.Name)))
{
return true;
}
}

return false;
}

public string ExportRandomImage(string aPassword)
{
ResourceEx rx;
string exportedFilename;

if (this.subImages.Count > 0)
{
rx = this.subImages[0];
exportedFilename = CEnvironment.GetTempFileName(Path.GetExtension(rx.Name));
ExportResource(rx, exportedFilename);
this.subImages.RemoveAt(0);
return exportedFilename;
}

if (NeedRetrieveImages(aPassword))
{
RetrieveImages(aPassword);
}

if (this.allImages.Count <= 0)
{
return string.Empty;
}

Random random = new Random();
int index = random.Next(this.allImages.Count);
rx = this.allImages[index];
while (IsSubImage(rx))
{
index -= 1;
rx = this.allImages[index];
}

exportedFilename = CEnvironment.GetTempFileName(Path.GetExtension(rx.Name));
CEnvironment.CurrentPassword = aPassword;
ExportResource(rx, exportedFilename);
this.allImages.Remove(rx);

while (true)
{
if (index >= this.allImages.Count)
{
break;
}

rx = this.allImages[index];
if (IsSubImage(rx))
{
this.subImages.Add(rx);
this.allImages.Remove(rx);
}
else
{
break;
}
}

return exportedFilename;
}

15. Let's go back to the main form. Since the code is pretty straightforward, I'll just post it here.

using System;
using System.Collections;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using com.casperlee.Library;

namespace com.casperlee.WallpaperSwitcher
{
public partial class MainForm : Form
{
private const int DefaultInterval = 6;

private const int MaxToKeep = 6;
private ImageGetter imageGetter;

private Queue imageFiles;

private string lastPassword;
public MainForm()
{
InitializeComponent();
imageFiles = new Queue();
}
private void ActivateTimer()
{
if (this.timer.Enabled)
{
this.timer.Stop();
}

if ((imageGetter == null) || (string.Compare(lastPassword, tbPassword.Text) != 0))
{
lastPassword = tbPassword.Text;
imageGetter = new ResourceImageGetter(lastPassword);
}

int interval;
if (!int.TryParse(tbInterval.Text, out interval)
|| (interval <= 0))
{
interval = DefaultInterval;
}

this.timer.Interval = interval * 1000;
this.timer.Start();
}
private void MainForm_SizeChanged(object sender, EventArgs e)
{
if (this.WindowState == FormWindowState.Minimized)
{
this.Hide();
this.niMain.Visible = true;
}
}

private void niMain_Click(object sender, EventArgs e)
{
this.Visible = true;
this.WindowState = FormWindowState.Normal;
this.niMain.Visible = false;
}

private void MainForm_Load(object sender, EventArgs e)
{
this.tbInterval.Text = DefaultInterval.ToString();
this.tbPassword.Text = string.Empty;
ActivateTimer();
}

private void timer_Tick(object sender, EventArgs e)
{
string imageFile = imageGetter.GetImageFile();
if (File.Exists(imageFile))
{
WinAPIWrapper.SystemParametersInfo(0x0014, 1, imageFile, 0x1 | 0x2);

if (imageFiles.Count > MaxToKeep)
{
string fileName = imageFiles.Dequeue().ToString();
if (File.Exists(fileName))
{
File.Delete(fileName);
}
}

imageFiles.Enqueue(imageFile);
}
}

private void btnApply_Click(object sender, EventArgs e)
{
ActivateTimer();
}

private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
while (imageFiles.Count > 0)
{
string fileName = imageFiles.Dequeue().ToString();
if (File.Exists(fileName))
{
File.Delete(fileName);
}
}
}
}
}

16. It refers to a Windows API function SystemParametersInfo. Let's create a wrapper for it in the Library project.

17. Add a WinAPIWrapper class to the Library project, and put the following code in.

using System.Runtime.InteropServices;

namespace com.casperlee.Library
{
public class WinAPIWrapper
{
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
public static extern int SystemParametersInfo(
int uAction,
int uParam,
string lpvParam,
int fuWinIni);

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern void OutputDebugString(string message);
}
}

18. Finished.