Tuesday, 8 April 2014

Using the CameraCaptureUI class in a Windows Store app

Previously I extended the sample photo viewer app so that the user will see the page content scrolled to the exact location it was at prior to termination or navigation, regardless of whether the page orientation has changed in between termination and reactivation.

This blog post extends the sample app further by integrating capturing photos into the app. It uses the CameraCaptureUI class to capture photos. This class provides a UI for capturing audio, video, and photos from a camera, and provides controls for cropping photos and videos, time-delayed capture, and for adjusting the camera’s settings such as resolution, audio device, brightness, and contrast.

Implementation

The MainPage now includes an AppBarButton that allows the user to take a photo.

<prism:VisualStateAwarePage.BottomAppBar>
    <CommandBar HorizontalAlignment="Right">
        <AppBarButton Command="{Binding TakePhotoCommand}"
                      Label="Take Photo"
                      Icon="Camera">
        </AppBarButton>
    </CommandBar>
</prism:VisualStateAwarePage.BottomAppBar>

The AppBarButton binds to the TakePhotoCommand in the MainPageViewModel class. The TakePhotoCommand property is a DelegateCommand that is initialised in the MainPageViewModel constructor to execute the TakePhoto method when the AppBarButton is selected.

private async void TakePhoto()
{
    StorageFile capturedFile = await _captureService.CapturePhotoAsync();
    if (capturedFile != null)
    {
        bool error = false;
        try
        {
            await capturedFile.CopyAsync(KnownFolders.PicturesLibrary, capturedFile.Name, NameCollisionOption.GenerateUniqueName);
            await capturedFile.DeleteAsync(StorageDeleteOption.PermanentDelete);
 
            // Refresh page content
            await Task.Delay(TimeSpan.FromSeconds(5));
            Photos = await _repository.GetPhotosAsync();
            OnPropertyChanged("Photos");
        }
        catch (FileNotFoundException)
        {
            error = true;
        }
 
        if (error)
        {
            await _messageService.ShowAlertAsync("There was an error when trying to read the photo. Please check that the photo is still available.", "Error with photo.");
        }
    }
}

The TakePhoto method invokes the CapturePhotoAsync method in the CameraCaptureService class, which returns a StorageFile object representing the captured photo. This photo is saved on disk in the apps TempState directory. Therefore, the method copies the photo to the pictures library before deleting the version stored in the TempState directory. After waiting five seconds to ensure that the file system index has been updated with the new photo details, the Photos property is updated by calling the GetPhotosAsync method on the FileSystemRepository instance. If the file is deleted before this operation can complete an error message is displayed to the user in a modal message box.

Photo capture is handled by the CameraCaptureService, which implements the ICaptureService interface. This service class is injected into the MainPageViewModel class by the Unity dependency injection container, after having been registered with the container in the App class.

public class CameraCaptureService : ICaptureService
{
    public async Task<StorageFile> CapturePhotoAsync()
    {
        var dialog = new CameraCaptureUI();
        dialog.PhotoSettings.MaxResolution = CameraCaptureUIMaxPhotoResolution.HighestAvailable;
        dialog.PhotoSettings.Format = CameraCaptureUIPhotoFormat.JpegXR;
        dialog.PhotoSettings.AllowCropping = true;
 
        var file = await dialog.CaptureFileAsync(CameraCaptureUIMode.Photo);
        return (file == null) ? null : file;
    }
}

Once the CameraCaptureUI instance is created, several PhotoSettings properties are specified to customize the capture experience. These are the maximum resolution, photo format, and a boolean value that enables the photo cropping interface. The CaptureFileAsync method is then called to launch the UI for capturing a photo from the camera. This method takes a CameraCaptureUIMode enumeration that specifies that the user can only capture a photo, rather than video. The user then has control over when to start the capture. When the CaptureFileAsync operation completes, a StorageFile object is returned, which is then returned to the calling method (in this case the TakePhoto method of the MainPageViewModel class). The UI used by the CameraCaptureUI class is shown in the following screenshot.

screenshot_04082014_115643

The overall effect is that when the user takes a photo, the app returns to the thumbnail view on the MainPage. After 5 seconds the page refreshes with the thumbnail for the new photo appearing. However, without additional code in the Photo model object, when the thumbnail appears it will be blank (grey specifically). This is because the file system will not have updated the thumbnail data for the photo. Therefore, it becomes necessary to modify the Photo model object to respond to the ThumbnailUpdated event of the FileInformation object.

private async void OnFileThumbnailUpdated(IStorageItemInformation sender, object args)
{
    await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() =>
        {
            OnPropertyChanged("Thumbnail");
        }));
}

The Photo constructor registers the OnFileThumbnailUpdated event handler for the ThumbnailUpdated event of the FileInformation object. This event handler fires when the thumbnail for a FileInformation object is updated, and simply fires property change notification for the Thumbnail property of the FileInformation object. However, the property change notification has to be dispatched back to the main thread (which is the UI thread), as the code that creates Photo objects (GetPhotosAsync in the FileSystemRepository class) is running on a background thread from the thread pool. This works by using the CoreDispatcher class’s RunAsync method to run code in the main thread. You can get access to a CoreDispatcher object from the Dispatcher property of the CoreWindow object.

The overall effect is that when the thumbnail for the photo is updated, the thumbnail on the MainPage will be updated. This happens quickly enough that the user won’t see a blank (grey) thumbnail, but without this additional code, the user will see a blank thumbnail.

Summary

This blog post has extended the sample app by further by integrating capturing photos into the app. It uses the CameraCaptureUI class to capture a photo. The MainPage is then updated with the thumbnail for a photo, with an event handler for the ThumbnailUpdated event of the FileInformation object dispatching the thumbnail property change notification back to the main thread.

The sample app can be downloaded here.

2 comments:

  1. the link to download the sample is not working.

    ReplyDelete
    Replies
    1. For some reason OneDrive has destroyed my download links. Here's a new link: https://1drv.ms/u/s!AqqxnsyjuZgbgUzW5cbuvh3w0pj6

      Delete