Category Archives: Development

Creating your own photo viewer app for Windows 8

Problem

In Windows 8 you can use the Photo app to view pictures in My Pictures folder. While the Photo app has lots of features and is updated with new ones quite often there is one feature I miss – the ability to navigate between pictures in the same folder as the picture I just clicked on.

Solution 

Writing a Windows Store app that solves the problem is very easy and here is how we do it.

If you are running Visual Studio on Windows 8 it comes preinstalled with templates to build Windows Store apps. For the purposes of this tutorial we are going to use the Blank App XAML template. I am going to name my app PhotoViewer.

create-project

Next we need to declare what the app needs to do it’s work and what it can do. Double-click Package.appxmanifest file in Solution Explorer, click on Capabilities tab and tick Pictures Library, this will give us access to user’s Pictures library, which is also a limitation of our app – we can only use it to view pictures that are stored in Pictures library.

We don’t need to connect to internet so you can untick Internet (client) capability.

set-capabilities

Next we are going to declare that our app can handle certain types of files. Navigate to Declarations tabs and add File Type Associations from Available Declarations dropdown. In the left pane type in Display Name and Name. Display Name is going to be used to describe the files this app can open, Windows Explorer will use this value to describe the files if you make PhotoViewer the default app to open files it supports. Next, tick Open is safe checkbox, if you wish you can tick Always unsafe if you want the user to confirm before file is opened. And lastly, add Supported file types. For the purposes of this example we are going to support jpg files, but you can add as many as you wish. Anyway, to add jpg type in image/jpeg in Content type and .jpg in File type text box, just like on the screenshot below.

set-declarations

With configuration done we can do some coding.

Open App.xaml.cs and override OnFileActivated method. The code we are going to put in OnFileActivated looks like this:

  
        protected override void OnFileActivated(FileActivatedEventArgs args)
        {
            var rootFrame = Window.Current.Content as Frame;

            if (rootFrame == null)
            {
                rootFrame = new Frame();
            }

            if (args.Files != null && args.Files.Any())
            {
                rootFrame.Navigate(typeof(MainPage), args);
                Window.Current.Content = rootFrame;
                var p = rootFrame.Content as MainPage;
            }

            Window.Current.Activate();
        }

In here we are setting up the contents of the app by creating a new Frame object which acts as a container for our pages. Our app’s starting page is MainPage so we navigate to it and passing file activation arguments to it. When MainPage is navigated to we need to set our data source. In order to do so we will tweak MainPage.cs like so:

    public sealed partial class MainPage : LayoutAwarePage
    {
        private FolderViewModel _viewModel;

        public MainPage()
        {
            this.InitializeComponent();
            _viewModel = new FolderViewModel();
            DataContext = _viewModel;
        }

        /// <summary>
        /// Populates the page with content passed during navigation.  Any saved state is also
        /// provided when recreating a page from a prior session.
        /// </summary>
        /// <param name="navigationParameter">The parameter value passed to
        /// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
        /// </param>
        /// <param name="pageState">A dictionary of state preserved by this page during an earlier
        /// session.  This will be null the first time a page is visited.</param>
        protected override async void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
        {
            // Allow saved page state to override the initial item to display
            if (pageState != null && pageState.ContainsKey("DataContext"))
            {
                _viewModel = (FolderViewModel)pageState["DataContext"];
            }
            else if (navigationParameter is FileActivatedEventArgs)
            {
                await _viewModel.Initialize((FileActivatedEventArgs)navigationParameter);
            }

            // To make debugging easier this code is only included in release mode
            if (!_viewModel.ContainsPictures)
            {
                await ShowWarningAndClose();
            }
        }

        private async Task ShowWarningAndClose()
        {
            var messageDialog = new MessageDialog("To use PhotoViewer select a picture in Pictures Library first.", "No Picture Selected");

            messageDialog.Commands.Add(new UICommand(
                "Close",
                new UICommandInvokedHandler(this.CommandInvokedHandler)));

            await messageDialog.ShowAsync();
        }

        private void CommandInvokedHandler(IUICommand command)
        {
            App.Current.Exit();
        }

        /// <summary>
        /// Preserves state associated with this page in case the application is suspended or the
        /// page is discarded from the navigation cache.  Values must conform to the serialization
        /// requirements of <see cref="SuspensionManager.SessionState"/>.
        /// </summary>
        /// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
        protected override void SaveState(Dictionary<String, Object> pageState)
        {
            pageState["DataContext"] = _viewModel;
        }
    }

First, note that I have changed MainPage class to inherit from LayoutAwarePage. LayoutAwarePage is a class that is included in other Windows Store app templates, such as Grid App XAML template. It handles a number of page navigation aspects hence I copied it over to my PhotoViewer app. When you copy this class over also copy BindableBase and SuspensionManager classes, we will need them. Next notice that I am setting DataContext to our view model in MainPage constructor, but this is not where the view model is initialized.

