Introduction
Today we’ll find out how we can change the wallpaper of a Windows 8 Store app. Hold on tight, we have a bumpy ride ahead. Let’s get started.
Windows 8 Store Apps
As we know by now, and if you have followed my most recent articles (the last two months or so), Windows 8 Store apps differ quite a bit from desktop apps. While doing today’s app, you will see most of the differences concern timers, and API’s as well as serialization capabilities. If you are new to Windows 8 Store apps, I suggest you read this article.
What Can We Work With?
It seems a silly question, but, you will need to know what tools are available, and what language features we can use. We are able to work with the next few features just to get this basic project to work:
- XAML features, including XAML controls – I quote Homer Simpson: “doh!”. Obviously we well need these as this is a Windows 8 Store app.
- Background tasks – This we will use instead of Timer controls, which aren’t available here.
- SystemParametersInfo API – Yes, it still does work.
- Asynchronous programming – We will use Async programming methods to accomplish our tasks asynchronously. Have a look at this article I wrote recently.
- Basic serialization – The reason why I included this point is: It is logical to assume we will need some sort of serialization here, as we will need to keep track of which paper was set and so on. I haven’t gone into much detail here, honestly; perhaps in an updated version of this article (or a future article) I will.
We aren’t able to work with the following features:
- Timer controls – these controls do not exist in the Windows 8 Store framework
- Registry – Windows 8 Store apps cannot access the regsitry. We can however use the following two APIs:
- Windows.Storage.ApplicationDataContainer
- Windows.Storage.ApplicationDataContainerSettings
Welcome to Windows 8 Store programming!
Our Project
Our project’s aim is to set change the wallpaper at different intervals. Because of the fact that there is no timer control present, we will have to create a Background task. This is not as simple as double clicking on a control. We will have to create a separate project for our background task, register it, and make it work with our main project. This means that we will have two projects in one solution.
Another caveat is that we have limited resources at our disposal. We will only be allowed to access certain folders, which are known to the app, and read their contents asynchronously. Lastly, we do not have access to the registry, so saving info might be hard.
Let us create our first project. Give it a name of Wallpaper Genie 2013, and design it to look like Figure 1.
Figure 1 – Our Main form’s design
Add another Page to your project through the Project menu, and design it to resemble Figure 2
Figure 2 – Settings page
Mainpage
Add the following Imports to your MainPage:
Imports Windows.Storage Imports Windows.Storage.Streams Imports Windows.UI.Xaml.Media.Imaging Imports System.Runtime.InteropServices Imports Windows.ApplicationModel.Background Imports Windows.UI.Core Imports Windows.UI.Core.CoreWindow Imports System.Xml Imports System.Runtime.Serialization Imports System.IO
Now that we have decent understanding of the need for Background tasks, we have to know how to create one.
Add a new project to your existing solution by clicking File, Add, Project. Select the Class Library project and give it a nice name. I have named mine GenieBackTask. Rename the default class name (Class1) to something more descriptive such as clsBack.vb. Your Solution Explorer should look like Figure 4.
Figure 4 – Solution Explorer
Add the following code to clsBack:
Imports Windows.ApplicationModel.Background Imports Windows.Storage Imports Windows.UI.Core Imports Windows.UI.Core.CoreWindow Imports System.Runtime.InteropServices Imports System.Xml Imports System.Runtime.Serialization Namespace GenieBackTask Public NotInheritable Class clsBack Implements IBackgroundTask <DllImport("user32", setlasterror:=True)> _ Private Shared Function SystemParametersInfo( _ ByVal intAction As Integer, _ ByVal intParam As Integer, _ ByVal strParam As String, _ ByVal intWinIniFlag As Integer) As Integer ' returns non-zero value if function succeeds End Function Const SPIF_UPDATEINIFILE = &H1 Const SPI_SETDESKWALLPAPER = 20 Const SPIF_SENDWININICHANGE = &H2 Public Async Sub Run(taskInstance As IBackgroundTaskInstance) Implements IBackgroundTask.Run Dim Deferral As BackgroundTaskDeferral = taskInstance.GetDeferral() Await UpdateUI() Deferral.Complete() End Sub Private Async Function UpdateUI() As Task Dim MyDispatcher = GetForCurrentThread().Dispatcher Await MyDispatcher.RunAsync(CoreDispatcherPriority.Normal, Async Function() Dim picker As New Windows.Storage.Pickers.FileOpenPicker picker.SuggestedStartLocation = Pickers.PickerLocationId.PicturesLibrary Dim strPicFileName = Await picker.PickSingleFileAsync Dim x As Integer x = SystemParametersInfo(SPI_SETDESKWALLPAPER, 0&, strPicFileName.DisplayName, SPIF_UPDATEINIFILE Or SPIF_SENDWININICHANGE) End Function) End Function End Class End Namespace
Let us break this code down, piece by piece. Obviously, the first couple of lines are the namespaces that we need.
This part:
Namespace GenieBackTask Public NotInheritable Class clsBack Implements IBackgroundTask
Creates a namespace (for the project) and creates the class. You will notice that this class implements the IBackgroundTask interface. This interface provides a method to perform the work of a background task.
The next section:
<DllImport("user32", setlasterror:=True)> _ Private Shared Function SystemParametersInfo( _ ByVal intAction As Integer, _ ByVal intParam As Integer, _ ByVal strParam As String, _ ByVal intWinIniFlag As Integer) As Integer ' returns non-zero value if function succeeds End Function Const SPIF_UPDATEINIFILE = &H1 Const SPI_SETDESKWALLPAPER = 20 Const SPIF_SENDWININICHANGE = &H2
Is the declaration of our API that will change the wallpapers.
Now things get tricky! The Run sub (which has to be included inside any Background task) is what will run when it has been triggered. The trigger we will set inside our MainPage class when we register the background task. The Run sub looks like:
Public Async Sub Run(taskInstance As IBackgroundTaskInstance) Implements IBackgroundTask.Run Dim Deferral As BackgroundTaskDeferral = taskInstance.GetDeferral() Await UpdateUI() Deferral.Complete() End Sub
What happens here is the following:
- We again implement the IBackgroundTask interface, but this time its Run method.
- We obtain a Deferral object as the task will run Async.
- We call our method that will run.
- We complete the deferral.
Now the fun part! This…
Private Async Function UpdateUI() As Task Dim MyDispatcher = GetForCurrentThread().Dispatcher Await MyDispatcher.RunAsync(CoreDispatcherPriority.Normal, Async Function() Dim picker As New Windows.Storage.Pickers.FileOpenPicker picker.SuggestedStartLocation = Pickers.PickerLocationId.PicturesLibrary Dim strPicFileName = Await picker.PickSingleFileAsync Dim x As Integer x = SystemParametersInfo(SPI_SETDESKWALLPAPER, 0&, strPicFileName.DisplayName, SPIF_UPDATEINIFILE Or SPIF_SENDWININICHANGE) End Function) End Function
…runs another Async function inside of it, and dispatches it to the current thread. We created a FileOpenPicker, which allows the user to select a file inside the PicturesLibrary. Obviously you can customize this further (as this is just an example) to locate the specific picture you need.
Finally, we apply the selected picture as a Wallpaper. Build this project now.
This is all we need for the Background task. We now need to connect it to our main project, and then register and start it from our main project. Connect this task to the main project now by opening the Package.Manifest file and navigating to the Declarations tab and entering GenieBackTask.clsBack in the Entry Point field, as displayed in Figure 5.
Figure 5 – Page Manifest
Now our main output project knows about the task. All that is left now is to register the task in code from our MainPage, and then we’re done. Add the following code to the Mainpage class:
Private Sub btnSet_Click(sender As Object, e As RoutedEventArgs) Handles btnSet.Click Dim x = RegisterBackgroundTask("GenieBackTask.clsBack", "GenieBackTask", New TimeTrigger(PaperDuration, False)) End Sub Public Shared Function RegisterBackgroundTask(TaskEntryPoint As String, TaskName As String, Trigger As IBackgroundTrigger) As BackgroundTaskRegistration For Each cur In BackgroundTaskRegistration.AllTasks If cur.Value.Name = TaskName Then Return DirectCast(cur.Value, BackgroundTaskRegistration) End If Next Dim Builder As New BackgroundTaskBuilder Builder.Name = TaskName Builder.TaskEntryPoint = TaskEntryPoint Builder.SetTrigger(Trigger) Dim task As BackgroundTaskRegistration = Builder.Register() Return task End Function Public Shared Sub UnregisterBackgroundTasks(TaskName As String) For Each cur In BackgroundTaskRegistration.AllTasks If cur.Value.Name = TaskName Then cur.Value.Unregister(True) End If Next End Sub
In btnSet we register the background task by supplying the same EntryPoint (as we did in the Mainfest), giving it a name, and when this task should run (PaperDuration).
Lastly, we Unregister the task. This is needed so that it doesn’t take up unnecessary resources when the app is closed.
Conclusion
This was tough, agreed. But things fall into place the more you work with a certain technology. My aim is just to guide you in the right direction, and I try to make your transition from normal desktop apps to Windows Store apps easier. I hope you have learned a thing or two today. Until next time, cheers!