Windows Phone - Image control and its Source property
Recently I was struggling with trying to set images (being a byte[] and/or being an Asset file and/or being an IsolatedStorage file) as an Image.Source. I didn’t know what should be the URI in case of dealing with Assets and IsolatedStorage, and I wasn’t sure what to do with a byte array aka byte[] as well. After a bit of research and some advices from my work colleagues I’ve manage to get it to work
If you are curious what Microsoft have to say about it, just go here and check official Image control site, scroll down to Remarks to learn how to set up a images from web URL and from relative URI (e.g. your app Assets). You can also learn from it, that Images are being cached. Every another time, same image, is referenced, it’s being taken from cache. It’s worth having a glimpse, at least on the remarks and examples. But going back to the subject
Image.Source as byte array aka byte[] (using async and await)
I assume, in this example, that you already have a byte array of image data prepared to be displayed on the screen. If not, this is how you can turn your StorageFile into byte array. Now, you can’t just simply assign Image.Source
to array of bytes, XAML will not know what to do with it, and how to display transform it to a bitmap an show it on the screen. It will fail. Hopefully, there’s such thing as Converters, which come with help. As for an example, XAML and ViewModel are pretty straight forward and could look like this
<Image Width="100"
Height="100"
Source="{Binding FileData, Converter={StaticResource ByteArrayToImageConverter}}" />
private byte[] fileData;
public byte[] FileData
{
get
{
return fileData;
}
set
{
fileData = value; RaisePropertyChanged(() => FileData);
}
}
The real ‘magic’ happens in the Converter, which looks like this
public class ByteArrayToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value == null || !(value is byte[]))
{
return null;
}
var writableBitmap = new WriteableBitmap(100, 100);
var dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
Task.Run(async () =>
{
using (var ms = new InMemoryRandomAccessStream())
{
using (var writer = new DataWriter(ms.GetOutputStreamAt(0)))
{
writer.WriteBytes((byte[])value); await writer.StoreAsync();
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
writableBitmap.SetSource(ms); writableBitmap.Invalidate();
});
}
}
});
return writableBitmap;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
}
Few things to notice in this code. First is that we’re using WritableBitmap
, which allows us to assign Source with it (empty) and return it from Converter, which releases UI thread, and transform our byte array, inside a separate task, into stream, which then is being set as source of earlier mentioned WritableBitmap
. In more deatils, loading of the image is handled in the Task.Run()
context, notice lack of await key word in front of the Task.Run()
call, which means it will be done in another thread but, app won’t wait until it’s done, hence UI thread won’t be bothered, and app will respond properly on users actions. Another thing to bear in mind here is the dispatcher
. Remember, that if you want to make any changes on the screen, in the UI, you need to do them on the UI thread. Dispatcher is the key to do just that. Call RunAsync()
method on it, and you’re safe from getting and exception
The application called an interface that was marshalled for a different thread
Last but not least, is that after setting the source SetSource(ms)
you need to call Invalidate()
method, which will force a redraw of entire bitmap. Oh, and remember that SetSource(ms)
method has to be called before you close and/or dispose your InMemoryRandomAccessStream
(in this case ms), that’s why it’s being called inside of the using
statement.
Image.Source as project file (Assets file)
This one seems to be the easiest one, but it made me sweat a bit when I tried setting relative UriSource
of a BitmapImage and assign it as a Image.Source
from the code behind. Even though, it’s well documented on the official, msdn, site that I already pointed out in my short prologue (for convenience), and its mechanism is pretty straightforward, it gave me a little headache. Anyway...All you need to do, in your XAML file, is to set a relative path to your project file (usually taken from Assets) like so
<Image Source="Assets/image.png"/>
and image appears on the screen! Easy! It’s not that simple, though, when you try setting your project file as Image.Source
from the code behind. At least not for me, and those who decided not to read the documentation and thought it’s too easy to waste their time. Wrong! It was a mistake, I should have read it, at least just have a look. To spare you the trouble…What I did, thinking that when I set same URI as I did in XAML, which is “Assets/image.png” as a UriSource
of a BitmapImage
I would get same results. Nope...because what happens in XAML, according to documentation, is that specified URI as the Source
of the Image
control is being processed in a specific manner
..the string as a URI, and calls the equivalent of the BitmapImage(Uri) constructor
Following this information I wrote:
var res = new BitmapImage()
{
UriSource = new Uri("Assets/image.png", UriKind.Relative)
};
Wrong again! What actually happens, and you can read about it, some paragraphs below, in the same document (third time’s the charm), that what actually happens, under the hood, with Image.Source
URI is that it’s being treated as a relative path suffix, and in the end it looks something like this (depends on the page file – XAML – location in the project)
ms-appx:///Assets/image.png
That lead me to use this code instead, which finally worked
var res = new BitmapImage()
{
UriSource = new Uri(@"ms-appx:///Assets/image.png", UriKind.Absolute)
};
If you want to refer something from you AppData folder you will have to use ms-appdata:///
scheme prefix instead of ms-appx://
. You can find more information about it here
Image.Source as IsolatedStorageFile
In this case you don’t have to do much. Just set the image URI as Image.Source
and you’re done
<Image Source="{Binding ImageUri}" />
The ‘hard’ part is to save and, later on, get the actual ImageUri
. In my case, I’m using MvvmCross libraries, which, by the way, I strongly recommend as a general Mvvm framework for Windows RT (Universal apps) and cross platform development, it was pretty easy. After saving it on the device, I had to call NativePath(path)
method, which is a part of IMvxFileStore
and my URI was ready to use. I’m not getting into more specific on this matter, because it’s not the topic of this post, but you can check out MvvmCross File Plugin and the entire framework by yourself, just use NuGet and start exploring.
An example of how native URI could look like:
C:\Data\Users\DefApps\APPDATA\Local\Packages\9e8374f5-6abb-4e02-bde7-c0fd586c936e_hnp1ad7aancnj\LocalState\Images\image.png