What You’ll Learn This Hour:
• Identifying the differences between a contract and an extension
• Using the File Open Picker contract
• Using the Print extension
• Making use of the Settings extension
• Implementing the Contact Picker extension
• Using the Search contract
• Understanding how the remaining contracts and extensions work
Windows 8 exposes extensions we can tie into. The API also has contracts that we can expose and consume to integrate with other apps. Using extensions can really help an app feel integrated into Windows and work well with other apps that exist today—and those that are yet to be created.
Hour 5, “Understanding Microsoft Design Style Principles,” discussed the principle of winning as one. Although it includes other items discussed during that hour, it also includes contracts and extensions. During this hour, we cover how we can win as one by using contracts and extensions.
Extensions are agreements between our app and Windows. Extensions enable us to extend standard Windows features. There are more extensions than contracts for us to tie into. Extensions really help an app to feel integrated into Windows, and it is important to take advantage of the extensions available.
Contracts are agreements between apps. The apps do not know about other apps that are installed or will be installed on the machine. Apps cannot assume that other apps are installed and cannot talk directly to other apps. Instead, apps expose and consume contracts. Two types of contracts exist: a source contract and a target contract. These sharing contracts have different sets of requirements. An app might want to support both contracts for the same item. Windows provides all the glue needed to make apps work together, so long as they expose these contracts.
We discussed the file open picker contract during Hour 14, “Using the App Bar and Pickers.” Here we implement the File Open Picker Contract in the FingerPainting app we started during Hour 15, “Handling Our App Life Cycle Events.” This code is not actually implementing the File Open Picker contract; it is simply consuming it. We discuss how an app can implement the contract later this hour.
Let’s open the FingerPainting app in Visual Studio. Inside artboard.js
, we can add the following code after the declaration of our variables:
WinJS.Namespace.define("STYWin.ArtBoard", {
canvas: canvas,
clearArtBoard: clearArtBoard,
setBackgroundImage: setBackgroundImage
});
We are exposing the canvas variables, as well as both the clearArtBoard
function and the not-yet-created setBackgroundImage
function. Let’s assign the exposed canvas property to the canvas variable when it is assigned in the ready
function:
canvas = document.getElementById("artboard");
STYWin.ArtBoard.canvas = canvas;
Before we create the setBackgroundImage
function, let’s add the file open picker to the project. We need to create a new JavaScript file inside the js
folder named filepicker.js
. Listing 16.1 shows the code we need to add to filepicker.js
.
"use strict";
(function () {
WinJS.Namespace.define("STYWin.FilePicker", {
selectFile: selectFile
});
function canShowPicker() {
var retVal = true;
var vm = Windows.UI.ViewManagement;
// Verify that we are currently not snapped,
// or that we can unsnap to open the picker
var currentState = vm.ApplicationView.value;
if (currentState === vm.ApplicationViewState.snapped &&
!Windows.UI.ViewManagement.ApplicationView.tryUnsnap()) {
// Fail silently if we can't unsnap
retVal = false;
}
return retVal;
}
function selectFile() {
if (STYWin.Utilities.asyncFlag) {
return;
}
if (!canShowPicker())
return;
var pickers = Windows.Storage.Pickers;
var fileOpenPicker = new pickers.FileOpenPicker();
fileOpenPicker.commitButtonText = "Set Background";
fileOpenPicker.suggestedStartLocation =
pickers.PickerLocationId.picturesLibrary;
fileOpenPicker.viewMode = pickers.PickerViewMode.thumbnail;
fileOpenPicker.fileTypeFilter.replaceAll([".jpg",".bmp",".gif",".png"]);
fileOpenPicker.pickSingleFileAsync().then(function (file) {
if (file) {
STYWin.ArtBoard.setBackgroundImage(file);
}
}, STYWin.Utilities.asyncError);
}
})();
The core of filepicker.js
is identical to what we created during Hour 14. However, we have added a check for an asyncFlag
, which we discuss shortly. We also added an error handler function, asyncError
, for pickSingleFileAsync
promise. In addition, we changed the call to displayFile
to STYWin
.
ArtBoard
.
setBackgroundImage
. The actual logic is what we can expect for the file open picker. We are specifying a single file and stating that we want to see the images in thumbnail view.
Now we can actually create the setBackgroundImage
function inside the artboard.js
file. Listing 16.2 shows the function.
function setBackgroundImage(image) {
if (image) {
var loadStream = null;
var imageProperties = null;
image.openAsync(Windows.Storage.FileAccessMode.read).then(
function (stream) {
// prevent future calls to this API until
// we are done with the first call
STYWin.Utilities.asyncFlag = true;
loadStream = stream;
return image.properties.getImagePropertiesAsync();
}
).done(
function (imageProperties) {
var url = URL.createObjectURL(image, { oneTimeOnly: true });
var imageObj = new Image(canvas.width, canvas.height);
imageObj.onload = function () {
STYWin.ArtBoard.clearArtBoard();
var width = imageProperties.width;
var height = imageProperties.height;
var top = 0, left = 0;
//determine if we need to resize according to width or height
var w = imageProperties.width / canvas.width;
var h = imageProperties.height / canvas.height;
if (w >= h) {
width = canvas.width;
var ratio = width / imageProperties.width;
height *= ratio;
//vertically center the image
top = Math.abs(canvas.height - height) / 2 | 0;
}
else {
height = canvas.height;
var ratio = height / imageProperties.height;
width *= ratio;
//horizontally center the image
left = Math.abs(canvas.width - width) / 2 | 0;
}
ctx.drawImage(imageObj, left, top, width, height);
};
imageObj.src = url;
// reset asyncFlag, can call loadAsync() once again
STYWin.Utilities.asyncFlag = false;
// input stream is IClosable interface; requires explicit close
loadStream.close();
},
function (e) {
STYWin.Messages.displayError("Load failed.");
// we still want to reset asyncFlag if an error occurs
STYWin.Utilities.asyncFlag = false;
// if the error occurred after the stream was opened,
//close the stream
if (loadStream) {
loadStream.close();
}
}
);
}
}
In the file picker example, we simply loaded the file from disk and set the blob URL to the src
attribute of the img
element. However, for the FingerPainting app, we need to load the image onto the canvas. To do this, we need to know the width and height of the image we are loading. After we open the image file passed in using openAsync
in read mode, we set an asyncFlag
to true
, store the stream, and get the image properties. In the promise, we call the createObjectURL
with the oneTimeOnly
property to create the blob URL.
We then set the onload
event handler to an anonymous function that clears the artboard, obtains the width and height of the image, and then scales the image as needed to get it to fit on the canvas. It also centers the image if it doesn’t fill up the entire canvas. Finally, the image load event handler draws the image on the canvas in the right position.
After declaring the load
event handler, the src
attribute is set to the blob URL. This kicks off the load
event. It then resets the asyncFlag
, which makes sure we don’t call open multiple times while work is being done. Finally, we close the stream. If an error occurs, we display an error and also reset the asyncFlag
so that we can try to load another file later, if we want. We also must make sure that the stream is closed if it was opened.
The code in Listing 16.2 worked with an asyncFlag
in STYWin
.
Utilities
and called a displayError
function in STYWin
.
Messages
. Let’s create those functions now. We can create two new JavaScript files in the js
folder, one named utilities.js
and the other named messages.js
.
Inside the newly created utilities.js
file, let’s add the following code:
"use strict";
(function () {
WinJS.Namespace.define("STYWin.Utilities", {
asyncFlag: asyncFlag,
asyncError: asyncError
});
var asyncFlag = false;
function asyncError(e) {
STYWin.Messages.displayError("Async error: " + e.toString());
}
})();
We are simply exposing the asyncFlag
we used, along with the asyncError
handler we passed to the file open picker. We now have a utilities object stubbed out in case we need to use it in the future. The message.js
file should contain the following code:
"use strict";
(function () {
WinJS.Namespace.define("STYWin.Messages", {
displayError: displayError,
displayStatus: displayStatus,
});
function showMessage(message, state) {
if (state && state.toLowerCase() === "error") {
var msg = new Windows.UI.Popups.MessageDialog(message, state);
msg.showAsync();
}
else {
console.log(message);
}
}
function displayStatus(message) {
showMessage(message);
}
function displayError(message) {
showMessage(message, "Error");
}
})();
Nothing new is being done here: We are simply displaying a message dialog if an error was passed in; otherwise, we just log the message to the console window (in Visual Studio). We need to add a reference to both of these JavaScript files in the default.html
file. We might end up needing to use the messages inside the Utilities
object, so let’s add them in the following order before the appbar.js
reference:
<script src="/js/messages.js"></script>
<script src="/js/utilities.js"></script>
Now we can hook up the file open picker code to the AppBar control. Inside appbar.js
, we can add an init
function and expose it in the namespace:
function init() {
document.getElementById("cmdLoad")
.addEventListener("click", STYWin.FilePicker.selectFile, false);
}
WinJS.Namespace.define("STYWin.AppBar", {
hide: hideAppBar,
init: init
});
In artboard.js
, we also have an init
function that adds the brush command click
event handlers to the app bar. We have split these out because we want our app to have a front page that lists the recent images we have created. When that page is displayed, the app bar should still allow a user to load an image (which is why the load icon is in the global section of the app bar), but the user shouldn’t be able to see the options to select a brush size. This init
function simply assigns the selectFile
function as the click
event handler for the Load button on the app bar. The app should call the init
function we just created. We can add this to default.js
. The code to be added appears in bold:
args.setPromise(WinJS.UI.processAll().then(function () {
if (nav.location) {
nav.history.current.initialPlaceholder = true;
return nav.navigate(nav.location, nav.state);
} else {
return nav.navigate(Application.navigator.home);
}
}).then(function () {
STYWin.AppBar.init();
}));
When the app is run, we can actually load an image by clicking the Load icon on the app bar. Figure 6.1 shows the final result of a loaded image, with some drawing on top of it. The base image was downloaded from http://visualstudiowallpapers.com/post/25145983097/embrace-the-case
.
Windows provides the print extension so that apps can tie into the Devices charm to print. Apps do not need a Print button because printing is done through charms. Adding printing capabilities to an app is really straightforward.
In the FingerPainting app, we can add a JavaScript file called print.js
to the js
folder. The contents of print.js
are as follows:
"use strict";
(function () {
WinJS.Namespace.define("STYWin.Print", {
registerForPrintContract: registerForPrintContract,
unregisterForPrintContract: unregisterForPrintContract
});
var printManager = Windows.Graphics.Printing.PrintManager.getForCurrentView();
function registerForPrintContract() {
printManager.addEventListener(
"printtaskrequested", onPrintTaskRequested);
}
function unregisterForPrintContract() {
printManager.removeEventListener(
"printtaskrequested", onPrintTaskRequested);
}
function onPrintTaskRequested(printEvent) {
printEvent.request.createPrintTask("FingerPainting", function (args) {
args.setSource(MSApp.getHtmlPrintDocumentSource(document));
});
}
})();
The namespace we created, STYWin.Print
, is exposing the register and unregister event handlers. We get the PrintManager
object associated with the current window with the call to getForCurrentView
. Inside the onPrintTaskRequested
event handler, we create a print task. This indicates that the app has content to print. It takes the title that will be displayed in the Devices charm as well as the handler to the code to actually print the document. The MSApp
.
getHtmlPrintDocumentSource
function is a helper function that returns the source content for the content to be printed.
In artboard.js
, we can add the following statement at the end of the ready
function:
STYWin.Print.registerForPrintContract();
We can also add the unregister
call to the unload
function of the artboard page:
STYWin.Print.unregisterForPrintContract();
We add this because we will be adding the main page that displays recent images created. That page won’t be printed, so we don’t want anything to display in the Devices charm. If we run the app, we can see that the app has notified Windows that it can print. Figure 16.2 shows the results.
The window
.
print
function could be used, but using the print contract is a best practice. The window
.
print
function simply prints exactly what is on the screen. Some apps might handle it fine, but in others, what is on the screen doesn’t translate well to print.
The print contract also exposes the options we can modify, such as the page range to print, the orientation, and the color mode. If we try to print currently, it defaults to portrait mode and chops off the image. Instead, let’s default the options to use landscape. Inside the print.js
file, in the onPrintTaskRequested
function, we store the return value from the createPrintTask
in a variable called printTask
. We can add the following code to the end of the onPrintTaskRequested
function:
printTask.options.orientation =
Windows.Graphics.Printing.PrintOrientation.landscape;
To demonstrate how to use the Settings extension, we to continue to update the FingerPainting app. We have a brushSize
stored in sessionState
if the app is suspended and resumed, but if the app is launched fresh, we set it straight to a brush size of 5. We want our users to be able to specify the default brush size when they launch the app fresh. Settings are modified by the Settings charm (Win+I) for each app. We add two commands for information and the general settings where the brush size will be set.
When running the app, we can select the General settings. When the app first loads, it gets the default brush size of 5, but this can be set to any other size. When we close the app and launch the app again, the new value appears in the General settings flyout. If this was a fresh launch, the brush size is used when we start painting. If we change the size and then suspend and terminate the app and launch it again, the brush size from the session state is used, which is what we want. Not much work was needed to actually tie into the Windows setting extension and to get it to work with the existing code.
Sometimes an app needs to have the user select from a contact list. During these times, the app can consume the Contact Picker Extension. However, apps can also be a contact source. This section looks at how to both retrieve contact information and provide a list of contacts for other apps.
We create two projects in this section. One is the contact selector. The other is the app that provides the contacts. (This was already created during Hour 13, “Working with Fullscreen, Filled, Snapped, and Portrait Views.”)
Let’s create a new Blank App project called ContactPickerExample. We can open the default.html
and replace the body
content with the following markup:
<button id="btnGetContact">Get Contacts</button>
<ul id="output"></ul>
We can add the WinJS.Utilities.ready
function at the end of default.js
:
WinJS.Utilities.ready(function () {
});
Inside the ready
function, we can add the following code:
var btnGetContact = document.getElementById("btnGetContact");
btnGetContact.addEventListener("click", getContact, false);
function getContact() {
// Verify that we are unsnapped or can unsnap to open the picker
var viewState = Windows.UI.ViewManagement.ApplicationView.value;
if (viewState === Windows.UI.ViewManagement.ApplicationViewState.snapped &&
!Windows.UI.ViewManagement.ApplicationView.tryUnsnap()) {
// Fail silently if we can't unsnap
return;
};
// Create the picker
var picker = new Windows.ApplicationModel.Contacts.ContactPicker();
picker.commitButtonText = "Select";
// Open the picker for the user to select contacts
picker.pickMultipleContactsAsync().done(function (contacts) {
if (contacts.length > 0) {
var ouput = document.getElementById("output");
var li;
contacts.forEach(function (contact) {
li = document.createElement("li");
li.innerText = contact.name;
output.appendChild(li);
});
} else {
// The picker was dismissed without selecting any contacts
console.log("No contacts were selected");
}
});
}
After hooking up the event handler for the button and making sure we aren’t in snapped mode, we instantiate a new ContactPicker
object. This is similar to how we worked with the File Open Picker. We set the text of the Commit button to Select
. Then we call pickMultipleContactsAsync
. A pickSingleContactAsync
function also is available. When we get the list of contacts, we loop through and add them to a list to be displayed onscreen. If no contacts are returned, we print a message to the console in Visual Studio. We can run the app and see that we can select contacts from the People app that comes with Windows.
Now we create an app that provides contacts. To get started, we first copy the ListViewExample project from Hour 13 (in the ListViewWithSemanticZoomExample
folder).
After we open the copy, we need to first modify the application package manifest file. We need to declare that the app is going to be a Contact Picker. We do this by going to the Declarations tab and selecting Contact Picker from the Available Declarations drop-down listbox. We can click the Add button to place the declaration in the Supported Declarations list. We can save and close the manifest file.
Next, inside default.js
, we need to modify the activation condition. Because this is a Contact Picker, it can be launched that way in addition to being launched normally. We can modify the args
.
detail
.
kind
condition in the onactivated
event handler to this:
if (args.detail.kind === activation.ActivationKind.launch ||
args.detail.kind === activation.ActivationKind.contactPicker) {
Then inside the condition we can add the following code:
if (args.detail.kind === activation.ActivationKind.contactPicker) {
STYWin.Utility.contactPickerUI = args.detail.contactPickerUI;
}
If we launched from a Contact Picker, we need to keep track of the contactPickerUI
passed in. Inside the ready
function, we need to declare the variable:
var contactPickerUI;
We also need to expose it in our namespace:
WinJS.Namespace.define("STYWin.Utility", {
contactPickerUI: contactPickerUI,
formatCurrency: WinJS.Binding.converter(function (num) {
Inside the initLayout
function, immediately after we declare the listView
variable, we add the following condition:
var listView = document.getElementById("simpleListView").winControl;
if (STYWin.Utility.contactPickerUI) {
listView.addEventListener("iteminvoked", addContact, false);
}
When we launched the app, we set the contactPickerUI
if it was launched as a Contact Picker. If not, the contactPickerUI
won’t be set. Assuming that we are in the correct mode, when an item is clicked, we call the addContact
function. Let’s add that function:
function addContact(evt) {
var contactToAdd = evt.detail.itemPromise._value.data;
var contact = new Windows.ApplicationModel.Contacts.Contact();
contact.name = contactToAdd.name;
var result = STYWin.Utility.contactPickerUI
.addContact(contactToAdd.name, contact);
}
When a contact is invoked, we grab the contact data. We instantiate a new Contact
object and set the name of the object to the name of the contact passed into this event handler. We then call the addContact
on the contactPickerUI
. We are using the name as the id
because our data didn’t have an id
and all users in our list have unique names. For a real app, we would provide much more information and have a unique identifier. We aren’t doing anything with the result from the contactPickerUI
.
addContact
function, but it does return a Windows
.
ApplicationModel
.
Contacts
.
Provider
.
AddContactResult
value. The possible values for the AddContactResult
are added
, alreadyAdded
, and unavailable
. We are ignoring any of the results for this little demo. The app is deployed by running it and then closing it, by choosing Deploy Solution from the Build menu, or by right-clicking the solution in the Solution Explorer.
After the ListViewExample (our contact provider) is deployed, we can run the ContactPickerExample app we created a moment ago. Now when we run it, we can pick the ListViewExample from the drop-down list at the top of the screen. Figure 16.6 shows the results.
The goal with the search contract is to make an app searchable from anywhere within Windows 8. The Search charm enables users to go to a single location to search for anything.
The search box is scoped to the main app on the screen. The main app provides the query suggestions on the screen. This provides the autocomplete for terms for which the app has search results. All the installed Windows Store apps that have implemented the search contract are listed and can be selected from the Search charm. So even if our app isn’t the main app open, the user can select it from the list to search the app. Besides the query suggestions, the app can provide result suggestions when it is the main app on the screen. These result suggestions show a direct match or a strong match that takes the user directly to some content. It must include a thumbnail and title.
Let’s update the RSSReader app we made during Hour 12, to make it implement the search extension. When we have the project copied over and opened, we can create a new folder called search
under the pages
folder. We can then right-click the newly created search
folder and add a new item. In the Visual Studio Add New Item dialog box, in Figure 16.7, we select Search Target Contract. We leave the name to the default searchResults.html
.
The template provides a lot of functionality for us automatically. It handles displaying search results and filtering the results. However, it does require modification to work with the real data in a way that makes sense for apps. Still, it is a great starting point and should be used. When we added the contract template, it automatically modified the app manifest file to declare that we are using Search.
Let’s modify the searchsearchResults.js
file. To begin, we have only a single group (the single Windows Blog feed), so we can remove the two filter push calls in the _generateFilters
function shown here:
this.filters.push({ results: null, text: "Group 1", predicate: function (item) ...
this.filters.push({ results: null, text: "Group 2+", predicate: function (item) ...
Next, we can modify what data we are actually searching. For the RSSReader app, we removed the subtitle and changed the description to content. So we can replace the first return
in the _searchData
function with the following return
:
return (item.title.indexOf(queryText) >= 0 ||
item.content.indexOf(queryText) >= 0);
We simply removed the subtitle
check and changed description
to content
. Inside searchResults.html
, we also need to make similar changes in the data we are binding. In the itemtemplate
, we need to remove the img
element and the item-subtitle
h4
element. We need to change the description
property to shortContent
because we are showing just a few lines of text in the list of search results.
While we are here, we can change the CSS reference to use ui-light
instead of ui-dark
. We also need to reference the searchResults.js
file. We can add the following line above the reference to default.js
inside of default.html
:
<script src="/pages/search/searchResults.js"></script>
If we run the app now, we can bring up the Search charm and enter something like HTML
or App
or Windows
and get results from the RSS feed. We can’t click on any of the results because we didn’t implement it. If we open the searchResults.js
file, we can see the _itemInvoked
function. If we add the following code under the comment TODO: Navigate to the item that was invoked
, we can select the items:
var index = Data.items.indexOf(item.data, 0);
nav.navigate("/pages/split/split.html", {
group: item.data.group, selectedIndex: index
});
The Split App project, which is what we started with when we created this RSSReader app, enables passing in an index so that the correct item is displayed. We are simply passing in the item group (which is always the same) and the index of the item the person selected. We determine the index by searching the data items with the item the user selected.
To finish this example, we just need to add one more piece back in the split.js
file. If we select an item that isn’t on the first page, the list view doesn’t scroll down to the selected index. The index is selected correctly in the previous code, but it isn’t visible to the user. To fix that, we need to add the following code in the ready
function after the // Set up the ListView
comment and before we call this
.
updateVisibility
:
if (this._itemSelectionIndex >= 0) {
msSetImmediate(function () {
listView.ensureVisible(options.selectedIndex);
});
}
We are checking to see if an index was passed in the options. If so, we make sure the index is visible. We wrap it in the msSetImmediate
function to have the function called when the current or pending task is completed. We want it called as soon as possible so that the index is actually set before the list view is rendered. Figure 16.8 illustrates searching the RSSReader app.
The code has a bug, though. As long as our app is running, we can do a search just fine. But if we close our app, bring up the Search charm, type in the search, and select our RSSReader app from the list, the app launches and then terminates. So the question becomes, “How exactly do we debug this problem?” Fortunately, Visual Studio provides a mechanism to do just that. Under the debug menu, we can select RSSReader Properties. On the Debugging configuration, we can see that Launch Application is set to Yes. We want to change that to No, as in Figure 16.9.
This allows to “run” the app in Visual Studio and then launch the app outside of Visual Studio. Visual Studio thus waits for the app to be executed and then attaches itself to the correct process so that it can be debugged. This is very handy, indeed.
With Visual Studio waiting to attach to the process, when we do the search in the Windows Search charm and then select the RSSReader app from the list, the code breaks in the _searchData
function in searchResults.js
. It tries to call createFiltered
on Data
.
items
, but Data
.
items
doesn’t have that function. This is because the RSS data hasn’t been loaded yet. We need to make sure that we have data to search that can be filtered.
To fix this, we need to modify the _handleQuery
function, which calls the _searchData
function. The _handleQuery
function assumes that data is immediately present. Because we are loading data from the web, we need it to wait on the data and display a progress bar if no data is loaded. We need to call the same logic twice (if the data is already loaded and if the data isn’t already in memory), so we want to create an additional function that will be called by _handleQuery
. Let’s replace the _handleQuery
function with the following two functions:
_populateResults: function (inst, element) {
document.querySelector('.fetchingresults').style.display = "block";
document.querySelector('.resultsmessage').style.display = "none";
document.querySelector('.filterarea').style.display = "none";
var originalResults = inst._searchData(inst._lastSearch);
if (originalResults.length === 0) {
document.querySelector('.resultsmessage').style.display = "block";
} else {
document.querySelector('.filterarea').style.display = "block";
}
document.querySelector('.fetchingresults').style.display = "none";
inst._populateFilterBar(element, originalResults);
inst._applyFilter(inst._filters[0], originalResults);
},
// This function executes each step required to perform a search.
_handleQuery: function (element, args) {
//var originalResults;
this._lastSearch = args.queryText;
WinJS.Namespace.define("searchResults",
{
markText: WinJS.Binding.converter(this._markText.bind(this))
});
this._initializeLayout(element.querySelector(".resultslist")
.winControl, Windows.UI.ViewManagement.ApplicationView.value);
this._generateFilters();
if (Data.items.length === undefined) {
var self = this;
Data.refreshFeeds().then(function () {
self._populateResults(self, element);
});
} else {
this._populateResults(this, element);
}
},
In the _handleQuery
function, we are checking to see if the items are actually present. If they aren’t, then length
will be undefined
instead of having a value. If there is data, then the original code is called and all is well. If there is no data, then we make the asynchronous call to our refreshFeeds
function we created in an earlier hour. In the promise we call the function we created named _populateResults
. Since this is inside a promise we need to store the this
reference and pass it in. If the data is already present, we call the same _populateResults
function passing in this
. The first part of the _handleQuery
function remains unaltered with the template code that handles filtering the data and highlighting the searched values.
The _populateResults
function we created is taking the code that was being called inside of _handleQuery
and does the same logic. The only difference is that we have added a .fetchingresults
class that we are tracking. When the function is called, we display the fetchingresults
div, which just contains a progress bar that we will see shortly. Then we hide both the actual list of items (.filterarea'
) and the no results found message (.resultsmessage
). So when the data is already present, the progress bar may not even be displayed and either the nothing found message or the actual list of items found will be displayed. On the other hand, if this is being launched from the charms menu, then the progress bar will be displayed and then it will hide and either the nothing found message or the actual list of items found will be displayed.
The markup we need to add for the progress bar to the searchResults.html is as follows:
<div class="fetchingresults">
<label class="win-type-xx-large">
<progress class="win-ring win-large"></progress>
Fetching ...
</label>
</div>
The previous markup can be placed at the very beginning of the Main content
section
. The following CSS rule needs to be added to the searchResults.css file to position the progress bar in the same place as the resultsmessage
is positioned:
.searchResults section[role=main] .fetchingresults {
margin-left: 120px;
margin-top: 193px;
}
We are simply setting the rule to the exact same values as the following rule:
.searchResults section[role=main] .resultsmessage
Finally, we need to add the following initialization states of the resultsmessage
and filterarea
to not be displayed when the page is first loaded. The code needs to be added to the very beginning of the ready
function:
document.querySelector('.resultsmessage').style.display = "none";
document.querySelector('.filterarea').style.display = "none";
Now, when we run the code we see the results we expect. We can change the debugging mode back so Visual Studio will actually launch the code.
Unfortunately, we don’t have the space to walkthrough setting up the query suggestions or the result suggestions. More information can be found in the “Further Exploration” section at the end of the hour.
Sharing allows users to share content from one app to another app. If our app is going to share with another app, our app needs to register with the DataTransfer Manager. Assuming a user shares something from our app through the Share charm, our app will receive an event and the app will need to populate the DataPackage. Once that is done we return the data to the Share Broker. The Share Broker will display a filtered list of target apps and quick links the user can select to share the data. The target app selected will be activated by the share broker. The target app will process the DataPackage contents the way it likes and then tell the Share Broker it is done. The target UI is then removed and the user is back to the main app.
DataPackages can contain plain text, formatted text, URI information, HTML, images, files and even custom data formats (assuming other apps know how to use it). We are out of room to show how this works, but we will see an example during Hour 20, “Making the FingerPainting App.” We will make the FingerPainting app be both a target and a source for an Image DataPackage.
There are more contracts than what we have seen so far. We are going to spend just a moment to list the different contracts available and explain how they can be used. The “Further Exploration” section at the end of this hour will provide links for more information on these contracts and extensions.
The Protocol Activation Extension will allow our app to be activated when a particular protocol is used, assuming a user chooses our app as the default handler for the protocol scheme. In order to handle the protocol activation extension, we need to add the extension point in the application package manifest file. Under the Declarations tab, we can select Protocol from the Available Declarations drop-down list. Finally, we can hit Add to add it to the Supported Declarations list. If we support a protocol, it is important that we provide the user with the functionality she expects. For example, if our app use the mailto: protocol, then it would be expected to actually open up an email and populate the mail information appropriately.
Once our app has declared that it will utilize a protocol, then any app or website can use the protocol. This means that any site can invoke a Windows Store app that is installed on the machine and has registered for the extension. Of course, the caller could have malicious intent, so it is important to not perform permanent actions based on the parameters. For example, the app should never update the user’s account information when the app is activated from this protocol.
The Account Picture Provider extension allows apps to get and set the user’s account pictures. This can be invoked programmatically, but it is mainly used when the user goes to set the account picture through the operating system. Any apps that have declared this contract are listed as an app that can change the account picture. The account picture provider extension actually uses the protocol activation extension. The following code checks whether the activation is a protocol and sees if it is specifically the ms-accountpictureprovider
protocol:
if (args.detail.kind ===
Windows.ApplicationModel.Activation.ActivationKind.protocol) {
// Check if the protocol matches the "ms-accountpictureprovider" scheme
if (args.detail.uri.schemeName === "ms-accountpictureprovider") {
Similar to the protocol activation extension, we can create an app that is launched based off a particular file type. So if a file with a particular extension was launched, Windows would activate the app associated to the file type and pass the filename to the app in the launch event. There is no need to use this extension to just use files. We have seen how we can read and write files to local storage without needing to declare this extension. This is only to handle a particular file type when the user launches the file type. As with the protocol activation extension, if our app registered for a file extension, it is important that the app actually act on the file in a way that the user expects. For example, if we registered for a .png
file extension, the app would be expected to immediately display the image file to the user. The declaration needed in the package manifest is the File Type Associations declaration. We could use the following condition to check whether the app was activated by this extension:
if (args.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.file){
The camera settings extension is made for the hardware vendors. If a hardware vendor provides extra functionality for their camera, they can create an app that would let the user utilize that camera app instead of the default one. This could be beneficial when the camera provided by the hardware vendor has more options than the default app exposes.
Similarly to how we created the Contact Picking contract, we can create a contract for a particular type of data. Other apps can then bring up our picker screen so that the user can select the desired data. This could be beneficial if our app provided extra value to a common file type or had its own file format that other apps wanted to utilize. Perhaps the app organizes the data in a particular way that makes having its own picker beneficial. If there is no added value, a File Open Picker can be used instead.
An example of an app providing value is one that lists music files in a way that isn’t handled by default. For example, an app might display music by artist or album title, regardless of how the music files are actually organized in the file system. In this case, the app could register for the File Open Picker contract and provide the UI that would show up in the File Open Picker center portion of the screen (inside the letterbox).
If an app has an interesting save location, such as a cloud service file storage app, it could register for the File Save Picker so that users can save the files to the proprietary cloud storage. Of course, if the app acts as a save location by participating in the File Save Picker, it should also participate in the File Open Picker so that the user can easily retrieve the files to be used in other apps.
Besides participating in the File Open Picker and File Save Picker contracts, apps can provide real-time updates for files by participating in the Cached File Updater contract. Assuming that a cloud storage app was being created, it would be beneficial for the files to be local as well and to change locally to automatically update the cloud version. Likewise, if the cloud version was updated, the app would bring down the updated version. If an app provides real-time updates for files using the Cached File Updater contract, it needs to participate in the File Save Picker and File Open Picker contracts.
If the app we are creating is a game, we can register with Windows as a game. This requires creating a Game Definition File (GDF) and including it as a binary resource in our app. Then we must declare the resource in the package manifest file. We will see how to do this during Hour 21, “Making the Simon Game.” Using this extension allows Windows to limit access to the game when family safety features have been enabled on the system.
The Play To contract allows apps to enable users to easily stream multimedia from the computer to other devices on the network. If the application is a picture viewer app, a music player, or a video player, we can declare the Play To contract in the app package manifest file to enable the user to select the Devices charm and have the media play to the selected device.
The AutoPlay extension allows our app to be an option the user can select when connecting a nonvolume device to a computer. This includes devices such as a camera, a USB thumb drive, a DVD player, SD cards, and media players. When users want to share files between two machines by using Proximity (tapping the devices), AutoPlay can also be used as an option to share the files.
Background tasks allow apps to run in the background for small work items that require no user interaction. They are allowed to run even when the app is suspended. They are associated to the lock screen and should run only because they provide relevant information to the user. Hour 17, “Using Live Tiles, Secondary Tiles, Notifications, and Toasts,” covers notifications in more detail.
The last extension is the SSL/Certificates extension, which enables us to install a digital certificate with an app. Digital certificates authenticate a user over SSL. They facilitate authenticating a web server to a user and authenticating a user to a web server. Not many apps require this sort of extension, but it is extremely beneficial for the apps that do have this requirement.
Many contracts and extension were just described, but we didn’t actually dig into how they need to be implemented. This section lists many links to Microsoft documentation that provides more information on each contract and extension:
How to print alternate content that is not displayed: http://msdn.microsoft.com/en-us/library/windows/apps/hh761455
Tutorial on how to add custom settings to the print window: http://msdn.microsoft.com/en-us/library/windows/apps/hh761450
Guidelines for developing print-capable Windows Store apps: http://msdn.microsoft.com/en-us/library/windows/apps/hh868178
More information on search contracts: http://msdn.microsoft.com/en-us/library/windows/apps/br225058
How to create a Protocol Activation Extension: http://msdn.microsoft.com/en-us/library/windows/apps/hh452686
Guidelines and checklist for file types and protocols: http://msdn.microsoft.com/en-us/library/windows/apps/hh700321
How to handle file activation: http://msdn.microsoft.com/en-us/library/windows/apps/hh452684
Streaming media to devices using Play To: http://msdn.microsoft.com/en-us/library/windows/apps/hh465176
Autolaunching with AutoPlay: http://msdn.microsoft.com/en-us/library/windows/apps/hh452731
Quickstart: How to register an app for AutoPlay: http://msdn.microsoft.com/en-us/library/windows/apps/hh452728
Introduction to background tasks: www.microsoft.com/download/details.aspx?id=27411
Ways to work with certificates: http://msdn.microsoft.com/en-us/library/windows/apps/hh465044
In addition to the documentation, Microsoft has the following Windows SDK samples that show how the code needs to be written to utilize the contracts and extensions:
• Association launching sample
• Contact Picker app sample
• Credential picker sample
• CryptoWinRT sample
• Lock screen personalization sample
• Lock screen apps sample
• Media Play To sample
• PlayToReceiver sample
• Print sample
• Search app contract sample
• Sharing content source app sample
• Sharing content target app sample
• SMS message send, receive, and SIM management sample
• SMS background task sample
• Windows 8 device app for camera sample
• Windows 8 banking app with strong authentication sample
We discussed a lot of contracts and extensions this hour—in fact, we discussed all of them! We looked in detail at the Print extension, Settings extension, Contract Picker extensions, and Search contract. We modified the FingerPainting app and added functionality with the Print extension and the Settings extension. We also consumed the File Open Picker contract to load in a background image.
We looked at the share contract and discuss it more detail during Hour 20. We also covered the rest of the contracts and extensions that we have access to, including the Protocol Activation extension, Account Picture Provider extension, File Activation extension, and Camera Settings extension. We also discussed the App to App Picking contract and how it works with the File Open Picker and File Save Picker and the Cached File Updater contracts. We wrapped up the hour by exploring the Game Explorer extension, the Play To contract, the Background Task extension, and the SSL/Certificates extension.
Q. Can my Windows Store app communicate with a desktop app?
A. No. Windows Store apps are contained in a sandboxed environment and can’t talk directly to desktop apps. Windows Store apps cannot assume that any app is installed on the desktop.
Q. If the primary purpose of my app is to provide search and to show extensive suggestions, how would I go about doing that with the search contract?
A. Although the search contract should be used, if the app’s purpose is really just for searching, the search box should be prominently displayed in the app to signify to the user the importance of the search function.
Q. When I run my app through Visual Studio, nothing happens—no splash screen or anything. What is wrong?
A. Chances are, the app was being debugged in regard to contracts. Check the project properties and, under the debugging section, confirm that Launch Application is set to Yes.
1. When having the user open a file through File Open Picker, do we need to add the File Open Picker declaration to our app package manifest file?
2. To search the Internet with our app, we need to implement the search contract. True or false?
3. Which extension do we use to associate an app with a particular file extension?
4. When implementing a Share contract, the app must support both the target and the source contracts. True or false?
1. No. When using the File Open Picker contract or the File Save Picker contract in our apps, we do not need to add the declaration to the app. The declarations need to be added only if we are creating our own File Picker that other apps can use to allow the user to select files.
2. False. The Internet capability, which is selected by default, is what we need to connect to the Internet. The Search contract is to allow our app to be searchable from the Windows Search charm.
3. To have our app activated when the user launches a particular file, we need to implement the File Activation extension.
4. False. An app can choose to implement both the source and target search contract, but if one is implemented, the other is not required to be. It might make sense for an app to only produce content or only consume content; for other apps, it may make sense to both consumer and produce content.
1. Currently, the FingerPainting app only clears the background to the color white. Add a setting that allows the user to specify what the background color should be when a blank artboard is created.
2. Complete the Microsoft Quickstart “Integrating with File Picker Contracts,” at http://msdn.microsoft.com/en-us/library/windows/apps/hh465192
.
3. Modify the ListViewExample to provide search suggestions.
18.221.136.142