Part 4: Enable file pickers (Windows Runtime apps using C++)
[ This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation ]
This final part of the C++ tutorial series shows how to add a FileOpenPicker so that a user can get a file, bind UI controls to data, and implement a MostRecentlyUsedList so that the user can access a previously opened file.
In the second tutorial in this series, Manage app lifecycle and state, you learned about app data and session data, and how to save this data in ApplicationData storage. By default, your app can access certain file locations—for example, app data locations, the app installation directory, and items it creates in the \Downloads\ folder.
In contrast, user data—such as pictures, videos, and document files—is independent of your app and is typically stored in the user’s library folders or other locations in the file system. To access these locations, your app must declare its ability to access the data programmatically, or include a file picker so that the user can open the files manually. Here, you use a file picker to enable access to the user’s Pictures library, and so you don't have to declare any app capabilities. For more info about capabilities, see App capability declarations.
In this tutorial, you add functionality to the photo page layout you created in Part 3: Add navigation and views. First, you handle the "Get photo" button-click event to open a file picker so that the user can select an image from his or her Pictures library. Then you bind UI controls to file properties to show the picture info. Finally, we revisit what you learned in the second tutorial about how to save app state. You use a MostRecentlyUsedList to maintain access to the image selected by the user.
Before you start...
- This is the fourth tutorial in a series. Before you start, read Hello World (C++), Manage app lifecycle and state (C++), and Navigation, layout and views (C++).
- You can view the complete code for this tutorial in the Hello World (C++) sample on Code Gallery.
Step 1: Use a file picker to get an image file (Windows 8.1)
Through the file picker, your app can access files and folders all over the user's system, based on user selection. When the file picker is called, the user can browse for files (or folders) to access and save. After the user picks files or folders, your app receives those selections as StorageFile and StorageFolder objects. Your app can then use those objects to operate on the selected files and folders.
The first thing you have to do is handle the GetPhotoButton_Click
event to get a picture to show.
We start here with the code from Navigation, layout and views (C++).
To add a file picker
In Solution Explorer, open PhotoPage.xaml in the Windows project.
Select the "Get photo" Button.
In the Properties window (F4), choose the Events button ().
Find the Click event in the events list. In the text box for the event, type "GetPhotoButton_Click" as the name of the method that handles the Click event.
Press Enter. The event handler method is created in PhotoPage.xaml.cpp and opened in the code editor so that you can add the code that's executed when the event occurs.
Add the following code to the event handler method. It opens a file picker so that the user can select a picture from the Pictures library. When the user picks a file, it's set as the source of the image and the data context of the page.
auto openPicker = ref new Windows::Storage::Pickers::FileOpenPicker(); openPicker->SuggestedStartLocation = Windows::Storage::Pickers::PickerLocationId::PicturesLibrary; openPicker->ViewMode = Windows::Storage::Pickers::PickerViewMode::Thumbnail; // Filter to include a sample subset of file types. openPicker->FileTypeFilter->Clear(); openPicker->FileTypeFilter->Append(".bmp"); openPicker->FileTypeFilter->Append(".png"); openPicker->FileTypeFilter->Append(".jpeg"); openPicker->FileTypeFilter->Append(".jpg"); // All this work will be done asynchronously on a background thread: // Wrap the async call inside a concurrency::task object create_task(openPicker->PickSingleFileAsync()) // Accept the unwrapped return value of previous call as input param .then([this](Windows::Storage::StorageFile^ file) { // file is null if user cancels the file picker. if (file == nullptr) { // Stop work and clean up. cancel_current_task(); } // For data binding text blocks to file properties this->DataContext = file; // Add picked file to MostRecentlyUsedList. mruToken = Windows::Storage::AccessCache::StorageApplicationPermissions::MostRecentlyUsedList->Add(file); // Return the IRandomAccessStream^ object return file->OpenAsync(Windows::Storage::FileAccessMode::Read); }).then([this](Windows::Storage::Streams::IRandomAccessStream^ fileStream) { // Set the stream as source of the bitmap Windows::UI::Xaml::Media::Imaging::BitmapImage^ bitmapImage = ref new Windows::UI::Xaml::Media::Imaging::BitmapImage(); bitmapImage->SetSource(fileStream); // Set the bitmap as source of the Image control displayImage->Source = bitmapImage; });
At the top of PhotoPage.xaml.cpp, add this using statement:
using namespace concurrency;
Press F5 to build and run the app. Navigate to the photo page and choose the "Get photo" button to run the FileOpenPicker. In the photo picker, select a photo and then choose the Open button. The photo appears on the photo page, but the photo info text is not updated. You'll enable that in the next step.
Here's what the app looks like with a picture selected:
Before moving on to the Windows Phone project, let’s take a closer look at the previous code. After we create and initialize a FileOpenPicker object, we call its asynchronous method FileOpenPicker::PickSingleFileAsync. The method will execute on a background thread, and we can't proceed until that asynchronous method completes. But we can't just wait for it because Store apps are not allowed to block the UI thread. To solve this problem, we use the concurrency::create_task method. This method is defined in ppltasks.h, which is included in the pch.h header file in all C++ projects. The create_task method wraps Windows Runtime asynchronous methods in a concurrency::task object, which enables you to call the method, and then wait for it on the background thread and use task::then to specify one or more continuations that will invoke a user-provided function when the async call completes. Typically, as in this case, the function is provided as a lambda expression. If the original asynchronous operation returns a value—in this case a StorageFile^ object—that result is wrapped in a new task object and passed to the continuation as an input parameter. You can chain any number of continuations in this way. In a nutshell, using create_task with then is the way to consume Windows Runtime asynchronous APIs in C++.
In our specific example, create_task invokes the PickSingleFileAsync method, which opens the file-picker dialog box and waits for the user to select a file. When the user chooses the OK button, the method returns a StorageFile object that represents the picked file. The continuation is waiting on the background thread to receive that file as input, and when that arrives, it processes the image stream to create a BitmapImage, and then sets the BitmapImage as the Source of the Image control in the UI. It also sets the file as the DataContext of the page—later in this tutorial, we bind the file name and path text block elements to properties on the StorageFile. All this happens on a background thread.
Step 1A: Use a file picker to get an image file (Windows Phone 8.1)
There is a big difference between the way you use a file picker in Windows Phone 8.1 versus Windows 8.1. On Windows Phone, opening a file picker deactivates the app, and you have to handle the OnActivated event that's fired when the picker closes. That event tells the app that it's waking up from a file-picker operation, and then passes a handle to the chosen file. In this walkthrough, we'll do the minimum required to get our app working. For a more complete example, see the File Picker Sample.
Add the ContinuationManager class files (Windows Phone 8.1)
In the Windows Phone project, press Ctrl+Shift+A to add a new item. Choose .h file, name it ContinuationManager.h and paste in this code:
#pragma once #include <windows.h> namespace WAA = Windows::ApplicationModel::Activation; namespace HelloWorld { /// <summary> /// ContinuationManager is used to detect if the most recent activation was due /// to a continuation such as the FileOpenPicker or WebAuthenticationBroker /// </summary> ref class ContinuationManager sealed { private: WAA::IContinuationActivatedEventArgs^ args; bool handled; Platform::Guid id; public: void Continue(WAA::IContinuationActivatedEventArgs^ args); void Continue(WAA::IContinuationActivatedEventArgs^ args, Windows::UI::Xaml::Controls::Frame^ rootFrame); void MarkAsStale(); WAA::IContinuationActivatedEventArgs^ GetContinuationArgs(bool includeStaleArgs); property Platform::Guid Id { Platform::Guid get() { return id; } } property WAA::IContinuationActivatedEventArgs^ ContinuationArgs { WAA::IContinuationActivatedEventArgs^ get() { if (handled) return nullptr; MarkAsStale(); return args; } } }; /// <summary> /// Implement this interface if your page invokes the file open picker /// API. /// </summary> public interface class IFileOpenPickerContinuable { /// <summary> /// This method is invoked when the file open picker returns picked /// files /// </summary> /// <param name="args">Activated event args object that contains returned files from file open picker</param> void ContinueFileOpenPicker(WAA::FileOpenPickerContinuationEventArgs^ args); }; }
Open the Add New Item dialog box again and choose .cpp file. Name it ContinuationManager.cpp, and then paste this code into it:
#include "pch.h" #include "ContinuationManager.h" using namespace HelloWorld; using namespace Platform; using namespace Windows::ApplicationModel::Activation; using namespace Windows::UI::Xaml::Controls; using namespace Windows::UI::Xaml; /// <summary> /// Sets the ContinuationArgs for this instance. Using default Frame of current Window /// Should be called by the main activation handling code in App.xaml.cs /// </summary> /// <param name="args">The activation args</param> void ContinuationManager::Continue(IContinuationActivatedEventArgs^ args) { Continue(args, dynamic_cast<Frame^>(Window::Current->Content)); } /// <summary> /// Sets the ContinuationArgs for this instance. Should be called by the main activation /// handling code in App.xaml.cs /// </summary> /// <param name="args">The activation args</param> /// <param name="rootFrame">The frame control that contains the current page</param> void ContinuationManager::Continue(IContinuationActivatedEventArgs^ args, Frame^ rootFrame) { this->args = args; this->handled = false; GUID result; if (SUCCEEDED(CoCreateGuid(&result))) { this->id = Platform::Guid(result); } if (rootFrame == nullptr) return; switch (args->Kind) { case ActivationKind::PickFileContinuation: { auto fileOpenPickerPage = dynamic_cast<IFileOpenPickerContinuable^>(rootFrame->Content); if (fileOpenPickerPage != nullptr) { fileOpenPickerPage->ContinueFileOpenPicker(dynamic_cast<FileOpenPickerContinuationEventArgs^>(args)); } } break; // See FilePickerSample for these cases // case ActivationKind::PickSaveFileContinuation: // case ActivationKind::PickFolderContinuation: // case ActivationKind::WebAuthenticationBrokerContinuation: } } /// <summary> /// Marks the contination data as 'stale', meaning that it is probably no longer of /// any use. Called when the app is suspended (to ensure future activations don't appear /// to be for the same continuation) and whenever the continuation data is retrieved /// (so that it isn't retrieved on subsequent navigations) /// </summary> void ContinuationManager::MarkAsStale() { this->handled = true; } /// <summary> /// Retrieves the continuation args, if they have not already been retrieved, and /// prevents further retrieval via this property (to avoid accidentla double-usage) /// </summary> IContinuationActivatedEventArgs^ ContinuationManager::GetContinuationArgs(bool includeStaleArgs) { if (!includeStaleArgs && handled) return nullptr; else { MarkAsStale(); return args; } }
Handle the OnActivated event (Windows Phone 8.1)
Add this #include statement to App.xaml.h:
#if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP #include "ContinuationManager.h" #endif
Add the following public member function to the App class in App.xaml.h:
#if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP virtual void OnActivated(Windows::ApplicationModel::Activation::IActivatedEventArgs^ e) override; #endif
Add private members to the App class so that the complete private declarations now look like this:
private: #if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP Windows::UI::Xaml::Media::Animation::TransitionCollection^ _transitions; Windows::Foundation::EventRegistrationToken _firstNavigatedToken; ContinuationManager^ continuationManager; void RootFrame_FirstNavigated(Platform::Object^ sender, Windows::UI::Xaml::Navigation::NavigationEventArgs^ e); #endif void OnSuspending(Platform::Object^ sender, Windows::ApplicationModel::SuspendingEventArgs^ e); void LaunchOrContinue(Windows::ApplicationModel::Activation::IActivatedEventArgs^ e); void OpenMainPage(Windows::UI::Xaml::Controls::Frame^ root, Windows::ApplicationModel::Activation::IActivatedEventArgs^ e);
Notice that we added a
ContinuationManager
field, aLaunchOrContinue
member function, and anOpenMainPage
member function.Add the implementations of the new members to App.xaml.cpp:
void App::LaunchOrContinue(Windows::ApplicationModel::Activation::IActivatedEventArgs^ e) { #if _DEBUG // Show graphics profiling information while debugging. if (IsDebuggerPresent()) { // Display the current frame rate counters DebugSettings->EnableFrameRateCounter = true; } #endif auto rootFrame = dynamic_cast<Frame^>(Window::Current->Content); // Do not repeat app initialization when the Window already has content, // just ensure that the window is active if (rootFrame == nullptr) { // Create a Frame to act as the navigation context and associate it with // a SuspensionManager key rootFrame = ref new Frame(); HelloWorld::Common::SuspensionManager::RegisterFrame(rootFrame, "appFrame"); // Set the default language rootFrame->Language = Windows::Globalization::ApplicationLanguages::Languages->GetAt(0); if (e->PreviousExecutionState == ApplicationExecutionState::Terminated) { // TODO: Restore the saved session state only when appropriate, scheduling the // final launch steps after the restore is complete HelloWorld::Common::SuspensionManager::RestoreAsync(); } if (rootFrame->Content == nullptr) { // If the navigation stack isn't restored, then go to the first page. OpenMainPage(rootFrame, e); } } else { if (rootFrame->Content == nullptr) { // When the navigation stack isn't restored navigate to the first page, // configuring the new page by passing required information as a navigation // parameter OpenMainPage(rootFrame, e); } } #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP if (e->Kind == ActivationKind::PickFileContinuation) { auto continuationEventArgs = dynamic_cast<IContinuationActivatedEventArgs^>(e); continuationManager = ref new ContinuationManager(); if (continuationEventArgs != nullptr) { continuationManager->Continue(continuationEventArgs, rootFrame); } } #endif // Place the frame in the current Window Window::Current->Content = rootFrame; // Ensure the current window is active Window::Current->Activate(); } void App::OpenMainPage(Frame^ root, Windows::ApplicationModel::Activation::IActivatedEventArgs^ e) { // If we are launching, then configure the new page by passing required // information as a navigation parameter if (e->Kind == ActivationKind::Launch) { auto launchArgs = dynamic_cast<LaunchActivatedEventArgs^>(e); if (!root->Navigate(TypeName(MainPage::typeid), launchArgs->Arguments)) { throw ref new FailureException("Failed to create initial page"); } } else { root->Navigate(TypeName(MainPage::typeid)); } }
Replace the code in
OnLaunched
andOnActivated
with this call toLaunchOrContinue
:LaunchOrContinue(e);
The code that handles the Activated event and the Launched event is very similar. In both cases, the app has to check whether it has a Frame and whether there are pages in that frame—if so, it has to restore the state in those pages; if not, then it just navigates to the default page. Here we have created a LaunchOrContinue member function to handle both cases. If the app is continuing after the file picker operation, and we are compiling for Windows Phone 8.1, then we create a Continuation Manager class and use it to do the work of restoring state after the file picker returns. The OpenMainPage member function is just a helper to reduce code duplication.
Modify the PhotoPage class (Windows Phone 8.1)
To handle continuation from a file-picker activation, the PhotoPage class has to implement the IFileOpenPickerContinuable interface that we defined in ContinuationManager.h:
First add the #include statement and a using statement for the file picker namespace:
#include "ContinuationManager.h"using namespace Windows::Storage::Pickers;
Then modify the class declaration:
public ref class PhotoPage sealed : IFileOpenPickerContinuable
Add this public member function in Photopage.xaml.h:
virtual void ContinueFileOpenPicker(Windows::ApplicationModel::Activation::FileOpenPickerContinuationEventArgs^ args);
Add the implementation in Photopage.xaml.cpp:
/// <summary> /// Handle the returned files from file picker /// This method is triggered by ContinuationManager based on ActivationKind /// </summary> /// <param name="args">File open picker continuation activation argment. It cantains the list of files user selected with file open picker </param> void PhotoPage::ContinueFileOpenPicker(FileOpenPickerContinuationEventArgs^ args) { Windows::Storage::StorageFile^ file; if (args->Files->Size > 0) { // Open a stream for the selected file. file = args->Files->GetAt(0); } // asynchronously return the IRandomAccessStream^ object create_task(file->OpenAsync(Windows::Storage::FileAccessMode::Read)) .then([this, file](Windows::Storage::Streams::IRandomAccessStream^ fileStream) { // Set the image source to the selected bitmap. Windows::UI::Xaml::Media::Imaging::BitmapImage^ bitmapImage = ref new Windows::UI::Xaml::Media::Imaging::BitmapImage(); bitmapImage->SetSource(fileStream); displayImage->Source = bitmapImage; this->DataContext = file; }); }
Set the Windows Phone project as the startup project and press F5. You can use the emulator to take a fake photo and load it into your app.
Step 2: Bind UI controls to file data
At this point, the image is shown in the UI, but the image-file properties are not yet shown in the text blocks you added.
You could set the Text property of each TextBlock in code, just as you set the Image.Source property. But to display data, you typically use data binding to connect a data source to the UI, either by setting the Binding.Source property or by setting the DataContext on the UI element. When you establish a binding and the data source changes, the UI elements that are bound to the data source reflect the changes automatically.
Note By default, bindings are one-way; this means that updates to the data source are reflected in the UI. You can specify a two-way binding so that changes the user makes in a UI element can be reflected in the data source. For example, if the user edits the value in a TextBox, the binding engine automatically updates the underlying data source to reflect that change.
In this tutorial, you use the DataContext property to set the default binding for an entire UI element and all of its child elements. You set the StorageFile selected in the FileOpenPicker as the DataContext of the photo page. In the preceding code, remember that after the image is selected, you use this line to set the DataContext:
this->DataContext = file;
You show the file name by binding the Text property of the title TextBlock to the StorageFile.DisplayName property of the selected file.
Because you don’t specify a binding Source, the data-binding engine looks for the DisplayName property on the DataContext. If the DataContext is null or doesn't have a DisplayName property, the binding fails silently and no text is shown in the TextBlock.
Here, you bind the TextBlock controls to StorageFile properties.
To bind controls to data
In Solution Explorer, open PhotoPage.xaml.
Select the TextBlock for the photo name that's under the "Get photo" button.
In the Properties window, choose the Properties button () to show the Properties view.
Under Common in the Properties window, choose the property marker for the Text property. The property menu opens.
Note The property marker is the small box symbol to the right of each property value. The Text property marker is black to indicate that it's set to a string value.
In the property menu, select Create Data Binding. The Create Data Binding dialog box opens.
In the dialog box, in the Binding type drop-down list, select Data context.
Enter "DisplayName" in the Path text box as shown here:
Note Notice that the message in the Create Data Binding dialog box says that the data context is not set. That's okay because you set the data context in code when you run the app and get a picture.
Choose the OK button.
Here's the Extensible Application Markup Language (XAML) for the TextBlock after you add the binding:
<TextBlock Grid.Row="1" TextWrapping="Wrap" Text="{Binding DisplayName}" Style="{StaticResource SubheaderTextBlockStyle}"/>
Select the TextBlock after the "File name:" TextBlock.
- Repeat steps 3-5 to create a data binding for this TextBlock.
- Enter "Name" in the Path text box and choose OK.
Select the TextBlock after the "Path:" TextBlock.
- Repeat steps 3-5 to create a data binding for this TextBlock.
- Enter "Path" in the Path text box and choose OK.
Here's the XAML for the photo info StackPanel after you add the bindings:
<StackPanel Margin="20,0,0,0"> <TextBlock TextWrapping="Wrap" Text="File name:" Style="{StaticResource CaptionTextBlockStyle}"/> <TextBlock TextWrapping="Wrap" Text="{Binding Name}" Style="{StaticResource BodyTextBlockStyle}" Margin="10,0,0,30"/> <TextBlock TextWrapping="Wrap" Text="Path:" Style="{StaticResource CaptionTextBlockStyle}"/> <TextBlock TextWrapping="Wrap" Text="{Binding Path}" Style="{StaticResource BodyTextBlockStyle}" Margin="10,0,0,30"/> </StackPanel>
Press F5 to build and run the app. Navigate to the photo page. Choose the Get photo button to launch the FileOpenPicker. With bindings in place, file properties are now shown when you select a file.
Here's what the app looks like when a picture is selected and the text blocks are bound to data.
Step 3: Save and load state
In the second tutorial in this series, Manage app lifecycle and state, you learned how to save and restore the app state. Now that you’ve added a new page to the app, you must save and load the state of the new page, too. For the photo page, you only have to save and restore the currently displayed image file.
But you can’t just save the path of the file and then re-open it by using that path. When a user uses a FileOpenPicker to select a file, they implicitly give your app permission to use that file. But if you later try to retrieve the file by using just the path, permission is denied.
Instead, so that you can preserve access to the file for later use, the StorageApplicationPermissions class provides two lists where you can store the file and the permissions that were granted when the user opened it with the file picker.
- MostRecentlyUsedList—To store the last 25 files accessed.
- FutureAccessList—For general storage of up to 1000 files for future access.
Because you only need access to the last file the user picked, you can use it to restore the page state. For this, the MostRecentlyUsedList fits the requirements.
When a user picks a file, you add it to the MostRecentlyUsedList. When you add a file to this list, the MostRecentlyUsedList returns a token that you use to retrieve the file later. You save this token in the pageState
dictionary and use it to retrieve the current image file when you restore the page state.
To save state
In Solution Explorer, open PhotoPage.xaml.h. (You may have to expand the PhotoPage.xaml node to reveal the two code-behind files underneath it.)
In the
PhotoPage
class, add the following private variable. It declares a variable to hold the token that's returned by the MostRecentlyUsedList.Platform::String^ mruToken;
In PhotoPage.xaml.cpp, add following code to the
GetPhotoButton_Click
event handler as a continuation on the last inner then method (whose last statement isthis->DataContext = file
). It adds the selected file to the MostRecentlyUsedList and gets the token..then([this, file]() { // Add picked file to MostRecentlyUsedList. mruToken = Windows::Storage::AccessCache::StorageApplicationPermissions::MostRecentlyUsedList->Add(file); })
Here's the code together with the code that surrounds it:
// Open the file picker. create_task(openPicker->PickSingleFileAsync()).then( [this](Windows::Storage::StorageFile^ file) { // file is null if user cancels the file picker. if (file != nullptr) { // Open a stream for the selected file. create_task([file]() { return file->OpenAsync(Windows::Storage::FileAccessMode::Read); }).then([this, file](Windows::Storage::Streams::IRandomAccessStream^ fileStream) { // Set the image source to the selected bitmap. Windows::UI::Xaml::Media::Imaging::BitmapImage^ bitmapImage = ref new Windows::UI::Xaml::Media::Imaging::BitmapImage(); bitmapImage->SetSource(fileStream); displayImage->Source = bitmapImage; this->DataContext = file; }).then([this, file]() { // Add picked file to MostRecentlyUsedList. mruToken = Windows::Storage::AccessCache::StorageApplicationPermissions::MostRecentlyUsedList->Add(file); }); } });
In the
PhotoPage::SaveState
method, add the following code. It checks whether the token exists, and saves it in thePageState
dictionary if it does.if (mruToken != nullptr && !mruToken->IsEmpty()) { e->PageState->Insert("mruToken", mruToken); }
Here's the complete code for the
navigationHelper_SaveState
method:void PhotoPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e){ (void) sender; // Unused parameter (void) e; // Unused parameter if (mruToken != nullptr && !mruToken->IsEmpty()) { e->PageState->Insert("mruToken", mruToken); } }
To load state
In PhotoPage.xaml.cpp, add the following code in the
PhotoPage::LoadState
method.You get the token from the
PageState
dictionary, and then use it to retrieve the file from the MostRecentlyUsedList and restore the UI state.if (e->PageState != nullptr && e->PageState->HasKey("mruToken")) { Object^ value = e->PageState->Lookup("mruToken"); if (value != nullptr) { mruToken = value->ToString(); // Open the file via the token that you stored when adding this file into the MRU list:: create_task(Windows::Storage::AccessCache::StorageApplicationPermissions::MostRecentlyUsedList->GetFileAsync(mruToken)) .then([this](Windows::Storage::StorageFile^ file) { if (file != nullptr) { // Open a stream for the selected file-> // Windows.Storage->Streams.IRandomAccessStream fileStream = create_task(file->OpenAsync(Windows::Storage::FileAccessMode::Read)) .then([this, file](Windows::Storage::Streams::IRandomAccessStream^ fileStream) { // Set the image source to a bitmap. auto bitmapImage = ref new Windows::UI::Xaml::Media::Imaging::BitmapImage(); bitmapImage->SetSource(fileStream); displayImage->Source = bitmapImage; // Set the data context for the page this->DataContext = file; }); } }); } }
Press F5 to build and run the app. Navigate to the photo page, choose the "Get photo" button to launch the FileOpenPicker, and pick a file. Now, when the app is suspended, terminated, and restored, the image is re-loaded.
Note Review Manage app lifecycle and state for instructions about how to suspend, terminate, and restore an app.
Summary
Congratulations! You're done with the fourth tutorial, which taught how to use file pickers and data binding in a Windows Store app.
See the code
Did you get stuck, or do you want to check your work? If so, see the Hello World (C++) sample on Code Gallery.