Monday, 15 October 2012

MVVM and commands in a C++/CX app

Previously I demonstrated how to implement the MVVM pattern in C++/CX, through a small sample app that allows the user to view the photos in their Pictures library. Since then I’ve enhanced the app by using the Repository pattern, adding design-time data, and by passing serializable objects when navigating between pages. I also wrote about how to access pixel data in a C++/CX Windows Store app.

In the MVVM blog post I mentioned that you can use data binding for UI controls that cause the app to perform operations, by binding the control’s Command property to an ICommand property on a view model. This applies to any controls that derive from ButtonBase. When the control’s command is invoked, the code in the view model is executed. The advantage of this approach is that it allows you to reduce (and in some cases, completely eliminate) the amount of code in the code-behind file for the view, and it helps to promote the testability of your app.

In this blog post I’ll further extend the app so that the user can convert a photo to greyscale, on the PhotoView page. In order to do this I’ll bind the Command property of a Checkbox control to an ICommand property on the PhotoViewModel.

Implementation

I’ve added a Checkbox control to the PhotoView page, to control whether the photo should be displayed in colour or greyscale. The Checkbox binds the IsChecked property to the IsGreyscale property on the PhotoViewModel class, to determine whether the Checkbox is checked or not. For details of how the view binds to properties on the view model see MVVM in C++/CX, and the Hilo project.

<CheckBox Command="{Binding GreyscaleCommand}" 
          IsChecked="{Binding Path=IsGreyscale, Mode=TwoWay}" />
The Checkbox also binds the Command property to the GreyscaleCommand property on the PhotoViewModel class, which is of type ICommand. The GreyscaleCommand property simply returns a member variable that contains a reference to a command implementation that describes the command logic.
ICommand^ PhotoViewModel::GreyscaleCommand::get()
{
    return m_greyscaleCommand;
}
The m_greyscaleCommand member variable is initialized in the PhotoViewModel constructor. This initialization sets the method that will be invoked when the user toggles the state of the Checkbox.
m_greyscaleCommand = ref new DelegateCommand(ref new ExecuteDelegate(this, &PhotoViewModel::ConvertPhotoToGreyscale), nullptr);
The m_greyscaleCommand is set to an instance of the DelegateCommand class, which was developed for the Hilo project. The DelegateCommand specifies an ExecuteDelegate and a CanExecuteDelegate. The ExecuteDelegate is used to specify a method to be invoked when the command is executed, with the CanExecuteDelegate specifying a method to be invoked to determine whether or not the command can be executed. However, in this scenario as we always want the command to be able to execute, a nullptr is passed to the CanExecuteDelegate.

The ExecuteDelegate specifies that the ConvertPhotoToGreyscale method will be invoked when the command is executed by the user toggling the state of the Checkbox. This method uses the WriteableBitmap type to manipulate the pixel values in order to convert them to their greyscale representation. For more information see Accessing Image Pixel Data in a C++/CX Windows Store App.

void PhotoViewModel::ConvertPhotoToGreyscale(Object^ parameter)
{
    if (!m_isGreyscale)
    {
        m_photo = nullptr;
        OnPropertyChanged("Photo");
        return;
    }
 
    unsigned int width = m_photo->PixelWidth;
    unsigned int height = m_photo->PixelHeight;
 
    // Create destination bitmap
    WriteableBitmap^ destImage = ref new WriteableBitmap(width, height);
 
    // Get pointers to the source and destination pixel data
    byte* pSrcPixels = GetPointerToPixelData(m_photo->PixelBuffer);
    byte* pDestPixels = GetPointerToPixelData(destImage->PixelBuffer);
 
    // Convert pixel data to greyscale
    parallel_for(0u, height, [width, pSrcPixels, pDestPixels](unsigned int y)
    {
        for (unsigned int x = 0; x < width; x++)
        {
            byte b = pSrcPixels[(x + y * width) * 4];
            byte g = pSrcPixels[(x + y * width) * 4 + 1];
            byte r = pSrcPixels[(x + y * width) * 4 + 2];
            byte a = pSrcPixels[(x + y * width) * 4 + 3];
            byte luminance = static_cast<byte>(0.299 * r + 0.587 * g + 0.114 * b);
 
            pDestPixels[(x + y * width) * 4] = luminance;      // B
            pDestPixels[(x + y * width) * 4 + 1] = luminance; // G
            pDestPixels[(x + y * width) * 4 + 2] = luminance; // R
            pDestPixels[(x + y * width) * 4 + 3] = a;         // A
        }
    });
 
    // Update image on screen
    m_photo = destImage;
    OnPropertyChanged("Photo");
}
The ConvertPhotoToGreyscale method uses the PPL’s parallel_for function to perform an operation using the computer's multicore hardware. The code parallelizes the outer loop only because it performs enough work to benefit from the overhead for parallel processing. If you parallelize the inner loop, you will not receive a gain in performance because the small amount of work that the inner loop performs does not overcome the overhead for parallel processing.

The method also includes functionality to reset the photo from greyscale to colour by forcing the original photo to be reloaded from the file system. Note that this approach is merely illustrative in order to include the functionality, and does not offer the user experience recommended for a Windows Store app.

Summary


Data binding can be used for UI controls that cause an app to perform operations, by binding the control’s Command property to an ICommand property on a view model. This applies to any controls that derive from ButtonBase. When the control’s command is invoked, the code in the view model is executed. The advantage of this approach is that it allows you to reduce (and in some cases, completely eliminate) the amount of code in the code-behind file for the view, and it helps to promote the testability of your app.

The sample application can be downloaded here.

No comments:

Post a Comment