Setting Wallpapers in a Windows 8 Store App with VB

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.

Our Main form's design
Figure 1 - Our Main form's design

Add another Page to your project through the Project menu, and design it to resemble Figure 2

Settings page
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

Here, you can have a detailed look at the available namespaces in Windows 8 Store apps and their uses.

Add the following code to your MainPage class:

    Public arrFolderNames() As String
    Public arrSubFolderNames() As String
    Public arrFileNames() As String

    Private PaperDuration As Integer

    Private Async Sub btSelect_Click(sender As Object, e As RoutedEventArgs) Handles btSelect.Click
        lstFolders.Items.Clear()

        Dim RootFolder As StorageFolder = KnownFolders.PicturesLibrary

        Dim FolderList As IReadOnlyList(Of IStorageItem) = Await RootFolder.GetItemsAsync

        Dim Counter As Integer

        For Each folder In FolderList

            If TypeOf folder Is StorageFolder Then
                lstFolders.Items.Add(folder.Name)
                ReDim Preserve arrFolderNames(Counter)
                arrFolderNames(Counter) = folder.Path
                Counter += 1

            End If
        Next



    End Sub

    Private Async Sub lstFolders_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles lstFolders.SelectionChanged
        lstSubFolders.Items.Clear()

        Dim SubFolder As StorageFolder = Await StorageFolder.GetFolderFromPathAsync(arrFolderNames(lstFolders.SelectedIndex))

        Dim SubFolderList As IReadOnlyList(Of IStorageItem) = Await SubFolder.GetItemsAsync

        Dim Counter As Integer

        For Each Folder In SubFolderList

            If TypeOf Folder Is StorageFolder Then
                lstSubFolders.Items.Add(Folder.Name)
                ReDim Preserve arrSubFolderNames(Counter)
                arrSubFolderNames(Counter) = Folder.Path
                Counter += 1
            End If
        Next
    End Sub

    Private Async Sub lstSubFolders_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles lstSubFolders.SelectionChanged
        lstFiles.Items.Clear()

        Dim SubFolder As StorageFolder = Await StorageFolder.GetFolderFromPathAsync(arrSubFolderNames(lstSubFolders.SelectedIndex))

        Dim SubFolderList As IReadOnlyList(Of IStorageItem) = Await SubFolder.GetItemsAsync

        Dim Counter As Integer
        For Each File In SubFolderList

            If TypeOf File Is StorageFile Then


                lstFiles.Items.Add(File.Name)
                ReDim Preserve arrFileNames(Counter)


                arrFileNames(Counter) = File.Path
                Counter += 1
            End If
        Next
    End Sub

    Private Async Sub lstFiles_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles lstFiles.SelectionChanged

        Dim strPicLoc As String = arrFileNames(lstFiles.SelectedIndex)

        Dim File2 As StorageFile = Await StorageFile.GetFileFromPathAsync(strPicLoc)

        Dim src As New BitmapImage()
        src.SetSource(Await File2.OpenAsync(FileAccessMode.Read))
        imgWall.Source = src

        imgWall.Stretch = Stretch.Fill


    End Sub

Here, we first obtain a list of all folders inside the PicturesLibrary. Next, we obtain a list of subfolders within a selected folder inside the Pictures library. Lastly, we again, asynchronously, get a list of files inside the selected sub folder inside the selected folder in pictures Library. What a mouthful! Keeping it simple. The last sub, obtains the selected file and displays the picture inside an image control. This has also changed (if you're a newbie to Windows 8 store programming). It is not as simple as just supplying a destination source of the picture (as in desktop apps). You have to open the picture and read its contents into the image control.

If you were to run your project now, you will be able to select folders, sub folders, and files and it would look similar to figure 3.

Our program in action - isn't Alizee Jacotey just beautiful!!?
Figure 3 - Our program in action - isn't Alizee Jacotey just beautiful!!?

This is where things get interesting, and I will continue with the Mainpage code a bit later. We have to put each part of the puzzle first, before we can have a completed puzzle.

Add the next event to the MainPage:

    Private Sub btSettings_Click(sender As Object, e As RoutedEventArgs) Handles btSettings.Click
        Dim spSettings As New Frame()
        spSettings.Navigate(GetType(BasicPage1))

        Window.Current.Content = spSettings

        Window.Current.Activate()
    End Sub

The Settings button takes us to the Settings page (or Frame) and activates it.

Let's now have a look at the settings page.

Settings

Not much work here, as this page will solely be used to determine when we want the wallpaper to change. Let us add its code:

    Private wpDuration As String

    Private Sub btBack_Click(sender As Object, e As RoutedEventArgs) Handles btBack.Click
        Me.Frame.Navigate(GetType(MainPage), wpDuration)

    End Sub

    Private Sub rdOneMin_Checked(sender As Object, e As RoutedEventArgs) Handles rdOneMin.Checked
        wpDuration = "1M"

    End Sub

    Private Sub rdTenMin_Checked(sender As Object, e As RoutedEventArgs) Handles rdTenMin.Checked
        wpDuration = "10"
    End Sub

    Private Sub rdOneWeek_Checked(sender As Object, e As RoutedEventArgs) Handles rdOneWeek.Checked
        wpDuration = "1W"
    End Sub

    Private Sub rdOneDay_Checked(sender As Object, e As RoutedEventArgs) Handles rdOneDay.Checked
        wpDuration = "1D"
    End Sub


End Class

There is not much happening here. All we have done here is to store whichever option was selected into a variable called wpDuration. We need to now transfer this variable back to our MainPage. We do this by overriding the MainPage's OnNavigatedTo event:

    Protected Overrides Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
        MyBase.OnNavigatedTo(e)

        Dim pDur As String = TryCast(e.Parameter, String)

        Select Case pDur
            Case "1M"
                PaperDuration = 60
            Case "10"
                PaperDuration = 600
            Case "1D"
                PaperDuration = 86400
            Case "1W"
                PaperDuration = 604800
        End Select

    End Sub
End Class

Once we are back at the MainPage, we determine which option buttons were selected by investigating the variable's contents. How did it know which variable to use? If you look at the settings page's Back click event, you will notice that we passed the wpDuration variable as a parameter. Inside the MainPage's OnNavigatedTo event we cast the parameter to a string, and voila - the two pages can now communicate with each other!

What is stored inside the PaperDuration variable is the amount of seconds for each option. 60 for one minute. 600 for 10 minutes. 86400 for once a day, and 604800 for once a week. As this program might run continuously (especially on a Windows 8 Phone) this works quite nicely. Now, you may recall that we do not have any timers at our disposal. What we should do now is make use of a Background task, and run it at each of these increments, depending of course on which button was selected.

Adding a Background Task

Background on Background Tasks

Have a proper read through these articles:

Sadly, all the code samples are in C# only (as always...).

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.

Solution Explorer
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
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!



Related Articles

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

  • On-demand Event Event Date: March 27, 2014 Teams need to deliver quality software faster and need integrated agile planning, task tracking, source control, auto deploy with continuous builds and a configurable process to adapt to the way you work. Rational Team Concert and DevOps Services (JazzHub) have everything you need to build great software, integrated seamlessly together right out of the box or available immediately in the cloud. And with the Rational Team Concert Client, you can connect your …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds