What I've learned today - Universal Apps and changing UI Element from other Thread
What I’ve learned today
Quick explanation: This is going to be a title for a kind of series that I plan to make from a blog posts about my struggles, mistakes, obstacles, errors and problems that’ve encountered during development time. It’s going to be a log of a WP8 and WP greenhorn programmer trying to make his way through a maze of new stuff. Don’t expect some fireworks, it’ll be rhather kind of compendium of what to do when you stumble upon a particular problem. I hope it will help others like me to avoid same mistakes, ease the pain of searching over the internet for a solution to their issue, make their life easier or just learn with me. Let’s get started!
Favorite Time Zones
This project, about which I’ve mentioned in my previous blog post, is a simple idea of showing on the screen, of your device with Windows 8.1 or cell phone with Windows Phone 8.1 on it, current time in different times zones.
After starting new Universal App project from Visual Studio with at least Update 2 on board I’ve added MVVM Light Librariesand Time Zone Conversion for Windows Runtime and PCL with NuGet pakcage manager.
MVVM wasn’t necessary because of the pragmatic point of view you don’t have to apply MVVM concept to such simple application. The reason behind me adding MVVM Light to this project is simple, I want to do things right, following good practicies. After that my NuGet packages manager looked like on the screenshot below. CommonServiceLocator is atomatically added with MVVM Light.
I used Time Zone Conversion for Windows Runtime and PCL because it was no longer that easy to list all the time zones within Windows RT. Usualy you would be able to get all the time zones just like that:
TimeZoneInfo.GetSystemTimeZones()
With this library it was similarly easy:
using System;
using System.Collections.Generic;
using System.Text;
using FavoriteTimeZones.ViewModels;
using TimeZones;
using System.Linq;
namespace FavoriteTimeZones.Data
{
public static class Constants
{
public static readonly TimeZone[] TimeZones = TimeZoneService.AllTimeZones
.Select(tz => new TimeZone()
{
Name = tz.StandardName,
DateTimeOffset = tz.BaseUtcOffset
}).ToArray();
}
}
And the model class, rhather obvious, but anyway I’ll put the code here, looks like this
public class TimeZone
{
public string Name { get; set; }
public TimeSpan DateTimeOffset { get; set; }
}
Having that, we can proceed further. We will be showing time, so it has to be ‘refreshed’ or ‘updated’ accordingly, like a watch. The first concept of welcome page of Favorite Time Zones project looks like this:
At the top, there will be displayed users current time and underneath it there will be a list with favorite time zones. After a little bit of design of the page, I put a lot of emphasis on little, we can start playing with some logic around it. First thing we do, is to get the user time clock to work. Usually you would create a Clock.cs class and imitate time lapsing with System.Threading, inifinite loop and Thread.Sleep(). Maybe it’s not the best way to create clock when you have to be really precise, but for our purposes it’s enough. To start new Thread you could do something like, right ?
static void Main(string[] args)
{
var newThread = new Thread(new ThreadStart(SomeFunctionThatRunsInNewThread));
newThread.Start();
}
No, you cannot. Not in a framework for Universal Apps, with them you have to use Tasks
. This implicates that we can no longer use Thread.Sleep(), but with help comes Task.Delay()
, its equivalent. No worries, we can use Task
's, although I’m rhater used to Threads
. After a while you will find Tasks
really useful, because of their usage with async
and await
functions, that really simplifies things. But let’s stay on the topic of updateing our clock. Clock itself could look like this
public class Clock
{
private const uint MIN_CLOCK_TICK_INTERVAL = 100; // 100 ms.
private uint _clockTickInterval;
public event EventHandler Tick;
public Clock(uint clockTickInterval)
{
_clockTickInterval = clockTickInterval;
}
public async void Start()
{
await Task.Run(() => { ClockStart(); });
}
private async void ClockStart()
{
if (Tick != null)
{
while (true)
{
await Task.Delay(Math.Max((int)_clockTickInterval, (int)MIN_CLOCK_TICK_INTERVAL));
Tick(this, EventArgs.Empty);
}
}
}
}
It has a ctor()
that takes tick interval and has one public method, Start()
. Although, the most important part is the Tick event handler that we will register to and we will update our clock once the event is invoked. ClockStart()
method is an endless loop, called from ClockStart()
in a separete Thread (Task
), that ‘Ticks’ every specified interval time. In our case it’s going to be 1 sec. Let’s then start our clock and register to its tick event.
public class MainPageViewModel : ViewModelBase
{
private Clock _clock;
private DateTime _yourTime;
public DateTime YourTime
{
get
{
_yourTime = DateTime.Now;
return _yourTime;
}
}
public MainPageViewModel()
{
_clock = new Clock(1000); // 1000 ms = 1 sec.
_clock.Tick += ClockTick;
_clock.Start();
}
private void ClockTick(object sender, EventArgs e)
{
RaisePropertyChanged(() => YourTime);
}
}
This is our ViewModel class for MainPage. It inherits from ViewModelBase
(MvvmLight). It provides implementation for INotifyPropertyChanged
interface and gives us easy way of communicating to the View that some changes in the ViewModel
have been made. This notification mechanism is done with RaisePropertyChanged
method. In the ctor()
we are creating a Clock (not the best place for it, but this is done only for the example purposes) and registering EventHandler
for the Tick
event and then we Start()
the clock. Everything looks great. Clock is working, Tick
event is fired and ‘captured’ so we could refresh View
and its done with RaisePropertyChanged
. Should work, right ?
This is what you’ll get. Exception, telling you that some thread wanted to access area that he wasn’t allowed to enter.
The application called an interface that was marshalled for a different thread
This is rhater obvious when you think about it. I’ll relate this situation to a real life one. Let’s say you are driving a car and next to you sits a passanger. You’re on a long and boring drive (Main Thread), straight road for hundreds of kilomiters, and all of a sudden, you don’t know why, your passanger is trying to grab the wheel and turn the car (different Thread). So what do you do ? Of course you are going to slap his hand(s) and ‘revoke’ his try to kill you and himself. This rule applies even when it comes to a radio station, slap ;] ! What in a situation if passanger had a really good reason to try to interrupt you ? Like, we are going to miss a turn or they started to play Justin B. on the radio. So the passanger could succeed with his intentions and actions, he is obligated to say to a driver ‘listen, you should take next turn left’ or in our case ‘listen, there is something that you should update on your view’ and then the driver, who has proper permisions, will respond with an action. This is done through a DispatcherHelper
and CheckBeginInvokOnUI()
method in MvvmLight library.
private void ClockTick(object sender, EventArgs e)
{
//RaisePropertyChanged(() => YourTime);
DispatcherHelper.CheckBeginInvokeOnUI(() => { RaisePropertyChanged(() => YourTime); });
}
It works!