View model initialization occurs in LoadState method. There are two ways of initializing the view model; we could be coming back to MainPage in which case the view model is initialized from page state (which gets saved in SaveState method) or we could be navigating to the page for the first time in which case we initialize view model based on the arguments that were passed in.

This class also contains some code to notify the user when there is nothing to display but that is fairly straight forward. Let’s have a look at the FolderViewModel class instead:

    internal class FolderViewModel : BindableBase
    {
        private ObservableCollection<PictureModel> _pictures = new ObservableCollection<PictureModel>();
        public ObservableCollection<PictureModel> Pictures 
        {
            get { return _pictures; }
        }

        private async Task SetPictures(IEnumerable<StorageFile> pictures)
        {
            _pictures.Clear();
            foreach (var picture in pictures.Select(f => new PictureModel(f)))
            {
                await picture.Initialize();
                _pictures.Add(picture);
            }
        }

        public bool ContainsPictures { 
            get { return Pictures != null && Pictures.Any(); }
        }

        public async Task Initialize(FileActivatedEventArgs args)
        {
            var file = args.Files[0];

            var folderPath = file.Path.Replace(file.Name, string.Empty);
            var folder = await StorageFolder.GetFolderFromPathAsync(folderPath);

            await SetPictures(await folder.GetFilesAsync());
        }
    }

When initializing the view model we take in file activation arguments (FileAcivatedEventArgs) which contain the path of the photo that was selected in Windows Explorer. Note that most IO APIs in WinRT are asynchronous by nature, hence the usage of async/await keywords. Based on the path being passed we determine the contaning folder and initialize our pictures collection with all photos from the same folder. The collection contains objects of PictureModel type which is a simple class containing information we need on the UI:

    internal class PictureModel : BindableBase
    {
        private StorageFile _file;

        public PictureModel(StorageFile file)
        {
            _file = file;
        }

        public string UniqueId { 
            get { return _file.Path; } 
        }

        private BitmapImage _image;
        public BitmapImage Image
        {
            get { return _image; }
        }

        public async Task Initialize()
        {
            var fileStream = await _file.OpenAsync(Windows.Storage.FileAccessMode.Read);
            BitmapImage bmp = new BitmapImage();
            bmp.SetSource(fileStream);
            _image = bmp;
            OnPropertyChanged("Image");
        }
    }

The bit that is important is in Initialize method – you have to initialize BitmapImage from a file stream, if you don’t you will end up with a page but no image.

Now that we have all the code we only need to create some XAML to display the information. And here is how it will look like:

        <FlipView
            AutomationProperties.AutomationId="ItemsFlipView"
            AutomationProperties.Name="Item Details"
            ItemsSource="{Binding Pictures}">

            <FlipView.ItemContainerStyle>
                <Style TargetType="FlipViewItem">
                    <Setter Property="Margin" Value="0,0,0,0"/>
                </Style>
            </FlipView.ItemContainerStyle>

            <FlipView.ItemTemplate>
                <DataTemplate>

                    <!--
                        UserControl chosen as the templated item because it supports visual state management
                        Loaded/unloaded events explicitly subscribe to view state updates from the page
                    -->
                    <UserControl Loaded="StartLayoutUpdates" Unloaded="StopLayoutUpdates">
                        <Grid>
                            <Image Height="Auto"
                                   Width="Auto"
                                   Source="{Binding Image}">
                            </Image>
                        </Grid>

                        <VisualStateManager.VisualStateGroups>

                            <!-- Visual states reflect the application's view state inside the FlipView -->
                            <VisualStateGroup x:Name="ApplicationViewStates">
                                <VisualState x:Name="FullScreenLandscape"/>
                                <VisualState x:Name="Filled"/>

                                <!-- Respect the narrower 100-pixel margin convention for portrait -->
                                <VisualState x:Name="FullScreenPortrait">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="image" Storyboard.TargetProperty="Margin">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="97,20,87,67"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>

                                <!-- When snapped, the content is reformatted and scrolls vertically -->
                                <VisualState x:Name="Snapped">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="image" Storyboard.TargetProperty="Margin">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="17,20,17,67"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>

                    </UserControl>
                </DataTemplate>
            </FlipView.ItemTemplate>
        </FlipView>

FlipView is a perfect control for what we are trying to achieve as it allows us to flip through items in our folder. A few things to notice here:

  • ItemsSource is bound to Pictures property of our FolderViewModel,
  • FlipView’s DataTemplate contains an Image control whose Source is bound to Image property of PictureModel class.

Conclusion

This is it. Running the app from Visual studio will only display a warning that no pictures could be found, but if you go to Windows Explorer and right click on a photo in Pictures library you should see an option to open it with PhotoViewer. Overall it is a very basic app, but it is a good start. Complete source code of this tutorial is on GitHub.