Friday, 11 April 2014

Using the CameraCaptureUI class in a C++/CX Windows Store app

Previously I extended the C# sample photo viewer app by integrating capturing photos into the app.

This blog posts extends my C++/CX sample photo viewer app 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.

<local:MvvmPage.BottomAppBar>
    <CommandBar HorizontalAlignment="Right">
        <AppBarButton Command="{Binding TakePhotoCommand}"
                      Label="Take Photo"
                      Icon="Camera">
        </AppBarButton>
    </CommandBar>
</local:MvvmPage.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.

void MainPageViewModel::TakePhoto(Object^ parameter)
{
    auto capturedFile = make_shared<StorageFile^>(nullptr);
 
    m_captureService->CapturePhotoAsync().then([capturedFile](StorageFile^ file)
    {
        assert(IsBackgroundThread());
        if (file == nullptr)
        {
            cancel_current_task();
        }
        (*capturedFile) = file;
        return file->CopyAsync(KnownFolders::PicturesLibrary, file->Name, NameCollisionOption::GenerateUniqueName);
    }, task_continuation_context::use_arbitrary()).then([capturedFile](StorageFile^ file)
    {
        assert(IsBackgroundThread());
        return (*capturedFile)->DeleteAsync();
    }, task_continuation_context::use_arbitrary()).then([this]()
    {
        assert(IsMainThread());
        OnPropertyChanged("Photos");
    });
}

The TakePhoto member function invokes the CapturePhotoAsync member function 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 continuation chain copies the photo to the pictures library before deleting the version stored in the TempState directory. In the final continuation, property change notification is fired on the Photos property to refresh the thumbnails shown on the MainPage. This causes the GetPhotosAsync member function in the FileSystemRepository class to be executed to retrieve the thumbnail data.

Photo capture is handled by the CameraCaptureService, which implements the pure virtual member functions in the CaptureService abstract base class. The App class contains a member variable, m_captureService, of type CaptureService, which is instantiated as a shared pointer of type CameraCaptureService. This instance is created as a shared pointer so that there’s only a single instance of the CameraCaptureService class in the app, which is then passed between the required classes. The CameraCaptureService class instance is then exposed by the GetCaptureService member function, which is called by the ViewModelLocator constructor. The ViewModelLocator then passes the CameraCaptureService instance into the MainPageViewModel class from where camera capture is invoked.

