Monday, 15 July 2013

Creating a Flyout in C++/CX

Previously I’ve blogged about creating a Flyout using Prism for the Windows Runtime. I thought it would be useful to have a similar Flyout control available in the C++/CX version of my sample PhotoViewer app. Therefore I decided to write a Flyout control in C++/CX, similar to to Prism’s FlyoutView.

Therefore, in this blog post I’ll further extend the PhotoViewer C++/CX sample app so that a Flyout can be displayed by tapping a photo on the PhotoView page. The Flyout will display basic photo information including filename, file type, resolution, and path. A series of value converters will be used to convert the photo information into a user friendly format for display.

Please note that this blog post and sample app use the Flyout control I’ve written, not the new Flyout control which is available in Windows 8.1.

Implementation

Creating the Flyout control

The Flyout class provides the behaviour for the Flyout control. It implements the IFlyout interface, which specifies that an Open method must be provided by implementing classes. The class constructor creates a new Popup control, which will be used to display the Flyout, sets the IsLightDismissEnabled property to true and gets the Frame control for the app.

Flyout::Flyout(int flyoutSize) : m_flyoutSize(flyoutSize)
{
    m_popup = ref new Popup();
    m_popup->IsLightDismissEnabled = true;
    m_frame = safe_cast<Windows::UI::Xaml::Controls::Frame^>(Window::Current->Content);
}

The Open method initializes the Flyout control for display, initializing different properties and registering event handlers for the Activated and Closed events. These event handlers simply define logic to be executed when the Popup is closed and when the window completes activation, and includes de-registering the event handlers to prevent memory leaks.

void Flyout::Open(Object^ parameter)
{
    ...
        auto viewModel = safe_cast<IFlyout^>(DataContext);
        if (viewModel != nullptr)
        {
            viewModel->Open(parameter);
        }
    ...
}

Once the Flyout is opened the Open method of the view model class that implements the Flyout behaviour is invoked. This provides the view model class with an opportunity to perform any logic that is required to display any data on the Flyout.

Creating the Flyout view

The PhotoInformationFlyoutView derives from the Flyout class.

<local:Flyout x:Class="PhotoViewer.PhotoInformationFlyoutView"
              xmlns:local="using:PhotoViewer"
              ...
              DataContext="{Binding Source={StaticResource ViewModelLocator}, Path=PhotoInfoFlyoutVM}">

The PhotoInformationFlyoutView simply binds to the sub-properties of the File property in the PhotoInformationFlyoutViewModel class. The PhotoInformationFlyoutView is associated with the PhotoInformationFlyoutViewModel class by binding to the PhotoInfoFlyoutVM property of the ViewModelLocator class.

The PhotoInformationFlyoutView constructor must specify the width of the Flyout. The StandardFlyoutSize class provides two standard sizes for Flyouts – Narrow and Wide.

PhotoInformationFlyoutView::PhotoInformationFlyoutView() : Flyout(StandardFlyoutSize::Narrow)
{
    InitializeComponent();
}
Creating the Flyout view model

The PhotoInformationFlyoutViewModel derives from the ViewModelBase class in order to provide property change notification, and implements the IFlyout interface. As previously mentioned, this interface specifies that implementing classes must implement an Open method.

FileInformation^ PhotoInformationFlyoutViewModel::File::get()
{
    return m_file;
}
 
void PhotoInformationFlyoutViewModel::File::set(FileInformation^ value)
{
    if (value != m_file)
    {
        m_file = value;
        OnPropertyChanged("File");
    }
}
 
void PhotoInformationFlyoutViewModel::Open(Object^ parameter)
{
    File = safe_cast<FileInformation^>(parameter);
}

In addition, the PhotoInformationFlyoutViewModel class provides a File property of type FileInformation. When the Open method is called by the FlyoutService class, the File property is set to the received FileInformation parameter. This ensures that the PhotoInformationFlyoutView, which binds to the File property of the PhotoInformationFlyoutViewModel class, has data to display.

Displaying the Flyout

The FlyoutService class provides a service that allows a Flyout to be displayed. It implements the IFlyoutService interface, which specifies that a ShowFlyout method must be provided by implementing classes.

The FlyoutService constructor simply registers an event handler for the SizeChanged event, with the destructor deregistering this event handler in order to prevent memory leaks. This event will fire when the window has rendered or changed its rendering size and is used here with the ShowFlyout method to ensure that a Flyout will be displayed when the app is snapped.


void FlyoutService::ShowFlyout(Object^ parameter, IFlyout^ flyout)
{
    if (ApplicationView::Value == ApplicationViewState::Snapped)
    {
        m_isUnsnapping = true;
        m_parameter = parameter;
        m_flyout = flyout;
        ApplicationView::TryUnsnap();
    }
    else
    {
        if (flyout != nullptr)
        {
            flyout->Open(parameter);
        }
    }
}

The ShowFlyout method will display the Flyout by invoking the Open method of the Flyout class. If the app is in a snapped state it will attempt to unsnap the app, which causes the SizeChanged event handler to be executed, which will in return call the ShowFlyout method to display the Flyout.

The FlyoutService is created as a singleton in the constructor of the ViewModelLocator object. Then, when the PhotoView page is associated with the PhotoViewModel class, by the PhotoVM property of the ViewModelLocator object, a new instance of the PhotoInformationFlyoutView class is created and passed into the PhotoViewModel class, along with the FlyoutService.

PhotoViewModel^ ViewModelLocator::PhotoVM::get()
{
    if (nullptr == m_photoInformationFlyoutView)
    {
        m_photoInformationFlyoutView = (Flyout^)ref new PhotoInformationFlyoutView();
    }
    return ref new PhotoViewModel(m_repository, m_flyoutService, m_photoInformationFlyoutView);
}

The PhotoInformationFlyoutView is displayed by tapping on a photo on the PhotoView page. The Image control in the PhotoView page uses the ImageTappedBehaviour attached behaviour to handle the Tapped event in the PhotoViewModel class.

<Image local:ImageTappedBehaviour.Command="{Binding ShowFlyoutCommand}"
       HorizontalAlignment="Center"
       Source="{Binding Photo}"
       VerticalAlignment="Center" />

The ShowFlyoutCommand is initialized in the PhotoViewModel constructor, to execute the ShowFlyout method.

void PhotoViewModel::ShowFlyout(Object^ parameter)
{
    (*m_flyoutService)->ShowFlyout(m_file, m_flyout);
}

The ShowFlyout method uses the FlyoutService instance, which is injected as a constructor parameter. to call the FlyoutService.ShowFlyout method to display the PhotoInformationFlyoutView. In the call to the ShowFlyout method the m_file parameter specifies the FileInformation object that is passed into the PhotoInformationFlyoutViewModel class, with the m_flyout parameter specifying the Flyout to be displayed (in this case the PhotoInformationFlyoutView). The m_flyout instance is injected into the PhotoViewModel class as a constructor parameter.

The end result of all this is that the PhotoInformationFlyoutView is displayed when a photo on the PhotoView page is tapped.

Summary

In this blog post I’ve extended the sample PhotoViewer C++/CX app so that a Flyout can be displayed by tapping on the photo on the PhotoView page. The PhotoInformationFlyoutView displays basic photo information including filename, file type, resolution, and path. A series of value converters are used to convert the photo information into a user friendly format for display.

The PhotoInformationViewFlyout derives from the Flyout class, with the PhotoInformationFlyoutViewModel class implementing the IFlyout interface. The ShowFlyout method of the FlyoutService class is used to programatically display the PhotoInformationFlyoutView.

The sample app can be downloaded here.

No comments:

Post a comment