How do I work with images in a portable class library targeting Windows Store Apps and WP7,WP8,WPF?

Iris Classon picture Iris Classon · Sep 20, 2012 · Viewed 7.8k times · Source

I am working on a first PCL that targets : WSA (Windows Store Application), WPF,WP7,WP8. We can say that it is a rolerdex kind of application, you have contacts , they have contact details and images. (it's not, but I can't give details about the application, so I am using a very simple example instead). Here are some of my questions :)

  1. Should I have the images in the PCL?

If yes:

  1. How do reference the image for usage in WSA?
  2. How do I best solve scaling with the scale qualifiers etc. when used across different projects?

I am not using a database and the images are not downloaded from an external service- I would like to keep the images (not many really) locally, in the app or in the PCL.

EDIT: I just want to display images. That's it. It's a static rolerdex, you can't add new people. I just want to display 5 number of people and their image (in the PCL). How do I reference the images if it's a Windows Store Application?

I have a binding and the DataContext is set to a ViewModel in the PCL. The ViewModel aggregates the data to be displayed from the models. The property I've bound against is MyImage. Ignoring the other platforms, how would the Uri look like? Everything else works fine.

I really just want help with these three questions, although I really appreciate all the answers!!!

Answer

David Kean picture David Kean · Sep 20, 2012

For a lot of cases, images are platform-specific. They need to cater for size and DPI of the device itself, and would need to fit in with the look and feel of the application. For these situations, I would have the View itself decide what images to show to the user, probably based on some sort of state/mode provided by the ViewModel.

However, these are cases where the images need to come from the ViewModel, for example, in the case of the sender thumbnails that get displayed in mail applications. In these cases, I have the ViewModel return some sort of a platform-agnostic concept of an image (such as byte[]), and then have the platform-specific projects convert that into something that their UI stack understands (in XAML, this would be a ImageSource).

The code would look something like this:

Portable project:

using System.IO;
using System.Reflection;

namespace Portable
{
    public class ViewModel
    {
        private byte[] _image = LoadFromResource("Image.png");

        public byte[] Image
        {
            get { return _image; }
        }

        private static byte[] LoadFromResource(string name)
        {
            using (Stream stream = typeof(ViewModel).GetTypeInfo().Assembly.GetManifestResourceStream("Portable." + name))
            {
                MemoryStream buffer = new MemoryStream();
                stream.CopyTo(buffer);

                return buffer.ToArray();
            }
        }
    }
}

Note: You will need to remove or add GetTypeInfo() depending on the platforms you are targeting.

Here we're reading from an embedded resource (Properties -> Build Action -> Embedded Resource), but you could imagine this coming from the network, or somewhere else.

Windows Store app project: In the Windows Store app, you would have a value converter to convert from byte[] -> ImageSource:

using System;
using System.IO;
using Windows.Storage.Streams;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media.Imaging;

namespace App
{
    public class ByteToImageSourceValueConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            InMemoryRandomAccessStream s = new InMemoryRandomAccessStream();

            byte[] bytes = (byte[])value;
            Stream stream = s.AsStreamForWrite();
            stream.Write(bytes, 0, bytes.Length);
            stream.Flush();
            stream.Seek(0, SeekOrigin.Begin);


            BitmapImage source = new BitmapImage();
            source.SetSource(s);

            return source;           

        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }
}

In the code behind of the View, set the DataContext:

DataContext = new ViewModel();

Then in the View itself binding to the ViewModel.Image property, and set the converter:

<Page.Resources>
    <local:ByteToImageSourceValueConverter x:Name="ImageConverter"/>
</Page.Resources>

<Grid >
    <Image HorizontalAlignment="Left" Height="242" Margin="77,10,0,0" VerticalAlignment="Top" Width="278" Source="{Binding Image, Converter={StaticResource ImageConverter}}"/>

</Grid>