task<StorageFile^> CameraCaptureService::CapturePhotoAsync()
{
    auto dialog = ref new CameraCaptureUI();
    dialog->PhotoSettings->MaxResolution = CameraCaptureUIMaxPhotoResolution::HighestAvailable;
    dialog->PhotoSettings->Format = CameraCaptureUIPhotoFormat::Jpeg;
    dialog->PhotoSettings->AllowCropping = true;
 
    return create_task(dialog->CaptureFileAsync(CameraCaptureUIMode::Photo)).then([](StorageFile^ file)
    {
        return 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[2]

The overall effect is that when the user takes a photo, the app returns to the thumbnail view on the MainPage. The thumbnails are then refreshed, with the thumbnail for the new photo appearing.

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. When the user takes a photo the app returns to the thumbnail view on the MainPage. The thumbnails are then refreshed, with the thumbnail for the new photo appearing.

The sample app can be downloaded here.

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.

Thursday, 3 April 2014

DevWeek2014–Accelerating Windows Store app development using Prism for the Windows Runtime

Today I spoke at DevWeek 2014 about Accelerating Windows Store app development using Prism for the Windows Runtime. The slides from the talk can be found here.

The code demos I used are:

For further resources see:

Thanks to DevWeek for giving me the opportunity to present, and to all attendees for listening.

DevWeek2014–Building native Windows Store apps for C# developers

Yesterday I spoke at DevWeek 2014 about Building native Windows Store apps for C# developers. The slides from the talk can be found here.

The code I walked through (the PhotoViewer sample app) can be downloaded here.

For further resources see:

Thanks to DevWeek for giving me the opportunity to present, and to all attendees for listening.

Wednesday, 19 March 2014

NDC Oslo 2014

NDC Is Norway’s largest conference dedicated to .NET and Agile development, and is backed by Microsoft. It attracts speakers and participants from all around, and this year I’ll be speaking there.

This year the sessions include content on ASP.NET Web API, Azure, Big Data, Continuous Delivery, DevOps, F#, HTML5, Knockout, Windows 8.1, and Windows Phone. For more info about what can be expected at NDC see the trailer below, and go to the website.

Monday, 10 March 2014

Capturing and setting a ScrollViewer’s scroll position to maintain scroll position across view states

Previously I’ve written about how Prism for the Windows Runtime can be used to save and restore view state. This is achieved by overriding the SaveState and LoadState methods in a page that derives from the VisualStateAwarePage class, and then saving the desired state. 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. However, I didn’t document how the scroll position is obtained from a control, and re-applied when the window size changes.

In this blog post I’ll explain how the scroll position is obtained from a control, and re-applied when the window size changes. The approach is to capture the ScrollViewer’s offset value as a proportion of its total size. Then, once the user rotates the device or changes the size of the frame (thus effectively resizing the window), the offset proportion is restored to the control. This means that if a GridView control has enough content to pan, and you pan to 80% of the content, you can rotate the device, or switch to MinimalLayout, and the scroll bar should be vertical but still at 80% of the content. In addition, if you navigate forward and then back, the scroll bar should still be at 80% of the content.

This technique was devised as an intuitive user experience for the AdventureWorks Shopper reference implementation.

Implementation

When the page loads the ScrollViewer embedded in the GridView is captured by the Loaded event handler for the page. The Loaded event fires when the control has been constructed and added to the object tree, meaning that it’s ready for interaction.

private void photosGridView_Loaded(object sender, RoutedEventArgs e)
{
    _photosGridViewScrollViewer = VisualTreeUtilities.GetVisualChild<ScrollViewer>(photosGridView);
}

This method uses the GetVisualChild method in the VisualTreeUtilites class (not shown here). In this usage the method walks the visual tree to obtain the ScrollViewer instance located in the GridView instance.

The LayoutUpdated event handler is then used to capture the ScrollViewer’s scroll position. The LayoutUpdated event fires when the layout of the visual tree changes, due to layout-relevant properties changing value or some other action that refreshes the layout.

private void photosGridView_LayoutUpdated(object sender, object e)
{
    _scrollViewerOffsetProportion = ScrollViewerUtilities.GetScrollViewerOffsetProportion(_photosGridViewScrollViewer);
}

This method simply uses the GetScrollViewerOffsetProportion in the ScrollViewerUtilites class to calculate the offset proportion.

public static double GetScrollViewerOffsetProportion(ScrollViewer scrollViewer)
{
    if (scrollViewer == null) return 0;
 
    var horizontalOffsetProportion = (scrollViewer.ScrollableWidth == 0) ? 0 : (scrollViewer.HorizontalOffset / scrollViewer.ScrollableWidth);
    var verticalOffsetProportion = (scrollViewer.ScrollableHeight == 0) ? 0 : (scrollViewer.VerticalOffset / scrollViewer.ScrollableHeight);
 
    var scrollViewerOffsetProportion = Math.Max(horizontalOffsetProportion, verticalOffsetProportion);
    return scrollViewerOffsetProportion;
}

The offset proportion is calculated as the maximum of either the HorizontalOffset divided by ScrollableWidth or the VerticalOffset divided by the ScrollableHeight. This value can then be restored when the user rotates their device or resizes the frame, in order to scroll to the same location.

After the page size changes the ScrollViewer can be set with the offset proportion. However, we must first wait for the ScrollViewer to finish rendering its items. One way to determine this is to examine the ComputedHorizontalScrollBarVisibility and ComputedVerticalScrollBarVisibilty properties in the SizeChanged event handler. The SizeChanged event fires when either the ActualHeight or ActualWidth property value changes on the page.

private void Page_SizeChanged(object sender, SizeChangedEventArgs e)
{
    var scrollViewer = VisualTreeUtilities.GetVisualChild<ScrollViewer>(photosGridView);
 
    if (scrollViewer != null)
    {
        if (scrollViewer.ComputedHorizontalScrollBarVisibility == Visibility.Visible && scrollViewer.ComputedVerticalScrollBarVisibility == Visibility.Visible)
        {
            ScrollViewerUtilities.ScrollToProportion(scrollViewer, _scrollViewerOffsetProportion);
        }
        else
        {        
            DependencyPropertyChangedHelper horizontalHelper = new DependencyPropertyChangedHelper(scrollViewer, "ComputedHorizontalScrollBarVisibility");
            horizontalHelper.PropertyChanged += ScrollBarVisibilityChanged;
 
            DependencyPropertyChangedHelper verticalHelper = new DependencyPropertyChangedHelper(scrollViewer, "ComputedVerticalScrollBarVisibility");
            verticalHelper.PropertyChanged += ScrollBarVisibilityChanged;
        }
    }
}

Here, if the ComputedHorizontalScrollBarVisibility and ComputedVerticalScrollBarVisibility both equal Visibility.Visible then the ScrollViewer can be scrolled by calling the ScrollToProportion method of the ScrollViewerUtilities class. If the two properties are not set to Visibility.Visible, then the DependencyPropertyChangedHelper class is used to listen for changes in the two properties. The ScrollBarVisibilityChanged event handler is executed when either of the two properties change.

private void ScrollBarVisibilityChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    var helper = (DependencyPropertyChangedHelper)sender;
    var scrollViewer = VisualTreeUtilities.GetVisualChild<ScrollViewer>(photosGridView);
 
    if (((Visibility)e.NewValue) == Visibility.Visible)
    {
       ScrollViewerUtilities.ScrollToProportion(scrollViewer, _scrollViewerOffsetProportion);
        helper.PropertyChanged -= ScrollBarVisibilityChanged;
    }
 
    if (_isPageLoading)
    {
        photosGridView.LayoutUpdated += photosGridView_LayoutUpdated;
        _isPageLoading = false;
    }
}

This event handler checks if either of the two properties have changed to Visibility.Visible. If they have, the ScrollViewer is updated by calling the ScrollToProportion method of the ScrollViewerUtilities class.

public static void ScrollToProportion(ScrollViewer scrollViewer, double scrollViewerOffsetProportion)
{
    if (scrollViewer == null) return;
    var scrollViewerHorizontalOffset = scrollViewerOffsetProportion * scrollViewer.ScrollableWidth;
    var scrollViewerVerticalOffset = scrollViewerOffsetProportion * scrollViewer.ScrollableHeight;
 
    scrollViewer.ChangeView(scrollViewerHorizontalOffset, scrollViewerVerticalOffset, null);
}

In the ScrollToProportion method the offset is calculated as the offset proportion multiplied by the ScrollableHeight, and the offset proportion multiplied by the ScrollableWidth (one for landscape, one for portrait). Then the ChangeView method is called on the ScrollViewer instance to load a new view into the viewport using the offsets.

If the user navigates to another page or the app is terminated, the offset proportion is lost. Therefore, the SaveState and LoadState methods are used to preserve this value. For more information about this see one of my previous blog posts on this topic.

Summary

In this blog post I’ve explained how the scroll position is obtained from a control, and re-applied when the window size changes. The approach is to capture the ScrollViewer’s offset value as a proportion of its total size. Then, once the user rotates the device or changes the size of the frame, the offset proportion is restored to the control

The sample app can be downloaded here.