Tips and tricks for C# metro developers–The Flyout control

This article starts a new series about small (but I hope useful) tips that can help you during your development phase.

Today I will talk about how you can create a flyout control.

A flyout control is a popup control you can make appear when you click on a button.

 

I used this control for WorldMonger (a game I currently working on):

The code to create this control is as follows:

 

public static Popup ShowPopup(FrameworkElement source, UserControl control)
{
    Popup flyout = new Popup();

    var windowBounds = Window.Current.Bounds;
    var rootVisual = Window.Current.Content;

    GeneralTransform gt = source.TransformToVisual(rootVisual);

    var absolutePosition = gt.TransformPoint(new Point(0, 0));

    control.Measure(new Size(Double.PositiveInfinity, double.PositiveInfinity));

    flyout.VerticalOffset = absolutePosition.Y  - control.Height - 10;
    flyout.HorizontalOffset = (absolutePosition.X + source.ActualWidth / 2) - control.Width / 2;
    flyout.IsLightDismissEnabled = true;

    flyout.Child = control;
    var transitions = new TransitionCollection();
    transitions.Add(new PopupThemeTransition() { FromHorizontalOffset = 0, FromVerticalOffset = 100 });
    flyout.ChildTransitions = transitions;
    flyout.IsOpen = true;

    return flyout;
}

This helper method just takes a source FrameworkElement (to detect where the flyout must pop up) and a UserControl control to display.

Please note the usage of the PopupThemeTransition to obtain and fluid and cool animation Sourire

How to cook a complete Windows 8 application with HTML5, CSS3 and JavaScript in a week – Day 5

The Windows 8 Release Preview (RP) is now out and you can download it here:
https://windows.microsoft.com/en-US/windows-8/release-preview

It is then obvious that I must port my little UrzaGatherer for the Release Preview.

I also take this opportunity to talk about some upgrades I added like the data binding support.

The complete version can be found here:
https://www.catuhe.com/MSDN/urza/day5.zip

The complete series can be found here:

 

Porting to Release Preview

To help you porting your own app, we (at Microsoft) released a great document:
https://go.microsoft.com/fwlink/?LinkId=251943

Furthermore, I invite you to read the following blog where the Windows 8 developers team talk about the differences between CP and RP:
https://blogs.msdn.com/b/windowsappdev/archive/2012/05/31/what-s-changed-for-app-developers-since-the-consumer-preview.aspx

From my point of view here are the main concerns I encountered:

The new navigator.js

The first point is about the navigator.js file where I added some cool new things.

First of all I added animations during navigation process. I also added a new function in the pages called afterPageEnter. This function is called when the enterPage animation is played. This allows your code to be launched when everything is ready. But it also allows your animations to get all the power in order to keep them fluid even on small computers:

_navigated: function (args) {
    var that = this;
    var newElement = that._createPageElement();
    var parentedComplete;
    var parented = new WinJS.Promise(function (c) { parentedComplete = c; });

    args.detail.setPromise(
        WinJS.Promise.timeout().then(function () {
            if (that.pageElement.winControl && that.pageElement.winControl.unload) {
                that.pageElement.winControl.unload();
            }
            return WinJS.UI.Pages.render(args.detail.location, newElement, args.detail.state, parented);
        }).then(function parentElement(control) {
            that._previousPage = newElement.winControl;
            that.element.appendChild(newElement);
            that.element.removeChild(that.pageElement);

            parentedComplete();

            var offset = { top: "0px", left: "50px" };
            var enterPage = WinJS.UI.Animation.enterPage(newElement, offset);
            enterPage.then(function () {
                document.body.focus();
                that.navigated();

                setImmediate(function () {
                    if (that._previousPage.afterPageEnter) {
                        that._previousPage.afterPageEnter(newElement);
                    }
                });
            });
        })
    );
},

The complete new navigator.js file is available here:

https://www.catuhe.com/MSDN/Urza/Navigator.zip

The default page

The first page of your project must be something like that now:

(function () {
    "use strict";

    var app = WinJS.Application;
    var nav = WinJS.Navigation;
    var ui = WinJS.UI;

…
    app.onactivated = function (eventObject) {
        if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch ||
            eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.search ||
            eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.fileOpenPicker) {

            eventObject.setPromise(WinJS.UI.processAll().then(function () {
                WinJS.Resources.processAll().then(function () {

if (nav.location) { nav.history.current.initialPlaceholder = true; return nav.navigate(nav.location, nav.state); } else { return nav.navigate(UrzaGatherer.navigator.home); } }); })); } }; app.oncheckpoint = function (args) { }; app.start(); })();

The application activation is now handled by a promise presented by the argument of the onactivated event. The navigation is also launched by your code with the nav.navigate function.

Multi-sized listView

Another important point is the new way used to define multi-sized listViews.

listView.layout= new ui.GridLayout({
        groupInfo: function () {
            return {
                enableCellSpanning: true,
                cellWidth: 28,
                cellHeight: 13
            };
        }
    });

This new naming was created in order to underline the fact that each item must have a proportional size relatively to a specified cell size (cellWidth / cellHeight).

Semantic Zoom bug workaround

The Release Preview is not the final version so some little bugs may be found.

One of them was really hard to fix so I decided to share the solution with you Sourire

On the main page, there is a semantic zoom. Under certain circumstances (linked to the power of the computer) the base listView just don’t display. Obviously this is not reproductible in debug mode (cool!) and not everytime (cool again !).

After fighting during a whole day, I finally find a workaround: After setting the datasources, you just have to launch again the layout process of the semantic zoom control:

var zoomedListView = element.querySelector(".zoomedList").winControl;

var groupDataSource = UrzaGatherer.ExpansionsListWithCardOfTheDay.createGrouped(
this.groupKeySelector, this.groupDataSelector, this.groupCompare); listView.itemDataSource = groupDataSource.dataSource; listView.groupDataSource = groupDataSource.groups.dataSource; zoomedListView.itemDataSource = groupDataSource.groups.dataSource; if (onlyOnce)
element.querySelector(".zoomControl").winControl.forceLayout();

 

Please note the usage of the onlyOnce variable to be sure that you apply the patch only the first time.

The magic of setImmediate

A last important point of this migration is the understanding of the setImmediate (or msSetImmediate) function:

https://msdn.microsoft.com/en-us/library/windows/apps/hh453394.aspx

This function is used to execute your code right after the current block and also right after any remaining operations (animations, UI,etc.).

This will be really useful to handle some specifics problems like for instance the “click-of-the-dead” when your users click too quickly the back button. Indeed, in this case, using a setImmediate allows you to go back to the previous page but after the completion of the page rendering (if you don’t do that, WinJS can try to manipulate some disposed UI elements).

To take this problem in account, I updated the navigator.js file in order to change the back button click handler:

if (backButton) {
    backButton.onclick = function () {
        setImmediate(function () {
            nav.back();
        });
    };

    if (nav.canGoBack) {
        backButton.removeAttribute("disabled");
    } else {
        backButton.setAttribute("disabled", "disabled");
    }
}

Another example can be found in the code used for the scrolling:

setImmediate(function () {
    if (currentIndex)
        listView.indexOfFirstVisible = currentIndex;
});

 

Adding binding with WinJS.Binding.as

I didn’t covered data binding during the previous episodes. It is indeed an important point and finally I used it broadly inside UrzaGatherer.

For instance, I wanted to add a random card in the home screen and I wanted to change the card every two minutes:

image_thumb9

To do so, I created a binding list with all expansions. I added to this list a specific object used to represent the random card.

To make this card bindable (I mean: every change applied to the card is automaticaly synchronized with the UI), it is really simple because you just have to to use a WinJS.Binding.as class:

 var bindableCard = new WinJS.Binding.as(card);
 expansion.cardsList.push(bindableCard);

Using this class, you create a completely bindable object.

<div class="cardOfTheDayTemplate" data-win-control="WinJS.Binding.Template">
    <div class="card-image-container" data-win-control="UrzaGatherer.Tools.DelayImageLoader"
        data-win-options="{root: 'cards'}">
        <img class="card-image" data-win-bind="src: url" src="#" />
    </div>
    <div class="card-overlay">
    </div>
    <h4 class="card-title" data-win-bind="textContent: name"></h4>
    <div class="item-border">
    </div>
    <div class="item-check" 
data-win-bind="style.display: card.isChecked UrzaGatherer.Tools.BoolToDisplayConverter"> <div class="checkBackground"></div> <div class="checkmark"></div> </div> </div>

Everything is based on the data-win-bind. The templates used by the listviews are fully compatible with this object and when a property of the object change, the UI responds automatically (like in XAML!!):

var updateCardOfTheDay = function () {
    var card;

    do {
        var cardNumber = Math.floor(Math.random() * cards.length);
        card = cards[cardNumber];
    }
    while (card.size.isPlane);

    if (cardOfTheDayBinding.element) {
        WinJS.Utilities.query("img", cardOfTheDayBinding.element).forEach(function (img) {
            WinJS.Utilities.removeClass(img, "loaded");
        });
    }

    cardOfTheDayBinding.url = card.logo;
    cardOfTheDayBinding.name = card.name;
    cardOfTheDayBinding.card = card;
}

With this superb technology, I decided to update all my templates. So for instance, when the user chooses to use local or distant files, I just have to change the according properties of my objects:

var updateBlockOfflineState = function (block, offline) {
            if (offlineMode) {
               block.logo = "ms-appdata:///local/blocks/" + block.name.replace(":", "_") + ".png";
               block.banner = "ms-appdata:///local/blocks/" + block.name.replace(":", "_") + "_banner.png";
            }
            else {
               block.logo = root + "/blocks/" + block.name.replace(":", "_") + ".png";
               block.banner = root + "/blocks/" + block.name.replace(":", "_") + "_banner.png";
            }
    }

 

image_thumb6

That’s it!!

That’s it! You’re done. The application can be published on the store (already done for UrzaGatherer: https://apps.microsoft.com/webpdp/app/urzagatherer/92adce33-8490-4af7-9392-9c35c91d8a37).

Now, feel free to use my tutorials as a basis for your project. You’re almost ready now Clignement d'œil

How to cook a complete Windows 8 application with HTML5, CSS3 and JavaScript in a week – Day 4

This is the final part of our series. Actually, I will post a last article when the Release Preview will be available to give you the updated version but you can consider this version as feature full.

And as usual the complete solution is available here: https://www.catuhe.com/msdn/urza/day4.zip

The complete series can be found here:

During this article you will discover how you can use Skydrive (via the Live SDK: Download the Live SDK) to save the collection’s state of your user.

The entire collection list is downloaded on a site as a json file (cf. Day 0). The collection’s state will be saved in another json file so the cards list can evolve without impacting the collection’s state. This json file will be saved to the user’s SkyDrive.

Adding a collection’s state file

In the expansion page, you have to add a contextual app bar to allow user to select card in order to indicate that the card is currently in his collection:

The new controls in the appbar are the following:

<button data-win-control="WinJS.UI.AppBarCommand" 
data-win-options="{id:'checkButton',section:'selection'}"> </button> <button data-win-control="WinJS.UI.AppBarCommand"
data-win-options="{id:'uncheckButton',section:'selection'}"> </button> <hr data-win-control="WinJS.UI.AppBarCommand"
data-win-options="{type:'separator',section:'selection'}" /> <button data-win-control="WinJS.UI.AppBarCommand"
data-win-options="{id:'checkAllButton',section:'selection'}"> </button> <button data-win-control="WinJS.UI.AppBarCommand"
data-win-options="{id:'uncheckAllButton',section:'selection'}"> </button>

Every button is set to use the ‘selection’ section in order to appear in the left part of the app bar (the contextual part). By right-clicking an item (or swiping it down) in the listView, WinJS displays the appbar with all contextual buttons activated. Otherwise, only the buttons placed in the global part are displayed.

Don’t forget obviously to activate the multi-selection on the listView:

listView.selectionMode = WinJS.UI.SelectionMode.multi;

By clicking on these buttons, you call one of the following functions:

var removeFromCollection = function (tab) {
    if (!UrzaGatherer.UserData) {
        showWarning();
        return;
    }

    for (var index = 0; index < (tab ? tab.length : filtered.length); index++) {
        var card = filtered.getAt(tab ? tab[index] : index);

        card.isChecked = false;

        if (card.checkElement)
            WinJS.Utilities.addClass(card.checkElement, "hidden");

        delete UrzaGatherer.UserData[card.id];
    };

    var listView = document.querySelector(".cardsList").winControl;
    listView.selection.clear();

    if (document.getElementById("checkFilter").selectedIndex > 0)
        that.updateLayout(document, appView.value);

    UrzaGatherer.Skydrive.UploadUserFile();

    document.querySelector(".appBar").winControl.hide();
};

var addToCollection = function (tab) {
    if (!UrzaGatherer.UserData) {
        showWarning();
        return;
    }

    for (var index = 0; index < (tab ? tab.length : filtered.length); index++) {
        var card = filtered.getAt(tab ? tab[index] : index);

        card.isChecked = true;

        if (card.checkElement)
            WinJS.Utilities.removeClass(card.checkElement, "hidden");

        UrzaGatherer.UserData[card.id] = true;
    };

    var listView = document.querySelector(".cardsList").winControl;
    listView.selection.clear();

    if (document.getElementById("checkFilter").selectedIndex > 0)
        that.updateLayout(document, appView.value);

    UrzaGatherer.Skydrive.UploadUserFile();

    document.querySelector(".appBar").winControl.hide();
};

Every function checks the existence of UrzaGatherer.UserData which represents the current state of the user’s collection. If a tab parameter is passed, each functions works on it else they work on the entire expansion.

Adding an item to the user’s collection is just like adding a property named with the card unique id:

UrzaGatherer.UserData[card.id] = true;

So the user’s collection is just a big object with a property for each owned card Sourire. This object is easily serilizable with JSON.stringify!

For ease of access, the card has also a isChecked property. 

Connecting to Skydrive

To keep track of the user’s collection, it is required to save it to a safe place. You can then use SkyDrive as a central repository (with the agreement of the user obviously).

To do so, you have to download the Live SDK and to reference it in your project:

Then you have to reference the javascript file:

    <script src="///LiveSDKHTML/js/wl.js"></script>

First of all you have to give the user a way to connect to Live services:

var init = function () {
    var onStatusChange = function (e) {
        WL.getLoginStatus(function (response) {
            if (response.status == "notConnected") {
                connect();
            }
        });
    };

    WL.Event.subscribe("auth.login", getUserFile);
    WL.Event.subscribe("auth.statusChange", onStatusChange);

    WL.init({
        scope: ["wl.signin", "wl.skydrive_update"]
    });
};

This code can be called from a button in your application (I recommend you to put it in a corner of your application with a duplicate in the settings pane).

As you saw, the code subscribes to two events: auth.login and auth.statusChange. When the user is connected, you have to call the getUserFile (you will see it later). When the status change to “notConnected” you have to call the connect code:

var connect = function () {
    if (UrzaGatherer.Skydrive.OnConnecting)
        UrzaGatherer.Skydrive.OnConnecting();

    WL.login({
        scope: ["wl.signin", "wl.skydrive_update"]
    }).then(function (response) {
        getUserFile();
    }, function (responseFailed) {

        if (responseFailed.error == "access_denied") {
        }
        else {
            if (UrzaGatherer.Skydrive.OnFailed)
                UrzaGatherer.Skydrive.OnFailed();
        }
    });
};

I defined some events to allow my UI to display some feedbacks during the connection. The WL.login functions displays a control that asks the user to authorize your application to sign in Live services (“wl.signin”) and to access the user’s skydrive (“_wl.skydrive_update_”).

If the user refuses to grant the authorization, you can raise an event to synchronize your UI.

If the user accepts to grant the authorization, the following code is called:

var getUserFile = function () {
    UrzaGatherer.UserLogged = true;

    // Creating folder
    WL.api({
        path: "me/skydrive",
        method: "POST",
        body: {
            "name": "UrzaGatherer",
            "description": "UrzaGatherer repository folder"
        }
    }).then(
            function (response) {
                Windows.Storage.ApplicationData.current.roamingSettings.values["folderID"] = response.id;
                getFileOnline();
            },
            function (responseFailed) {
                if (responseFailed.error.code != "resource_already_exists") {
                    getFileOffline();
                    if (UrzaGatherer.Skydrive.OnFailed)
                        UrzaGatherer.Skydrive.OnFailed();
                }
                else {
                    getFileOnline();
                }
            }
        );

};

This function creates a folder for UrzaGatherer in SkyDrive and try to get the user’s collection. If there is an error, it tries to get the local copy of the user’s collection.

To get the online version, the following code is called:

var getFileOnline = function () {
    var fileID = Windows.Storage.ApplicationData.current.roamingSettings.values["fileID"];

    if (!fileID) {
        getFileOffline();
        return;
    }

    // Get file info
    WL.api({
        path: fileID,
        method: "GET"
    }).then(
            function (response) {
                var distantVersion = parseInt(response.description ? response.description : "-1");
                var localVersion = currentUserFileVersion();

                // Download file
                if (localVersion < distantVersion) {
                    Windows.Storage.ApplicationData.current.localFolder.
createFileAsync(filename, Windows.Storage.CreationCollisionOption.replaceExisting).then(
function (file) { WL.download({ path: fileID, file_output: file }).then( function (response) { getFileOffline(); }, function (responseFailed) { getFileOffline(); } ); }); return; } getFileOffline(); }, function (responseFailed) { getFileOffline(); } ); };

You can notice that once the file is loaded, a local copy is automaticaly created.

You may also have noticed that a file version number is maintained in order to know if the server version is more recent than the local one.

In case of an error, the following code can retrieve the local copy (if it exists):

var getFileOffline = function () {
    Windows.Storage.ApplicationData.current.localFolder.getFileAsync(filename).then(function (file) {
        // On success
        Windows.Storage.FileIO.readTextAsync(file).then(function (data) {
            if (data)
                UrzaGatherer.UserData = JSON.parse(data);
            else
                UrzaGatherer.UserData = {};

            if (UrzaGatherer.Skydrive.OnConnected && UrzaGatherer.UserLogged)
                UrzaGatherer.Skydrive.OnConnected();

            if (UrzaGatherer.Skydrive.OnDataAvailable)
                UrzaGatherer.Skydrive.OnDataAvailable();
        });
    }, function () {
        // On error
        Windows.Storage.ApplicationData.current.localFolder.
createFileAsync(filename, Windows.Storage.CreationCollisionOption.replaceExisting).then(
function (file) { UrzaGatherer.UserData = {}; Windows.Storage.FileIO.writeTextAsync(file, JSON.stringify(UrzaGatherer.UserData)); Windows.Storage.ApplicationData.current.
localSettings.values[
"userFileVersion"] = currentUserFileVersion() + 1; if (UrzaGatherer.Skydrive.OnConnected && UrzaGatherer.UserLogged) UrzaGatherer.Skydrive.OnConnected(); if (UrzaGatherer.Skydrive.OnDataAvailable) UrzaGatherer.Skydrive.OnDataAvailable(); }); }); };

If the system is unable to get a local copy, an empty version is created.

Updating the user’s collection to SkyDrive

Finally, the application can call the following code to upload the user’s collection to SkyDrive:

var uploadUserFile = function (deferral) {
    Windows.Storage.ApplicationData.current.localFolder.
createFileAsync(filename, Windows.Storage.CreationCollisionOption.replaceExisting).then(
function (file) { Windows.Storage.FileIO.writeTextAsync(file, JSON.stringify(UrzaGatherer.UserData)).
then(
function () { var folderID = Windows.Storage.ApplicationData.current.roamingSettings.values["folderID"]; Windows.Storage.ApplicationData.current.localSettings.
values[
"userFileVersion"] = currentUserFileVersion() + 1; WL.upload({ path: folderID, file_name: file.name, file_input: file, overwrite: true }).then( function (response) { Windows.Storage.ApplicationData.current.roamingSettings.values["fileID"] = response.id; updateDistantFileVersion(currentUserFileVersion()); if (deferral) deferral.complete(); }, function (responseFailed) { if (deferral) deferral.complete(); } ); }); }); };

Once the file is uploaded, the file version number is updated locally and on SkyDrive:

var updateDistantFileVersion = function (version) {
    var fileID = Windows.Storage.ApplicationData.current.roamingSettings.values["fileID"];

    if (!fileID)
        return;

    WL.api({
        path: fileID,
        method: "PUT",
        body: {
            description: version
        }
    }).done();
};

So with a small set of API, you can create, read and write data on SkyDrive. The connection UI is provided by the SDK so it is really simple to integrate it in your onw application.

You can even use predefined controls to present an Connect/Disconnect button.

To be continued

You have now a complete and compliant Windows 8 application that can help you create our own award winning app! When the Release Preview will be out (early in June), I will post a last article to show you how to port your code.

On my side, I will publish UrzaGatherer in the Windows store when the Release Preview will be available. I added some visual features like a card of the day, a dynamic background and tons of appealing animations:

 

So stay tuned for the final version Sourire

How to cook a complete Windows 8 application with HTML5, CSS3 and JavaScript in a week – Day 3

Today the menu is about integrating your application into Windows 8 Metro.

The first part of the integration was done with the snapped views and settings but it is now time to finish the job with:

  • Search contract
  • Share contract
  • File picking contract
  • Live tile
  • Secondary tiles

These five subjects are really important for the symbiosis between Windows 8 Metro and your application.

 

  

And as usual the complete solution is available here: https://www.catuhe.com/msdn/urza/day3.zip

The complete series can be found here:

You said “contracts” ?

A contract (https://msdn.microsoft.com/en-us/library/windows/apps/hh464906.aspx) is the definition of a technical interface between your application and Windows 8 Metro. It is a clearly important subject because it allows your application to define new entry points beyond its own tile.

Your application is then allowed to take part of some important services of Windows 8 Metro like the search, the data sharing or the file selection for instance.

You must think of supporting contracts during the creation of your application because if you omit them users can be disappointed.

By chance (!!) UrzaGatherer can easily supports the 3 main contracts.

Search contract

The search contract allows the user to search within your application but from a global common interface:

All the applications supporting the search contract (and allowed by the user) are listed here using a “most used first” sort. You can also see that searching apps, settings or files is available in the same place.

To be recognized by Windows as a search provider, you just have to declare it using the application manifest:

It is possible to define a special landing page for the search but for UrzaGatherer as I need to load data first and I don’t want to change all my architecture, I don’t need to provide a special page.

It is then important to update the default.js file to detect the fact that the application was launched from the search pane:

    app.onactivated = function (eventObject) {
        if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch ||
            eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.search ||

By the way, if the search pane launched the application, you have to save the query text:

switch (eventObject.detail.kind) {
    case Windows.ApplicationModel.Activation.ActivationKind.search:
        UrzaGatherer.QueryText = eventObject.detail.queryText;
        break;

So when the home.html page is loaded, if the query text is defined, you can jump to the search page (home.html was in this case only used to load data):

if (UrzaGatherer.QueryText) {
    var queryText = UrzaGatherer.QueryText;
    delete UrzaGatherer.QueryText;

    nav.navigate("/pages/search/search.html", { queryText: queryText });
}

Furthermore, you need to handle the fact that the search can be launched when your application is already launched. To do so, you have to listen for a special WinRT event:

// Search
var searchPane = Windows.ApplicationModel.Search.SearchPane.getForCurrentView();
searchPane.addEventListener("querysubmitted", function (e) {
    nav.navigate("/pages/search/search.html", { queryText: e.queryText });
}, false);

As you can see, when a request is emitted, you just have to jump to the search page (passing it the query text).

The search page is a clone of the expansion page but instead of displaying expansion’s cards it displays the cards containing the query text:

Please note that the search page also contains filters to refine the search if required.

Your application can also improve the search experience by providing search suggestions as user enters his query:

 

To do so, you have to listen another event:

// Register to Handle Suggestion Request
searchPane.addEventListener("suggestionsrequested", function (e) {
    var query = e.queryText;
    var maxNumberOfSuggestions = 5;
    var suggestionRequest = e.request;

    for (var i = 0, len = UrzaGatherer.Cards.length; i < len; i++) {
        if (UrzaGatherer.Cards[i].name.substr(0, query.length).toLowerCase() === query) {
            suggestionRequest.searchSuggestionCollection.appendQuerySuggestion(UrzaGatherer.Cards[i].name);
            if (suggestionRequest.searchSuggestionCollection.size === maxNumberOfSuggestions) {
                break;
            }
        }
    }
}, false);

 

Share contract

The share contract is a really useful contract as it gets rid of the pain of implementing a client for every data sharing services you want to support (Facebook, Twitter, mail, …).

With the share contract, applications can be defined as a share source or as a share target:

For example in this screen capture, I can share my card to Facebook or Twitter using FlipToast or I can send it by mail using Courrier.

Your application can now focus on its own business!

For UrzaGatherer, each card can exposed its image to the data sharing service. All share targets can then decide what they want to do with (publish on Twitter, send by mail, etc.) when they are selected by the user.

To do so, you just have to listen for the datarequested event from the DataTransferManager :

// Share
var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView();
dataTransferManager.addEventListener("datarequested", shareFunction);

The used function is the following:

var shareFunction = function (e) {
    var request = e.request;
    var deferral = request.getDeferral();

    request.data.properties.title = card.name + UrzaGatherer.Tools.GetString("ShareTitle") 
+ card.expansion.name; request.data.properties.description = UrzaGatherer.Tools.GetString(
"ShareDescription"); UrzaGatherer.Tools.GetCardFile(card).then(function (file) { var streamReference = Windows.Storage.Streams.RandomAccessStreamReference.createFromFile(file); request.data.properties.thumbnail = streamReference; request.data.setStorageItems([file]); request.data.setBitmap(streamReference); deferral.complete(); }); };

The function just have to fill in the request with information from the card. Please note the use of getDeferral because the function is asynchronous (meaning that the fill process is not finished when the function returns)

FileOpenPicker contract

The file picking contract allows application to provide files to others application (for instance, Skydrive allows users to pick a file in the cloud as it was local). There is also a FileSavePicker contract to get files from others application (for instance, Skydrive allows users to save a file in the cloud).

For UrzaGatherer, you can provide an image for each card. For instance, starting from the mail application:

You can decide to pick a picture inside UrzaGatherer to add it as an attached file:

As UrzaGatherer is a file provider, you can see it in the list of registered providers (at the same level as SkyDrive). By selecting it, UrzaGatherer is launched inside the Windows picker UI and the user can choose a card to get a file containing its picture:

To register your application as a file provider you can use a similar process as the search contract:

image_thumb6

From the point of view of your code, it is easy! First of all, you have to take in account that the application can be launched by the file picker:

app.onactivated = function (eventObject) {
        if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch ||
            eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.search ||
            eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.fileOpenPicker){

 

Then, you have to save a link to the file picker UI to be able to transmit files afterward:

switch (eventObject.detail.kind) {
    case Windows.ApplicationModel.Activation.ActivationKind.fileOpenPicker:
        UrzaGatherer.FileOpenPickerUI = eventObject.detail.fileOpenPickerUI;
        break;
    default:

Then, once you are in the expansion page, when the user clicks on a card, instead of jumping to the card page you just have to retrieve a file:

itemInvoked: function (eventObject) {
    if (UrzaGatherer.FileOpenPickerUI) {
        var item = eventObject.detail.itemPromise.then(function (invokedItem) {
            var card = invokedItem.data;

            UrzaGatherer.Tools.GetCardFile(card).then(function (file) {
                UrzaGatherer.FileOpenPickerUI.addFile(card.name, file);
            });
        });
        return;
    }
    nav.navigate("/pages/card/card.html", { cardIndex: eventObject.detail.itemIndex, cards: filtered });
},

The following function is used to get a file from a card :

var getCardFile = function (card) {
    // HTTP ?
    if (card.logo.substring(0, 5).toLowerCase() == "http:") {
        var uri = new Windows.Foundation.Uri(card.logo);
        var thumbnail = Windows.Storage.Streams.RandomAccessStreamReference.createFromUri(uri);

        return Windows.Storage.StorageFile.createStreamedFileFromUriAsync(card.name + ".jpg", 
uri, thumbnail); }
// Local ? return Windows.Storage.ApplicationData.current.localFolder.getFileAsync(card.alt); }

Live tile

Windows 8 Metro is now based on tiles to launch applications. A tile is like a super icon (https://msdn.microsoft.com/en-us/library/windows/apps/hh779724.aspx) which is dynamic and updatable by many ways (application, background task, notification service):

Send an update to the tile

The UrzaGatherer main tile is updated every time you go to the card page. The code used is the following:

var updateTile = function (card) {
    var Notifications = Windows.UI.Notifications;
    var Imaging = Windows.Graphics.Imaging;

    var tileXml = Notifications.TileUpdateManager.getTemplateContent(
Notifications.TileTemplateType.tileWideSmallImageAndText02);
var tileTextAttributes = tileXml.getElementsByTagName("text"); tileTextAttributes[0].appendChild(tileXml.createTextNode("UrzaGatherer")); tileTextAttributes[1].appendChild(tileXml.createTextNode(card.name)); tileTextAttributes[2].appendChild(tileXml.createTextNode(card.expansion.name)); tileTextAttributes[3].appendChild(tileXml.createTextNode(card.expansion.block.name)); var filename = card.alt.replace(".jpg", "_small.png"); rescaleImage(card.logo, 150, 150, filename, true, function (appDatafilename) { // Image var tileImageAttributes = tileXml.getElementsByTagName("image"); tileImageAttributes[0].setAttribute("src", appDatafilename); // Square var squareTileXml = Notifications.TileUpdateManager.getTemplateContent(
Notifications.TileTemplateType.tileSquareImage);
var squareTileImageAttributes = squareTileXml.getElementsByTagName("image"); squareTileImageAttributes[0].setAttribute("src", appDatafilename); var node = tileXml.importNode(squareTileXml.getElementsByTagName("binding").item(0), true); tileXml.getElementsByTagName("visual").item(0).appendChild(node); // Update var tileNotification = new Notifications.TileNotification(tileXml); tileNotification.tag = card.id; var tileUpdater = Windows.UI.Notifications.TileUpdateManager.createTileUpdaterForApplication(); tileUpdater.enableNotificationQueue(true); tileUpdater.update(tileNotification); }); }

First of all you have to choose the configuration of your tile with the Notifications.TileUpdateManager.getTemplateContent function(https://msdn.microsoft.com/en-us/library/windows/apps/hh761491.aspx).

This function returns a XML object that you have to fill in to update your tile.

Generate a new picture for the tile

One of the problem I faced when I created the code to update the tile was the size of the awaited pictures. I had to rescale my image to fit in the tile format. I used the following function to do so:

var rescaleImage = function (src, destinationWidth, destinationHeight, localfilename, fillAlpha, then) {
    var Imaging = Windows.Graphics.Imaging;
    var image = new Image();

    // lors du chargement
    image.addEventListener('load', function () {
        var canvas = document.createElement('canvas');

        canvas.width = destinationWidth;
        canvas.height = destinationHeight;

        var targetWidth;
        var targetHeight;

        if (this.width > this.height) {
            var ratio = destinationWidth / this.width;
            targetWidth = destinationWidth;
            targetHeight = this.height * ratio;
        }
        else {
            var ratio = destinationHeight / this.height;
            targetWidth = this.width * ratio;
            targetHeight = destinationHeight;
        }

        var context = canvas.getContext('2d');
        if (fillAlpha)
            context.clearRect(0, 0, canvas.width, canvas.height);
        else {
            context.fillStyle = "#fff";
            context.fillRect(0, 0, canvas.width, canvas.height);
        }
        context.drawImage(this, (canvas.width - targetWidth) / 2, (
canvas.height - targetHeight) / 2, targetWidth, targetHeight); Windows.Storage.ApplicationData.current.localFolder.createFileAsync(localfilename, Windows.Storage.CreationCollisionOption.replaceExisting).then(
function (file) { file.openAsync(Windows.Storage.FileAccessMode.readWrite).then(function (stream) { Imaging.BitmapEncoder.createAsync(Imaging.BitmapEncoder.pngEncoderId, stream).then(
function (encoder) { encoder.setPixelData(Imaging.BitmapPixelFormat.rgba8, Imaging.BitmapAlphaMode.straight, canvas.width, canvas.height, 96, 96, new Uint8Array(context.getImageData(0, 0, canvas.width, canvas.height).data)); encoder.flushAsync().then(function () { stream.flushAsync().then(function () { stream.close(); if (then) then("ms-appdata:///local/" + localfilename.replace("\", "/")); }); }); }); }); }); }, false); // Chargement image.src = src; }

Its operation is as follows:

  • Create a HTML image and listen for the load event
  • Set the source of the image to the URL of the source picture
  • After loading the image, draw it on a well sized canvas
  • Get the pixels of the canvas using getImageData
  • Create a BitmapEncoder to generate a bitmap file
  • Using a typed array (Uint8Array ) copy (without any conversion) the canvas pixels to the BitmapEncoder using setPixelData function
  • Save it to a file and voila !

Cycling on many updates

Using a tileUpdater (through Windows.UI.Notifications.TileUpdateManager.createTileUpdaterForApplication function), you can register an update of your tile.

You can even define a queue of notifications to create a cool cycling effect on your tile (like a FIFO of updates).

And to reduce the disk footprint of this system, you have to save the path of every generated picture in order to remove them afterwards:

// Store and clean
var previousTilesValue = Windows.Storage.ApplicationData.current.localSettings.values["previousTiles"];
var previousTiles = [];

if (previousTilesValue)
    previousTiles = JSON.parse(previousTilesValue);

previousTiles.push(filename);

if (previousTiles.length > 5) {
    var toRemove = previousTiles.shift();

    Windows.Storage.ApplicationData.current.localFolder.getFileAsync(toRemove).then(function (file) {
        file.deleteAsync().done();
    });
}

Windows.Storage.ApplicationData.current.localSettings.values["previousTiles"] = 
JSON.stringify(previousTiles);

 

Secondary tiles

The secondary tiles work the way as the main tile but they give users the opportunity to create a deep link into your application. Indeed, instead of pointing to the root page (default.html), they can provide arguments to point to any part of the application.

For UrzaGatherer, the expansion page can create secondary tiles via its application bar (the bottom bar):

 

So first of all you have to create the appbar:

<!--App bar-->
<div data-win-control="WinJS.UI.AppBar" data-win-options="">
    <button data-win-control="WinJS.UI.AppBarCommand" data-win-options="{id:'pinButton', 
icon:'pin',section:'global'}"> </
button> </div>

Then you have to register to the click on your appbar button:

var pinByElementAsync = function (then) {
    var tileID = expansion.id;
    var shortName = expansion.name;
    var displayName = expansion.name;
    var tileOptions = Windows.UI.StartScreen.TileOptions.showNameOnWideLogo;
    var tileActivationArguments = expansion.id;

    UrzaGatherer.Tools.RescaleImage(expansion.logo, 150, 150, 
expansion.logoPath.replace(
".png", "_uri.png"), false, function (uriLogo) { var tile = new Windows.UI.StartScreen.SecondaryTile(tileID, shortName, displayName,
tileActivationArguments, tileOptions,
new Windows.Foundation.Uri(uriLogo)); tile.foregroundText = Windows.UI.StartScreen.ForegroundText.dark; UrzaGatherer.Tools.RescaleImage(expansion.logo, 310, 150,
expansion.logoPath.replace(
".png", "_wide.png"), false, function (wideLogo) { tile.wideLogo = new Windows.Foundation.Uri(wideLogo); var element = document.getElementById("pinButton"); var selectionRect = element.getBoundingClientRect(); tile.requestCreateAsync({ x: selectionRect.left, y: selectionRect.top }).then(then); }); }); };

You can notice the reuse of the RescaleImage function in order to scale the logos to an appropriate size.

The tile is attached with an argument (tileActivationArguments) which will be transmitted to the application when the users will click on the tile.

This argument can be retrieved with the following code:

 app.onactivated = function (eventObject) {
     if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch ||
         eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.search ||
         eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.fileOpenPicker) {

switch (eventObject.detail.kind) { case Windows.ApplicationModel.Activation.ActivationKind.search: UrzaGatherer.QueryText = eventObject.detail.queryText; break; case Windows.ApplicationModel.Activation.ActivationKind.fileOpenPicker: UrzaGatherer.FileOpenPickerUI = eventObject.detail.fileOpenPickerUI; break; default: UrzaGatherer.Arguments = eventObject.detail.arguments; break; }

So when home.html is loaded, you can check if UrzaGatherer.Arguments is not null in order to directly jump to the expansion page:

if (UrzaGatherer.Arguments) {
    var expansionID = parseInt(UrzaGatherer.Arguments);
    delete UrzaGatherer.Arguments;

    var expansion;

    for (var index = 0; index < UrzaGatherer.Expansions.length; index++) {
        var exp = UrzaGatherer.Expansions.getAt(index);

        if (exp.id == expansionID) {
            expansion = exp;
            break;
        }
    }

    nav.navigate("/pages/expansion/expansion.html", { expansion: expansion });
}

Your application can so have many associated tiles on the home screen:

image_thumb2

Obviously, in the same way as the main tile, secondary tiles are updatable.

 

To be continued

The next stop will be about adding Live SDK and Skydrive support in order to handle your own collection.

How to cook a complete Windows 8 application with HTML5, CSS3 and JavaScript in a week – Day 2

Today, the chief proposes you to delight yourself with the following dishes:

  • Internationalization
  • Managing the activation of the offline mode
  • Adding a flipView to the cards page
  • Handling snapped views
  • Supporting different form factors
  • Supporting different cards sizes

image_thumb2

 

As usual the complete solution is there : https://www.catuhe.com/msdn/urza/day2.zip

The complete series can be found here:

    

Internationalization

UrzaGatherer is intended to be present on all marketplaces supported by the Windows 8 store. It is then important to internationalize it. To do so, you just have to add resources.resjon files in a folder named with the language you want to support (for instance, “en” for English or “fr” for French):

 

image_thumb8

image_thumb11

This file contains a json description of key/value pairs used by the localization system of WinJS (the core JavaScript of Windows 8 Metro):

{
    "Settings"              : "Settings",
    "CardsZoomLevel"        : "Cards zoom level",
    "OfflineFiles"          : "Offline files",
    "OfflineFilesRestart"   : "(You must restart to apply this settings)",
    "On"                    : "On",
    "Off"                   : "Off",
    "DownloadInProgress"    : " download(s) in progress",
    "LoadingData"           : "Loading data...please wait",
    "ErrorConnection"       : "Error during connection to server. Please check you connectivity.",
    "All"                   : "All ",
    "Colors"                : "colors",
    "Authors"               : "authors",
    "Rarities"              : "rarities",
    "Types"                 : "types",
    "Cards"                 : " cards",
    "Number"                : "Number:",

    "Type"                  : "Type:",
    "Color"                 : "Color:",
    "Power"                 : "Power:",
    "Rarity"                : "Rarity:",    
    "Text"                  : "Text:",
    "Flavor"                : "Flavor:",
    "Author"                : "Author:",
    "AveragePrice"          : "Average price:",
    "ByNumber"              : "By number",
    "ByName"                : "By name",
    "OnlyMissing"           : "Only missing",
    "AllExceptMissing"      : "All except missing"
}

You can have this kind of file for each language you want to support. WinJS will then choose for you the right file according to the active user language.

To use the resource file in your HTML page, you have to add a data-win-res attribute:

<span data-win-res="{innerText: 'OfflineFilesRestart'}"></span>

Calling the following function will process the added attribute and will affect the resource string to the specified property:

WinJS.Resources.processAll();

You just have to not forget to call it. For UrzaGatherer I call it in the ready function of my pages:

 ui.Pages.define("/pages/card/card.html", {
     ready: function (element, options) {
         WinJS.Resources.processAll();

Furthermore, you can also call the resource system from your own code to get the value associated to a given key:

var resourceLoader = new Windows.ApplicationModel.Resources.ResourceLoader();
var getString = function (name) {
    if (!name)
        return "";

    return resourceLoader.getString(name);
};

 

Managing the activation of the offline mode

Some users may not want to use the offline files mode (to prevent UrzaGatherer to use too much disk space for instance). To handle that, you can add a new property in the settings:

image_thumb5

To do so, you just have to add the following HTML code in the default.html page:

<div id="settingsDiv" data-win-control="WinJS.UI.SettingsFlyout" data-win-options="{width:'narrow'}">
    <div class="win-header">
        <button type="button" onclick="WinJS.UI.SettingsFlyout.show()" class="win-backbutton">
        </button>
        <div class="win-label" data-win-res="{innerText: 'Settings'}"></div>
    </div>
    <div class="win-content">
        <h4 data-win-res="{innerText: 'CardsZoomLevel'}"></h4>
        <input type="range" id="zoomRange" min="20" max="80" value="50" />
        <fieldset class="controlGroup">
            <legend class="controlGroupName"><span data-win-res="{innerText: 'OfflineFiles'}"></span>
                <br />
                <i><span data-win-res="{innerText: 'OfflineFilesRestart'}"></span></i>
            </legend>
            <label class="radioLabel horizontalLabelLayout">
                <input type="radio" name="offlineMode" checked id="offlineModeOn"/>
                <span data-win-res="{innerText: 'On'}"></span>
            </label>
            <label class="radioLabel horizontalLabelLayout">
                <input type="radio" name="offlineMode" id="offlineModeOff"/>
                <span data-win-res="{innerText: 'Off'}"></span>
            </label>
        </fieldset>
    </div>
</div>

Each radio controls the state of the offlineMode roaming setting:

// Offline mode
var on = document.getElementById("offlineModeOn");
var off = document.getElementById("offlineModeOff");
var offlineMode = Windows.Storage.ApplicationData.current.roamingSettings.values["offlineMode"];

if (offlineMode)
    on.checked = true;
else
    off.checked = true;

on.addEventListener("change", function () {
    Windows.Storage.ApplicationData.current.roamingSettings.values["offlineMode"] = true;
});

off.addEventListener("change", function () {
    Windows.Storage.ApplicationData.current.roamingSettings.values["offlineMode"] = false;
});

The image loading code is then modified to switch between a local mode (ms-appdata) et un mode distant (http):

 var offlineMode = Windows.Storage.ApplicationData.current.roamingSettings.values["offlineMode"];
 if (offlineMode) {
     block.logo = "ms-appdata:///local/blocks/" + block.name.replace(":", "_") + ".png";
     block.banner = "ms-appdata:///local/blocks/" + block.name.replace(":", "_") + "_banner.png";
 }
 else {
     block.logo = root + "/blocks/" + block.name.replace(":", "_") + ".png";
     block.banner = root + "/blocks/" + block.name.replace(":", "_") + "_banner.png";
 }

And voila! Sourire.

Adding a flipView to the cards page

The next stop is for the card page which can be easily improved by adding a flipView control to switch between cards without having to go back to the expansion page.

The flipView is a touch aware control which allows you to slide between items but it also allows you to use your mouse with the navigation buttons on the left and right edges of the control:

image_thumb14

By the way, the data have to be bound now and not directly set by code:

<body>
    <!--Template-->
    <div class="itemTemplate" data-win-control="WinJS.Binding.Template">
        <div class="item-root">
            <header aria-label="Header content" role="banner">
                <button class="win-backbutton" aria-label="Back"></button>
                <h1 class="titlearea win-type-ellipsis"><span class="pagetitle" 
                    data-win-bind="innerText: name">
                </span></h1>
            </header>
            <section aria-label="Main content" role="main">
                <h3 class="item-number-label" data-win-res="{innerText: 'Number'}"></h3>
                <div class="item-number" data-win-bind="innerText: number"></div>
                <h3 class="item-type-label" data-win-res="{innerText: 'Type'}"></h3>
                <div class="item-type" data-win-bind="innerText: type"></div>
                <h3 class="item-color-label" data-win-res="{innerText: 'Color'}"></h3>
                <div class="item-color" data-win-bind="innerText: color"></div>
                <h3 class="item-power-label" data-win-res="{innerText: 'Power'}"></h3>
                <div class="item-power" data-win-bind="innerText: power"></div>
                <h3 class="item-rarity-label" data-win-res="{innerText: 'Rarity'}"></h3>
                <div class="item-rarity" data-win-bind="innerText: rarity"></div>
                <h3 class="item-text-label" data-win-res="{innerText: 'Text'}"></h3>
                <div class="item-text" data-win-bind="innerText: text"></div>
                <h3 class="item-flavor-label" data-win-res="{innerText: 'Flavor'}"></h3>
                <div class="item-flavor" data-win-bind="innerText: flavor"></div>
                <h3 class="item-author-label" data-win-res="{innerText: 'Author'}"></h3>
                <div class="item-author" data-win-bind="innerText: author"></div>
                <h3 class="item-price-label" data-win-res="{innerText: 'AveragePrice'}"></h3>
                <div class="item-price" data-win-bind="innerText: price UrzaGatherer.Tools.PriceConverter">
                </div>
                <div class="item-image-container" data-win-control="UrzaGatherer.Tools.DelayImageLoader"
                    data-win-options="{root: 'cards'}">
                    <img class="item-image-container" data-win-bind="src: logo" />
                </div>
                <div class="expansion-image-container" data-win-control="UrzaGatherer.Tools.DelayImageLoader">
                    <img class="expansion-picture" src="#" data-win-bind="src: expansion.banner" />
                </div>
            </section>
        </div>
    </div>
    <!--Content-->
    <div class="card fragment">
        <div id="flipView" data-win-control="WinJS.UI.FlipView" 
            data-win-options="{ itemTemplate : select('.itemTemplate') }">
        </div>
    </div>
</body>

You can notice the presence of data-win-res for the localization and the presence of data-win-bind for the binding.

To set up the connection between the flipView and the data, you can faily use the same code you used for the listView:

var flipView = document.getElementById("flipView").winControl;
ui.setOptions(flipView, {
    itemDataSource: options.cards.dataSource,
    currentPage: options.cardIndex
});

 

Handling snapped views

To be able to pass store certifications, your application must support the snapped view. This mode is activated when your application is set side by side with another application. The space allowed for a snapped application is 320px wide.

You have to define a new design for the snapped view for each screen of your application.

For UrzaGatherer, I decided to provide a complete experience when in snapped view. You can of course choose to remove non relevant features but for UrzaGatherer, all features are dispatched in the snapped view.

The main screen evolves like this:

image_thumb18

The expansion screen in snapped view is like this:

image_thumb21

And the card screen:

image_thumb24

 

The magic thing here is that all these modifications are done using only one feature : the media queries (https://msdn.microsoft.com/en-us/library/windows/apps/hh453556.aspx). This feature allows you to select new CSS rules based on specific queries (for instance, the application is in snapped mode).

Here is an example for the expansion page:

@media screen and (-ms-view-state: snapped) {

    #cardsCount {
        display: none;
    }

    #cardsPrice {
        display: none;
    }

    .expansion section[role=main] {
        -ms-grid-rows: 0px 1fr;
    }
}

 

These rules will replace (or complete) the original rules. For example, the base rules for “.expansion section[role=main]” is the following:

.expansion section[role=main] {
    -ms-grid-columns: 1fr;
    -ms-grid-rows: 50px 1fr;
    display: -ms-grid;
}

In snapped mode, according to our media query it will become:

.expansion section[role=main] {
    -ms-grid-columns: 1fr;
    -ms-grid-rows: 0px 1fr;
    display: -ms-grid;
}

Your job is then to use media queries to move your HTML elements from a large horizontal layout to a small vertical layout (thanks to CSS3 Grids!).

Supporting different form factors

Media queries can also be used to adapt your application to different form factors. For example, the card page can use a media query to adapt its display to small resolutions:

@media screen and (min-height: 1024px) {
    .card section[role=main] {
        -ms-grid-columns: auto 1fr 960px;
    }

    .card .item-image-container {
        margin-top: 0px;
    }
}

On a large resolution the display is like this:

image_thumb31

 

And on a smaller resolution:

image_thumb28

You can see that the columns are smaller and the picture is moved upwards to have enough room to display.

Supporting different cards sizes

The last point I want to talk today is about handling multi-templates for the listView. Indeed, some cards in my collection are twice wider than the standard ones (mainly in the Planechase expansion):

image_thumb34

To do so, instead of giving a static template, you can provide a JavaScript function that builds dynamically the template:

var listView = element.querySelector(".cardsList").winControl;

ui.setOptions(listView, {
    itemTemplate: this.itemRenderer,
    oniteminvoked: this.itemInvoked.bind(this)
});

The itemRenderer function is responsible for rendering each item. So you have the full control on how every element is produced! In this case you can check if the card is large or not and accordingly you can modify the styles:

itemRenderer: function (itemPromise) {
    return itemPromise.then(function (currentItem, recycled) {
        var template = document.querySelector(".itemTemplate").winControl.renderItem(itemPromise, recycled);
        template.renderComplete = template.renderComplete.then(function (elem) {

            if (currentItem.data.isLarge) {
                var zoomLevel = Windows.Storage.ApplicationData.current.roamingSettings.values["zoomLevel"];

                if (!zoomLevel)
                    return;

                var level = zoomLevel / 100.0;
                elem.querySelector(".item-container").style.width = (480 * level * 2) + "px";
            }
        });
        return template.element;
    })
},

You can note that the code take the local zoom in account to compute the correct size.

However, it is not sufficient. Indeed, the listView must be in “multisize” mode to support different sizes items. To achieve this, it is necessary to add a “groupInfo” parameter:

ui.setOptions(listView, {
    itemDataSource: filtered.dataSource,
    layout: new ui.GridLayout({
        groupHeaderPosition: "top",
        groupInfo: function () {
            var zoomLevel = Windows.Storage.ApplicationData.current.roamingSettings.values["zoomLevel"];

            if (!zoomLevel)
                zoomLevel = 50;

            var level = zoomLevel / 100.0;

            return {
                multiSize: true,
                slotWidth: Math.round(480 * level),
                slotHeight: Math.round(680 * level)
            };
        }
    })
});

It is clearly important to provide a correct cell size (slotWidth et slotHeight) because every item in the listView must have a size proportional to cell size **!**

To be continued

Our next stop will be focused on integrating with Windows 8:

  • Search contract 
  • Share contract
  • FileOpenPicker
  • Live tiles
  • Secondary tiles

How to cook a complete Windows 8 application with HTML5, CSS3 and JavaScript in a week – Day 1

The day 0 was dedicated to creating the home page and setting up the connection with data.

Today, you will focus on creating the missing screens and adding offline support.

The wireframe of the complete application is like this:

The complete solution can be found there: https://www.catuhe.com/msdn/urza/day1.zip 

The complete series can be found here:

 

The expansion screen

The expansion screen is built upon a ListView which is used to display all cards belonging to the expansion.

A first row is used to present filters to the users and a second row is filled with the ListView (cardsList):

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>UrzaGatherer</title>
    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.0.6/css/ui-light.css" rel="stylesheet">
    <script src="//Microsoft.WinJS.0.6/js/base.js"></script>
    <script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
    <!-- UrzaGatherer references -->
    <link href="expansion.css" rel="stylesheet">
    <script src="expansion.js"></script>
</head>
<body>
    <!--Templates-->
    <div class="itemTemplate" data-win-control="WinJS.Binding.Template">
        <div class="item-image-container" data-win-control="UrzaGatherer.Tools.DelayImageLoader" 
data-win-options="{root: 'cards'}"> <img class="item-image" data-win-bind="src: logo; alt: name" src="#" /> </div> <div class="item-overlay"> <h4 class="item-title" data-win-bind="textContent: name"></h4> </div> </div> <!--Content--> <div class="expansion fragment"> <header aria-label="Header content" role="banner"> <button class="win-backbutton" aria-label="Back"></button> <h1 class="titlearea win-type-ellipsis"><span class="pagetitle" id="pageTitle"></span> </h1> </header> <section aria-label="Main content" role="main"> <div class="filters"> <select id="orderSelect"> <option>By number</option> <option>By name</option> </select> <select id="colorFilter"> </select> <select id="authorFilter"> </select> <select id="rarityFilter"> </select> <select id="typeFilter"> </select> <select id="checkFilter"> <option>All</option> <option>Only missing</option> <option>All except missing</option> </select> <input type="search" id="textFilter" /> </div> <div class="cardsList" aria-label="List of cards" data-win-control="WinJS.UI.ListView" data-win-options="{itemTemplate:select('.itemTemplate'), selectionMode:'none',
swipeBehavior:'none', tapBehavior:'invoke', layout:{type:WinJS.UI.GridLayout}}"> </
div> </section> </div> </body> </html>

As always, the html file is used to create the skeleton of the page and the css file is used to style and define the position of all tags. For instance, the layout of the filters is defined using CSS3 Grid (https://msdn.microsoft.com/en-us/library/windows/apps/hh465327.aspx#css3_grid_alignment):

.expansion .filters {
    margin-left: 120px;
    -ms-grid-row: 1;
    -ms-grid-columns: auto auto auto auto auto auto 1fr;
    display: -ms-grid;
}

    .expansion .filters #orderSelect {
        -ms-grid-column: 1;
    }

    .expansion .filters #colorFilter {
        -ms-grid-column: 2;
        margin-left: 10px;
    }

    .expansion .filters #authorFilter {
        -ms-grid-column: 3;
        margin-left: 10px;
    }

    .expansion .filters #rarityFilter {
        -ms-grid-column: 4;
        margin-left: 10px;
    }

    .expansion .filters #typeFilter {
        -ms-grid-column: 5;
        margin-left: 10px;
    }

    .expansion .filters #checkFilter {
        -ms-grid-column: 6;
        margin-left: 10px;
    }

    .expansion .filters #textFilter {
        -ms-grid-column: 7;
        -ms-grid-column-align: end;
        margin-right: 10px;
    }

Handling zoom level

UrzaGatherer must be able to display the cards at different sizes:

For now the size of the items is controlled by a CSS class:

.expansion .cardsList .win-item {
    height: 340px;
    width: 240px;
    color: white;
    background-color: white;
    -ms-grid-columns: 1fr;
    -ms-grid-rows: 1fr;
    display: -ms-grid;
    outline: rgba(0, 0, 0, 0.8) solid 2px;
}

To change the size of the items, you just have to update this class. To do so, you have to find the css file in the DOM, look for the specific css rule (“.expansion .cardsList .win-item“) and update the width and height rules:

var updateSize = function (forceUpdate) {
    var zoomLevel = Windows.Storage.ApplicationData.current.roamingSettings.values["zoomLevel"];

    if (!zoomLevel)
        return;

    var css = UrzaGatherer.Tools.FindCSS("expansion.css");
    var level = zoomLevel / 100.0;

    for (var index = 0; index < css.cssRules.length; index++) {
        if (css.cssRules[index].selectorText == ".expansion .cardsList .win-item") {
            css.cssRules[index].style.width = (480 * level) + "px";
            css.cssRules[index].style.height = (680 * level) + "px";
        }
    }

    if (forceUpdate) {
        var listView = document.querySelector(".cardsList").winControl;
        listView.forceLayout();
    }
}

The current zoom level is a roaming setting (https://msdn.microsoft.com/en-us/library/windows/apps/hh465094.aspx) retrieved with Windows.Storage.ApplicationData.current.roamingSettings.values[“zoomLevel”].

To find the good css, the following code is used:

var findCSS = function (name) {
    for (var index = 0; index < document.styleSheets.length; index++) {
        if (document.styleSheets[index].href.indexOf(name) != -1)
            return document.styleSheets[index];
    }
}

WinJS.Namespace.define("UrzaGatherer.Tools", {
    FindCSS: findCSS
});

The updateSize function is called by Windows every time a specific function is called:

Windows.Storage.ApplicationData.current.signalDataChanged();

 

Adding application settings

To configure the zoom level, the settings pane is obviously the best place. Every time you need a setting that is global to your application, the settings pane is the right place (https://msdn.microsoft.com/en-us/library/windows/apps/Hh780611.aspx):

 

To create a settings pane for your application, you have to define the html structure in a html file (I used the default.html file):

<!--Settings-->
<div id="settingsDiv" data-win-control="WinJS.UI.SettingsFlyout" data-win-options="{width:'narrow'}">
    <div class="win-header">
        <button type="button" onclick="WinJS.UI.SettingsFlyout.show()" class="win-backbutton">
        </button>
        <div class="win-label">Settings</div>
    </div>
    <div class="win-content">
        <h4>Cards zoom level:</h4>
        <input type="range" id="zoomRange" min="20" max="80" value="50" />
    </div>
</div>

As you can notice, I used the WinJS.UI.SettingsFlyout control with a narrow width (I don’t need too much room).

In the default.js file, you just have to add an event listener on the change event of the range control (zoomRange):

// Zoom range
var zoomRange = document.getElementById("zoomRange");

var zoomLevel = Windows.Storage.ApplicationData.current.roamingSettings.values["zoomLevel"];
if (zoomLevel)
    zoomRange.value = zoomLevel;

zoomRange.addEventListener("change", function () {
    Windows.Storage.ApplicationData.current.roamingSettings.values["zoomLevel"] = zoomRange.value;
    Windows.Storage.ApplicationData.current.signalDataChanged();
});

So every time the range is changed, the roaming setting is updated and a signal is sent with signalDataChanged.

Offline mode

All cards are about 150 KB each so you need to provide a way to save locally the downloaded content in order to not rely afterwards on the network.

To do so, you have to change the way pictures are referenced. Indeed, instead of using an url reference to an http resource (such as https://www.mysite.com/card.jpg) you can reference a local resource inside the local folder with this kind of moniker: “ms-appdata:///local/cards/card.jpg“ and in case of failure (the file is not found for instance), you can download the picture and save it locally.

Because you used the DelayImageLoader (see the previous article) to display pictures, you can easily change its behavior to integrate the offline support:

var delayImageLoader = WinJS.Class.define(
        function (element, options) {
            this._element = element || document.createElement("div");
            this.element.winControl = this;

            var downloadImage = function (source, filename, img) {
                var url = options ? UrzaGatherer.Root + "/" + options.root + "/" + source : 
UrzaGatherer.Root +
"/" + source; var xmlRequest = new XMLHttpRequest(); xmlRequest.open("GET", url, true); xmlRequest.responseType = "blob"; xmlRequest.onreadystatechange = function () { if (xmlRequest.readyState === 4) { UrzaGatherer.Stats.DecrementDownloads(); if (xmlRequest.status == 200) { var blob = xmlRequest.response; var input = blob.msDetachStream(); Windows.Storage.ApplicationData.current.localFolder.createFileAsync(filename, Windows.Storage.CreationCollisionOption.replaceExisting).then(
function (file) { file.openAsync(Windows.Storage.FileAccessMode.readWrite).then(
function (output) { Windows.Storage.Streams.RandomAccessStream.copyAsync(input, output).then(
function () { output.flushAsync().then(function () { input.close(); output.close(); img.src = "ms-appdata:///local/" + source; }); }); }); }); } } }; xmlRequest.send(null); } WinJS.Utilities.addClass(this.element, "imageLoader"); WinJS.Utilities.query("img", element).forEach(function (img) { img.addEventListener("load", function () { WinJS.Utilities.addClass(img, "loaded"); }); img.addEventListener("error", function (err) { if (img.src.substring(0, 5) == "http:") { } else { var source = img.src.replace("ms-appdata:///local/", ""); var filename = source.replace("/", "\"); Windows.Storage.ApplicationData.current.localFolder.getFileAsync(filename).then(
function (file) { var url = options ? UrzaGatherer.Root + "/" + options.root + "/" + source :
UrzaGatherer.Root +
"/" + source; img.src = url; }, function () { // Not found UrzaGatherer.Stats.IncrementDownloads(); downloadImage(source, filename, img); }); } }); }); }, { element: { get: function () { return this._element; } }, }); WinJS.Namespace.define("UrzaGatherer.Tools", { DelayImageLoader: delayImageLoader });

By handling the “error” event for each image, you can detect the load failure and launch the download of the picture with a XmlHttpRequest. And once the data is downloaded, you can create the local file and relaunch the image load.

 

Adding filters

Finally, you have to change the way the data is linked to the ListView to take in account filters. To do so, you can use the createFiltered function of the WinJS.Binding.List:

updateLayout: function (element, viewState) {
    updateSize();

    var listView = element.querySelector(".cardsList").winControl;

    var sorted = expansion.cardsList.createSorted(sortFunction);
    var filtered = sorted.createFiltered(filterFunction);

    if (viewState === appViewState.snapped) {
    } else {
        ui.setOptions(listView, {
            itemDataSource: filtered.dataSource,
            layout: new ui.GridLayout({ groupHeaderPosition: "top" })
        });
    }
}

This code uses filterFunction function:

 var filterFunction = function (item) {
     var result = true;

     //Filters
     for (var index = 0; index < filters.length; index++) {
         var filter = document.getElementById(filters[index][0] + "Filter").value;

         if (filter.substring(0, 3) != "All") {
             result &= (item[filters[index][0]] == filter);
         }
     }

     // Text
     var textFilter = document.getElementById("textFilter").value.toLowerCase();

     if (textFilter != "") {
         result &= (item.name.toLowerCase().indexOf(textFilter) != -1
             || item.text.toLowerCase().indexOf(textFilter) != -1
             || item.flavor.toLowerCase().indexOf(textFilter) != -1);
     }

     return result;
 }

Instead of directly refer to properties, this function uses the filters array declared as follow:

var filters = [["color", "colors"], ["author", "authors"], ["rarity", "rarities"], ["type", "types"]];

With this array, it is easy to add new filters. Only the text filter is treated separately because it applies to more than one property.

The filters array is also used to fill the combo boxes:

// Filters
for (var index = 0; index < filters.length; index++) {
    prepareFilter(filters[index][0], filters[index][1], that);
}
var prepareFilter = function (property, plural, that) {
    var filter = document.getElementById(property + "Filter");
    var results = [];

    for (var cardIndex = 0; cardIndex < expansion.cards.length; cardIndex++) {
        var value = expansion.cards[cardIndex][property];

        if (results.indexOf(value) == -1)
            results.push(value);
    }

    results.push("All " + plural);
    var sortedResults = results.sort(function (i0, i1) {
        if (i0 == i1)
            return 0;

        if (i0.substring(0, 3) == "All")
            return -1;

        if (i1.substring(0, 3) == "All")
            return 1;

        if (i0 > i1)
            return 1;

        return -1;
    });

    for (var index = 0; index < sortedResults.length; index++) {
        filter.options[index] = new Option(sortedResults[index]);
    }

    filter.addEventListener("change", function () {
        that.updateLayout(document, appView.value);
    });
};

 

The card screen

The card screen is pretty simple:

The card is linked to the UI with the following code (direct mapping):

(function () {
    "use strict";

    var appView = Windows.UI.ViewManagement.ApplicationView;
    var appViewState = Windows.UI.ViewManagement.ApplicationViewState;
    var nav = WinJS.Navigation;
    var ui = WinJS.UI;
    var utils = WinJS.Utilities;

    var card;

    ui.Pages.define("/pages/card/card.html", {
        ready: function (element, options) {
            card = options.card;

            document.getElementById("pageTitle").innerText = card.name;

            document.querySelector("#picture").src = card.logo;
            document.querySelector(".item-number").innerText = card.number + " / " + 
card.expansion.cards.length; document.querySelector(
".item-type").innerText = card.type; document.querySelector(".item-color").innerText = card.color; document.querySelector(".item-power").innerText = card.power; document.querySelector(".item-text").innerText = card.text; document.querySelector(".item-flavor").innerText = card.flavor; document.querySelector(".item-author").innerText = card.author; document.querySelector("#expansion-picture").src = card.expansion.banner; this.updateLayout(element, appView.value); }, updateLayout: function (element, viewState) { if (viewState === appViewState.snapped) { } else { } } }); })();

Fairly simple, isn’t it ? Sourire

 

To be continued

The next part will introduce:

  • Localization
  • Snapped views
  • More settings
  • Adaptation to form factors

How to cook a complete Windows 8 application with HTML5, CSS3 and JavaScript in a week – Day 0

The goal of these articles is to provide a pragmatic recipe to create a complete Windows 8 application from scratch.

The application I use as sample is called UrzaGatherer and it is used to help Magic The Gathering collectors to handle their cards collection.

image_thumb4

UrzaGatherer was originally developed using WPF 4.0 (https://urzagatherer.codeplex.com/) but I decided to use HTML5, CSS3 and JavaScript for developing the Windows 8 version.

You will need the following to start creating the application:

The complete solution can be find there: https://www.catuhe.com/msdn/urza/day0.zip 

The complete series can be found here:

Creating the project

First of all, you have to create a blank project (You can obviously create a more prepared project like the ‘Grid application’ but the goal here is to understand all things are tied up) using the File/New Project menu:

image_thumb8

The project is created with only required files:

image_thumb11

Creating required assets

The package.appxmanifest is the file that describes your application to Windows 8. It contains especially the description of the application alongside with the associated logos:

image_thumb14

I love adding logos and colors to my application because it is a simple way to polish it.

The splash screen for example is really important because it is the first thing a user see of your application (and you should know that the very first contact is really significant):

image_thumb17

This is sometimes the harder part of the development because developers are not often designers Sourire

Structuring the project

This part is really dependent on your way of thinking. Personally, I chose to create this project layout:

image_thumb20

  • A folder for my assets (/images)
  • A folder for my JavaScript code which is not related to pages (/js)
  • A folder for the pages (/pages)
  • A folder for every page (/pages/xxx) where I create css, js and html files (home.js, home.html, home.css)
  • A root page called default.html (with an associated .css and .js)

 

Connecting to data

Once the assets and the project structure are done, you can add a data.js file in the js folder to handle all that is related to data.

For UrzaGatherer, the data is composed of:

  • a all.json file which describes all the supported cards
  • a list of cards pictures
  • A list of logo for each expansion (cards belong to an expansion which in turn belong to a block)
  • A list of logo for each block

So starting with you empty data.js file, you have to created  an automatic anonymous function:

(function () { })();

Inside this function you can connect to your data. For UrzaGatherer, the data is stored in a json file which is too big to be downloaded every time (~ 8MB) so you have to load it only once and save it locally:

(function () {

    var blocks = new WinJS.Binding.List();
    var expansions = new WinJS.Binding.List();
    var root = "https://urzagatherer.blob.core.windows.net";

    var processBlocks = function (data) {
        var result = JSON.parse(data);

        for (var blockIndex = 0; blockIndex < result.length; blockIndex++) {
            var block = result[blockIndex];

            block.logo = root + "/blocks/" + block.name.replace(":", "_") + ".png";
            blocks.push(block);

            var sortedExpansions = block.expansions.sort(expansionSorter);

            for (var expansionIndex = 0; expansionIndex < sortedExpansions.length; expansionIndex++) {
                var expansion = sortedExpansions[expansionIndex];
                expansion.block = block;
                expansion.logo = root + "/logos/" + expansion.name.replace(":", "_") + ".png";
                expansions.push(expansion);
            }
        }
    }

    var getBlocksDistant = function (onload) {
        var localFolder = Windows.Storage.ApplicationData.current.localFolder;
        var requestStr = root + "/cards/all.json";

        WinJS.xhr({ url: requestStr }).then(function (request) {
            processBlocks(request.responseText);

            localFolder.createFileAsync("all.json", 
Windows.Storage.CreationCollisionOption.replaceExisting).then(
function (file) { Windows.Storage.FileIO.writeTextAsync(file, request.responseText); }); if (onload) onload(); }); } var getBlocks = function (onload) { var localFolder = Windows.Storage.ApplicationData.current.localFolder; localFolder.getFileAsync("all.json").done(function (file) { return Windows.Storage.FileIO.readTextAsync(file).then(function (data) { processBlocks(data); if (onload) onload(); }); }, function () { getBlocksDistant(onload); }); } var expansionSorter = function (i0, i1) { if (i0.orderInBlock > i1.orderInBlock) return 1; else if (i0.orderInBlock < i1.orderInBlock) return -1; return 0; }; WinJS.Namespace.define("UrzaGatherer", { Blocks: blocks, Expansions: expansions, Init: getBlocks }); })();

Using the WinJS.Namespace.define, you can declare a global object (called UrzaGatherer) available everywhere in your code.

The Init function starts by trying to load data locally and if it fails, it will download it using WinJS.xhr (https://msdn.microsoft.com/en-us/library/windows/apps/br229787.aspx). Init also takes a function in parameter to signal the availability of the data. I use this function to hide my wait ring (a progress bar in “ring” mode).

Preparing the landing page

The default.html page is the landing page i.e. the page where the user lands after launching the application. This page is responsible for creating the navigation system:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>UrzaGatherer</title>

    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.0.6/css/ui-light.css" rel="stylesheet">
    <script src="//Microsoft.WinJS.0.6/js/base.js"></script>
    <script src="//Microsoft.WinJS.0.6/js/ui.js"></script>

    <!-- UrzaGatherer references -->
    <link href="/default.css" rel="stylesheet">
    <script src="/js/data.js"></script>
    <script src="/js/tools.js"></script>
    <script src="/js/navigator.js"></script>
    <script src="/default.js"></script>
</head>
<body>
    <div id="contenthost" data-win-control="UrzaGatherer.PageControlNavigator" 
data-win-options="{home: '/pages/home/home.html'}"></div> </body> </html>

The page is pretty simple: it references the WinJS files and then load the default stylesheets and the JavaScript code.

It only contains one div which is the host where sub-pages will be loaded. This is an important point for understanding how the navigation works when you use HTML5/JavaScript for Windows 8. Indeed, the pages are not loaded as root pages but as children pages of the default.html page.

To handle this functionality, you have to create the navigator.js page (You can copy it from any project templates in Visual Studio 11 such as ‘Grid Application’):

(function () {
    "use strict";

    var appView = Windows.UI.ViewManagement.ApplicationView;
    var displayProps = Windows.Graphics.Display.DisplayProperties;
    var nav = WinJS.Navigation;
    var ui = WinJS.UI;
    var utils = WinJS.Utilities;

    WinJS.Namespace.define("UrzaGatherer", {
        PageControlNavigator: WinJS.Class.define(
        // Define the constructor function for the PageControlNavigator.
            function (element, options) {
                this.element = element || document.createElement("div");
                this.element.appendChild(this._createPageElement());

                this.home = options.home;

                nav.onnavigated = this._navigated.bind(this);
                appView.getForCurrentView().onviewstatechanged = this._viewstatechanged.bind(this);

                document.body.onkeyup = this._keyupHandler.bind(this);
                document.body.onkeypress = this._keypressHandler.bind(this);
                nav.navigate(this.home);
            }, {
                // This function creates a new container for each page.
                _createPageElement: function () {
                    var element = document.createElement("div");
                    element.style.width = "100%";
                    element.style.height = "100%";
                    return element;
                },

                // This function responds to keypresses to only navigate when
                // the backspace key is not used elsewhere.
                _keypressHandler: function (eventObject) {
                    if (eventObject.key === "Backspace")
                        nav.back();
                },

                // This function responds to keyup to enable keyboard navigation.
                _keyupHandler: function (eventObject) {
                    if ((eventObject.key === "Left" && eventObject.altKey) 
|| (eventObject.key ===
"BrowserBack")) { nav.back(); } else if ((eventObject.key === "Right" && eventObject.altKey)
|| (eventObject.key ===
"BrowserForward")) { nav.forward(); } }, // This function responds to navigation by adding new pages // to the DOM. _navigated: function (eventObject) { var newElement = this._createPageElement(); var parentedComplete; var parented = new WinJS.Promise(function (c) { parentedComplete = c; }); var that = this; WinJS.UI.Pages.render(eventObject.detail.location, newElement,
eventObject.detail.state, parented). then(
function (control) { that.element.appendChild(newElement); that.element.removeChild(that.pageElement); parentedComplete(); document.body.focus(); that.navigated(); }); }, // This function is called by _viewstatechanged in order to // pass events to the page. _updateLayout: { get: function () { return (this.pageControl && this.pageControl.updateLayout) || function () { }; } }, _viewstatechanged: function (eventObject) { (this._updateLayout.bind(this.pageControl))(this.pageElement, eventObject.viewState); }, // This function updates application controls once a navigation // has completed. navigated: function () { // Do application specific on-navigated work here var backButton = this.pageElement.querySelector("header[role=banner] .win-backbutton"); if (backButton) { backButton.onclick = function () { nav.back(); }; if (nav.canGoBack) { backButton.removeAttribute("disabled"); } else { backButton.setAttribute("disabled", "disabled"); } } }, // This is the PageControlNavigator object. pageControl: { get: function () { return this.pageElement && this.pageElement.winControl; } }, // This is the root element of the current page. pageElement: { get: function () { return this.element.firstElementChild; } } } ), // This function navigates to the home page which is defined when the // control is created. navigateHome: function () { var home = document.querySelector("#contenthost").winControl.home; var loc = nav.location; if (loc !== "" && loc !== home) { nav.navigate(home); } }, }); })();
 

As you can see, the PageControlNavigator class is a control that load a page and add it as a child element after removing the previous page. You have to well understand this point because it implies that all loaded css and scripts remain active and exist in a unique and single global page (https://msdn.microsoft.com/en-us/library/windows/apps/hh452768.aspx).

Applying styles to the page

The default.css stylesheet is the root stylesheet and as you know now, it is applied to every loaded page. This file is responsible for setting the global structure such as:

  • A potential background
  • A layout with a room for the banner (title and back button) and the content

I also use it to store global style used everywhere in my application such as the hidden class (used to hide elements):

html {
    cursor: default;
}

body {
    background-image: url('images/background.jpg');
    background-size: 100% 100%
}

#contenthost {
    height: 100%;
    width: 100%;
}

.fragment {
    /* Define a grid with rows for a banner and a body */
    -ms-grid-columns: 1fr;
    -ms-grid-rows: 133px 1fr 0px;
    display: -ms-grid;
    height: 100%;
    width: 100%;
}

.fragment header[role=banner] {
    /* Define a grid with columns for the back button and page title. */
    -ms-grid-columns: 120px 1fr;
    -ms-grid-rows: 1fr;
    display: -ms-grid;
}

.fragment header[role=banner] .win-backbutton {
    margin-left: 39px;
    margin-top: 59px;
}

.fragment header[role=banner] .titlearea {
    -ms-grid-column: 2;
    margin-top: 37px;
}

.fragment header[role=banner] .titlearea .pagetitle {
    width: calc(100% - 20px);
}

.fragment section[role=main] {
    -ms-grid-row: 2;
    height: 100%;
    width: 100%;
}

.hidden {
    display: none;
}
 
 
 

You will notice that I use display:-ms-grid everywhere I can because I think it is really easy to construct the layout and the alignment using the CSS3 grid system (https://msdn.microsoft.com/en-us/library/windows/apps/hh465327.aspx).

For the background, you can obviously ask a designer to help you but you can also use a simple graphic tool to create thin gradient like this one:

image_thumb[3]

 

Creating the home screen

The first visible screen is the home screen where I want to display the available blocks with theirs expansions.

The base version of the page is like the following :

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>UrzaGatherer</title>
    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.0.6/css/ui-light.css" rel="stylesheet">
    <script src="//Microsoft.WinJS.0.6/js/base.js"></script>
    <script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
    <!-- UrzaGatherer references -->
    <link href="home.css" rel="stylesheet">
    <script src="home.js"></script>
</head>
<body>
    <!--Content-->
    <div class="home fragment">
        <header aria-label="Header content" role="banner">
            <button class="win-backbutton" aria-label="Back" disabled></button>
            <h1 class="titlearea win-type-ellipsis"><span class="pagetitle">UrzaGatherer</span>
            </h1>
        </header>
        <section aria-label="Main content" role="main">

        </section>
    </div>
</body>
</html>

You can note the banner (header) and a section to put the content.

To do so, I used a WinJS.UI.ListView. This control can be used to display a grouped list of values (here it displays a list of expansions grouped by blocks):

 
<div class="blocksList" aria-label="List of blocks" data-win-control="WinJS.UI.ListView"
    data-win-options="{itemTemplate:select('.itemTemplate'), groupHeaderTemplate:select('.headerTemplate')
                       , selectionMode:'none', swipeBehavior:'none', tapBehavior:'invoke',
                        layout:{type:WinJS.UI.GridLayout}}">
</div>

The control references two templates (itemTemplate and headerTemplate) which define how to render every item and the headers:

<div class="headerTemplate" data-win-control="WinJS.Binding.Template">
    <div class="header-title" data-win-bind="innerText: name">
    </div>
    <img class="item-image" data-win-bind="src: logo" src="#" />
</div>
<div class="itemTemplate" data-win-control="WinJS.Binding.Template">
    <img class="item-image" data-win-bind="src: logo" src="#" />
    <div class="item-overlay">
        <h4 class="item-title" data-win-bind="textContent: name"></h4>
    </div>
</div>

As I said before, I always try to create my layout using CSS3 grid. For example, here are the styles for the items in the list:

.home .blocksList .win-item {
    -ms-grid-columns: 1fr;
    -ms-grid-rows: 1fr 30px;
    display: -ms-grid;
    height: 130px;
    width: 260px;
    background: white;
    outline: rgba(0, 0, 0, 0.8) solid 2px;
}

    .home .blocksList .win-item:hover {
        outline: #5F38FF solid 2px;
    }

    .home .blocksList .win-item .item-image-container {
        -ms-grid-columns: 1fr;
        -ms-grid-rows: 1fr;
        -ms-grid-row: 1;
        display: -ms-grid;
        padding: 4px;
        -ms-transition: opacity ease-out 0.2s, -ms-transform ease-out 0.2s;
        -ms-transform: scale(1.0, 1.0);
    }

        .home .blocksList .win-item .item-image-container:hover {
            opacity: 0.9;
            -ms-transform: scale(1.1, 1.1);
        }

    .home .blocksList .win-item .item-image {
        -ms-grid-row: 1;
        -ms-grid-column-align: center;
        -ms-grid-row-align: center;
        max-height: 90px;
    }

    .home .blocksList .win-item .item-overlay {
        -ms-grid-row: 2;
        padding: 3px 15px 2px;
        background-color: rgba(0, 0, 0, 0.8);
    }

Thanks to CSS3 transitions (https://msdn.microsoft.com/en-us/library/windows/apps/Hh781227.aspx), it is also really simple to handle the “hover” state. Please note the use of “.home” as a prefix to only apply these styles to the _home.html_page (because of the single page navigation system).

The blocksList control is then filled with the data using the createGrouped function of the WinJS.Binding.List class:

var groupDataSource = UrzaGatherer.Expansions.createGrouped(this.groupKeySelector,
                                                            this.groupDataSelector, this.groupCompare);

ui.setOptions(listView, {
    itemDataSource: groupDataSource.dataSource,
    groupDataSource: groupDataSource.groups.dataSource,
    layout: new ui.GridLayout({ groupHeaderPosition: "top" })
});

One important point here is the groupKeySelector function. This function is used to create a key for every group. This key is used to group items and will also be used when you will add a SemanticZoom control:

groupKeySelector: function (item) { return item.block.name + "*" + item.block.index; },

Please note that you MUST return a string and not a number !

The groupCompare function receives the key and must sort them:

groupCompare: function (i0, i1) { 
var index0 = parseInt(i0.split("*")[1]);
var index1 = parseInt(i1.split("*")[1]);

return index1 - index0;
}

Adding a custom control for pictures

The problem with the pictures is that it can be long to download them and when the download is complete they appear without animation and it is like a bad popping effect. So I decided to create a custom control that will add a cool animation to make pictures appear softly.

To declare a custom control it is rather simple (using the WinJS.Class.define function):

(function () {

    var delayImageLoader = WinJS.Class.define(
            function (element, options) {
                this._element = element || document.createElement("div");
                this.element.winControl = this;
                WinJS.Utilities.addClass(this.element, "imageLoader");
                WinJS.Utilities.query("img", element).forEach(function (img) {
                    img.addEventListener("load", function () {
                        WinJS.Utilities.addClass(img, "loaded");
                    });
                });
            },
            {


                element: {
                    get: function () { return this._element; }
                },
            });

    WinJS.Namespace.define("UrzaGatherer.Tools", {
        DelayImageLoader: delayImageLoader
    });
})();

As you can see, the control looks for children images and adds an event listener for the “load” event. All the magic is in the CSS in fact because the control just add the imageLoader class at beginning and the loaded class and the end.

These two classes are defined in the default.css file:

.imageLoader img {
opacity: ;
-ms-transform: scale(0.8, 0.8);


.imageLoader img.loaded {
    opacity: 1;
    -ms-transition: opacity ease-out 0.2s, -ms-transform ease-out 0.2s;
    -ms-transform: scale(1, 1);
}

Using CSS3 transitions, the picture will softly pop when the download is finished Sourire

 

Adding a semantic zoom

Finally, I added a semantic zoom (https://msdn.microsoft.com/en-us/library/windows/apps/hh465492.aspx) in the home page to allow users to quickly jump to a block:

image_thumb[6]

To do so, you have to embed the initial ListView with another one (the zoomed ListView) inside a WinJS.UI.SemanticZoom control

<div class="zoomControl" data-win-control="WinJS.UI.SemanticZoom">
    <div class="blocksList" aria-label="List of blocks" data-win-control="WinJS.UI.ListView"
     data-win-options="{itemTemplate:select('.itemTemplate'), groupHeaderTemplate:select('.headerTemplate'), 
                            selectionMode:'none', swipeBehavior:'none', tapBehavior:'invoke',  
                            layout:{type:WinJS.UI.GridLayout}}">
    </div>
    <div class="zoomedList" aria-label="Zoomed List of blocks" data-win-control="WinJS.UI.ListView"
     data-win-options="{itemTemplate:select('.semanticZoomTemplate'), selectionMode:'none', 
                         swipeBehavior:'none', tapBehavior:'invoke',  layout:{type:WinJS.UI.GridLayout}}">
    </div>
</div>

To synchronize the two ListViews, you just have to use the same datasource for the group in the first list and the items in the second:

var groupDataSource = UrzaGatherer.Expansions.createGrouped(this.groupKeySelector,
                                                            this.groupDataSelector, this.groupCompare);

ui.setOptions(listView, {
    itemDataSource: groupDataSource.dataSource,
    groupDataSource: groupDataSource.groups.dataSource,
    layout: new ui.GridLayout({ groupHeaderPosition: "top" })
});

ui.setOptions(zoomedListView, {
    itemDataSource: groupDataSource.groups.dataSource
});
 

To be continued

The next article will introduce :

  • The expansion page
  • The card page
  • Settings
  • Offline mode
 
 
 

Unleash the power of HTML 5 Canvas for gaming

HTML 5 browsers and HTML 5 for Windows 8 Metro are now serious candidates for developing modern games.   

With the canvas, you have access to an hardware accelerated space where you can draw the content of your game and with some tips and tricks you will be able to achieve a splendid 60 frame per second render.

This notion of fluidity is really important in games because the smoother the game is the better the feeling of the player is.

The goal of this article is to give you some keys on how to get the maximum power from HTML 5 canvas.  

I will use a sample in the following chapters to help me demonstrate the concepts I introduce. The sample is a 2D tunnel effect I wrote for the Coding4Fun session I presented for the TechDays 2012 in France (https://video.fr.msn.com/watch/video/techdays-2012-session-technique-coding4fun/zqy7cm8l).

This effect is mainly inspired by some Commodore AMIGA code I wrote when I was a young demomaker back in the 80’s Sourire.


Now, it only uses canvas and Javascript (where original code was only based on 68000 assembler):



The complete code is available there: https://www.catuhe.com/msdn/canvas/tunnel.zip

The aim of this article is not to explain how the tunnel is developed but how you can start from a given code and optimize it to achieve real time performance. 

Using an off-screen canvas to read picture data

The first point I want to talk about is how you can use a canvas to help you read picture data. Indeed, on every game, you need graphics for your sprites or your background. The canvas has a really helpful method to draw an image: drawImage. This function can be used to draw a sprite in the canvas because you can define a source and a destination rectangle.

But sometimes it is not enough. For example, it is not sufficient when you want to apply some effects on the source image. Or when the source image is not a simple bitmap but a more complex resource for your game (for instance, a map where you need to read data from).

In these cases, you need to access internal data of the picture. But the Image tag do not have a way to read its content. And this is where the canvas can help you!

Indeed, every time you need to read the content of a picture, you can use an off-screen canvas. The main idea here is to load a picture and when the picture is loaded, you just have to render it in a canvas (not included in the DOM). You can then get every pixel of the source image by reading pixel of the canvas (which is really simple).

The code for this technique is the following (used in the 2D tunnel effect to read the tunnel’s texture data):

var loadTexture = function (name, then) {
    var texture = new Image();
    var textureData;
    var textureWidth;
    var textureHeight;
    var result = {};

    // on load
    texture.addEventListener('load', function () {
        var textureCanvas = document.createElement('canvas'); // off-screen canvas

        // Setting the canvas to right size
        textureCanvas.width = this.width; //<-- "this" is the image
        textureCanvas.height = this.height;

        result.width = this.width;
        result.height = this.height;

        var textureContext = textureCanvas.getContext('2d');
        textureContext.drawImage(this, 0, 0);

        result.data = textureContext.getImageData(0, 0, this.width, this.height).data;

        then();
    }, false);

    // Loading
    texture.src = name;

    return result;
};

To use this code, you have to take in account that the load of the texture is asynchronous and so you have to use the then parameter to transmit a function to continue your code:

// Texture
var texture = loadTexture("soft.png", function () {
    // Launching the render
    QueueNewFrame();
});

      

Using the hardware scaling feature

Modern browsers and Windows 8 support hardware accelerated canvas. It means that, for instance, you can use the GPU to rescale the content of the canvas.

In the case of the 2D tunnel effect, the algorithm requires to process every pixel of the canvas. So for instance for a 1024×768 canvas you have to process 786432 pixels. And to be fluid you have to do that 60 times per second which corresponds to 47185920 pixels per second !

It is obvious that every solution that helps you reducing the pixel count will drastically improve the overall performance.

And once again, the canvas has a solution! The following code shows you how to use the hardware acceleration to rescale the internal working buffer of a canvas to the external size of the DOM object:

// Setting hardware scaling
canvas.width = 300;
canvas.style.width = window.innerWidth + 'px';
canvas.height = 200;
canvas.style.height = window.innerHeight + 'px';

It is worth noting the difference between the size of the DOM objet (canvas.style.width and canvas.style.height) and the size of the working buffer of the canvas (canvas.width and canvas.height).

When there is a difference between these two sizes, hardware is used to scale the working buffer and in our case it is a excellent thing: we can work on a smaller resolution and let the GPU rescales the result to fit the DOM object (with a beautiful and free filter to blur the result).

In this case, the render is done in 300×200 and the GPU will scale it to the size of your window.

This feature is widely supported across all modern browsers so you can count on it.

Optimize your rendering loop

When you are writing a game, you must have a rendering loop where you draw all the components of your game (background, sprites, score, etc..). This loop is the backbone of your code and must be over-optimized to be sure that your game is fast and fluid.

RequestAnimationFrame

One interesting feature introduced by HTML 5 is the function window.requestAnimationFrame. Instead of using window.setInterval to create a timer that calls your rendering loop every (1000/16) milliseconds (to achieve a good 60 fps), you can delegate this responsibility to the browser with requestAnimationFrame. Calling this method indicates that you want to be called by the browser as soon as possible to update graphics related stuff.

The browser will include your request inside its own rendering schedule and will synchronize you with its rendering and animations code (CSS, transitions, etc…). This solution is also interesting because your code won’t be called if the window is not displayed (minimized, fully occluded, etc.)

This can help performance because the browser can optimize concurrent rendering (for example if your rendering loop is too slow) and by the way produce more fluid animations.

The code is pretty obvious (please note the usage of the vendor specific prefixes):

var intervalID = -1;
var QueueNewFrame = function () {
    if (window.requestAnimationFrame)
        window.requestAnimationFrame(renderingLoop);
    else if (window.msRequestAnimationFrame)
        window.msRequestAnimationFrame(renderingLoop);
    else if (window.webkitRequestAnimationFrame)
        window.webkitRequestAnimationFrame(renderingLoop);
    else if (window.mozRequestAnimationFrame)
        window.mozRequestAnimationFrame(renderingLoop);
    else if (window.oRequestAnimationFrame)
        window.oRequestAnimationFrame(renderingLoop);
    else {
        QueueNewFrame = function () {
        };
        intervalID = window.setInterval(renderingLoop, 16.7);
    }
};

To use this function, you just have to call it at the end of your rendering loop to register the next frame:

var renderingLoop = function () {
    ...

QueueNewFrame(); };

      

Accessing the DOM (Document Object Model)

To optimize your rendering loop, you have to follow at least one golden rule: DO NOT ACCESS THE DOM. Even if modern browsers are optimized on this point, reading DOM object properties is still to slow for a rendering loop.  

For example, in my code, I used the Internet Explorer 10 profiler (available in the F12 developer bar) and the result is obvious:

As you can see accessing the canvas width and height takes a lot of time in my rendering loop!

The initial code was:

var renderingLoop = function () {


    for (var y = -canvas.height / 2; y < canvas.height / 2; y++) {
        for (var x = -canvas.width / 2; x < canvas.width / 2; x++) {

            ...

        }
    }
};

You can remove the canvas.width and canvas.height properties with 2 variables previously filled with the good value:

var renderingLoop = function () {

    var index = 0;
    for (var y = -canvasHeight / 2; y < canvasHeight / 2; y++) {
        for (var x = -canvasWidth / 2; x < canvasWidth / 2; x++) {
            ...
        }
    }
};

Simple, isn’t ? It may be sometimes hard to realize but believe me it is worth trying!

Pre-compute

According to the profiler, the Math.atan2 function is a bit slow. In fact, this operation is not hardly coded inside the CPU so the JavaScript runtime must add some code to compute the result.

In a general way, if you can pre-compute some long running code it is always a good idea. Here, before running my rendering loop, I compute the result of Math.atan2:

// precompute arctangent
var atans = [];

var index = 0;
for (var y = -canvasHeight / 2; y < canvasHeight / 2; y++) {
    for (var x = -canvasWidth / 2; x < canvasWidth / 2; x++) {
        atans[index++] = Math.atan2(y, x) / Math.PI;
    }
}

The atans array can then be used inside the rendering loop to clearly boost the performance.

Avoid using Math.round, Math.floor and parseInt

The last relevant point is the usage of parseInt:

When you use a canvas, you need to reference pixels with integer coordinates (x and y). Indeed, all your computation are made using floating point numbers and you need to convert them to integer at the end of the day.

JavaScript provides Math.round, Math.floor or even parseInt to convert number to integer. But this function makes some extra works (for instance to check ranges or to check if the value is effectively a number. parseInt even first converts its parameter to string!). And inside my rendering loop, I need to have a quick way to perform this conversion.

Remembering my old assembler code, I used a small trick: Instead of using parseInt, you just have to shift your number to the right with a value of 0. The runtime will move the floating value from a floating register to an integer register and use an hardware conversion. Shifting this value to the right with a 0 value will let it unchanged and so you can get back your value casted to integer.

The original code was:

u = parseInt((u < 0) ? texture.width + (u % texture.width) : (u >= texture.width) ? u % texture.width : u);

And the new one is the following:

u = ((u < 0) ? texture.width + (u % texture.width) : (u >= texture.width) ? u % texture.width : u) >> 0;

Of course this solution requires that you are sure the value is a correct number Sourire

Final result

Applying all the optimizations gives you the following report:

 

You can see that now the code seems to be well optimized with only essential functions.

Starting from the original render of the tunnel (without any optimization):


And after applying all of these optimizations:


We can resume the impact of each optimization with the following chart which gives you the framerate measured on my own computer

Going further

With these key points in mind, you are ready to produce real time fast and fluid games for modern browsers or Windows 8!

KISS : Use Kinect for Windows SDK to protect your privacy

Fotolia_27381572_S

I have the incredible chance to work with a very fun and motivating team. But one drawback of this situation is the risk to send unwanted emails when you forget to lock your computer when you go out.

And they can be really creative with this so French and sophisticated humor when they use your Outlook. So to protect myself I decided to write a small program using one of my favorite technology : Kinect for Windows SDK.

The main goal of KISS (which stands for Kinect Intelligent Security System) is to track the user in front of the sensor and detect when he goes out (to lock the computer for example).

You will discover how to develop such a program during this article.

The final solution is available to download there: https://www.catuhe.com/msdn/kiss.zip

 

Initializing the sensor

First of all, you have to reference the Kinect for Windows SDK assembly (C:Program FilesMicrosoft SDKsKinectv1.0AssembliesMicrosoft.Kinect.dll) and instantiate a new KinectSensor. To do so, the SDK helps you with a static helper class called KinectSensor which contains a list of already detected sensors : KinectSensor.KinectSensors. It also provides an event KinectSensor.KinectSensors.StatusChanged which will be raised when something happens to one of the attached Kinect sensors.






  1. public KinectIntelligentSecuritySystem()


  2. {


  3. try


  4. {


  5. //listen to any status change for Kinects


  6. KinectSensor.KinectSensors.StatusChanged += Kinects_StatusChanged;


  7.  


  8. //loop through all the Kinects attached to this PC, and start the first that is connected without an error.


  9. foreach (KinectSensor kinect in KinectSensor.KinectSensors)


  10. {


  11. if (kinect.Status == KinectStatus.Connected)


  12. {


  13. kinectSensor = kinect;


  14. break;


  15. }


  16. }


  17.  


  18. if (kinectSensor == null)


  19. MessageBox.Show(“No Kinect found”);


  20. else


  21. Initialize();


  22.  


  23. }


  24. catch (Exception ex)


  25. {


  26. MessageBox.Show(ex.Message);


  27. }


  28. }




 






  1. void Kinects_StatusChanged(object sender, StatusChangedEventArgs e)


  2. {


  3. switch (e.Status)


  4. {


  5. case KinectStatus.Connected:


  6. if (kinectSensor == null)


  7. {


  8. kinectSensor = e.Sensor;


  9. Initialize();


  10. }


  11. break;


  12. case KinectStatus.Disconnected:


  13. if (kinectSensor == e.Sensor)


  14. {


  15. Clean();


  16. MessageBox.Show(“Kinect was disconnected”);


  17. }


  18. break;


  19. case KinectStatus.NotPowered:


  20. if (kinectSensor == e.Sensor)


  21. {


  22. Clean();


  23. MessageBox.Show(“Kinect is no more powered”);


  24. }


  25. break;


  26. }


  27. }




 

As you can see, it is pretty simple as you just have to track the StatusChanged event and call Clean or Initialize accordingly:






  1. private void Initialize()


  2. {


  3. if (kinectSensor == null)


  4. return;


  5.  


  6. kinectSensor.Start();


  7.  


  8. kinectSensor.DepthStream.Enable(DepthImageFormat.Resolution80x60Fps30);


  9. kinectSensor.DepthFrameReady += kinectSensor_DepthFrameReady;


  10. kinectSensor.DepthStream.Range = DepthRange.Near;


  11. }


  12.  


  13. void Clean()


  14. {


  15. kinectSensor.Stop();


  16. kinectSensor = null;


  17. }




A thing worth noting is the activation of the near mode:






  1. kinectSensor.DepthStream.Range = DepthRange.Near;




With this mode activated, skeletons tracking is disabled but the depth values can be retrieved from a near distance (about 40cm).

 

Displaying the depth stream

Now, the sensor is instantiated and ready to use. You have then to connect to the depth stream.

When you develop with Kinect, it is important to provide a visual feedback of what the sensor sees. An event is already handled in the Initialize method to do get the depth frames:






  1. kinectSensor.DepthFrameReady += kinectSensor_DepthFrameReady;




So you have to create a method that can build a bitmap from the depth stream:






  1. void DisplayData(DepthImageFrame frame)


  2. {


  3. for (int i16 = 0, i32 = 0; i16 < data.Length && i32 < data32.Length; i16++, i32++)


  4. {


  5. int realDepth = (data[i16] >> 3);


  6. byte intensity = (byte)(255 – (255 realDepth / 3000.0f));


  7.  


  8. data32[i32] = (intensity / 2) + ((intensity / 2) << 16) + ((intensity / 2) << 8) + (255 << 24);


  9. }


  10.  


  11. if (DepthBitmap == null)


  12. {


  13. DepthBitmap = new WriteableBitmap(frame.Width, frame.Height, 96, 96, PixelFormats.Bgra32, null);


  14. }


  15.  


  16. DepthBitmap.Lock();


  17.  


  18. int stride = DepthBitmap.PixelWidth DepthBitmap.Format.BitsPerPixel / 8;


  19. Int32Rect dirtyRect = new Int32Rect(0, 0, DepthBitmap.PixelWidth, DepthBitmap.PixelHeight);


  20. DepthBitmap.WritePixels(dirtyRect, data32, stride, 0);


  21.  


  22. DepthBitmap.AddDirtyRect(dirtyRect);


  23. DepthBitmap.Unlock();


  24.  


  25. if (PropertyChanged != null)


  26. {


  27. PropertyChanged(this, new PropertyChangedEventArgs(“DepthBitmap”));


  28. }


  29. }




DisplayData uses a DepthFrame (provided by the event) to fill a WriteableBitmap with grayed values (from white (near) to black (far)).

The KinectIntelligentSecuritySystem class implements INotifyPropertyChanged and exposes a property called DepthBitmap. This property is used by the main program to fill a WPF Image control:






  1. <Window x:Class=”KISS.MainWindow”


  2. xmlns=”https://schemas.microsoft.com/winfx/2006/xaml/presentation"


  3. xmlns:x=”https://schemas.microsoft.com/winfx/2006/xaml"


  4. Title=”Kinect Intelligent Security System” Height=”350” Width=”525”>


  5. <Grid>


  6. <Image x:Name=”depthImage” Source=”{Binding DepthBitmap}“ />


  7. </Grid>


  8. </Window>




 






  1. depthImage.DataContext = kiss;




So every time DisplayData computes a new image, the PropertyChanged event is raised and the WPF Image control updates itself.

This code is directly inspired by the Kinect Toolbox that you can grab there : https://kinecttoolbox.codeplex.com

 

Detecting user and locking the computer

Finally the main point is here: using the depth stream to compute the average depth of what is in front of the sensor and to detect any big variation of this average:






  1. // Computing depth average


  2. var avg = data.Average(pixel => pixel);


  3.  


  4. previousValues.Add(avg);


  5.  


  6. var currentAvg = previousValues.Average(value => value);


  7.  


  8. if (previousValues.Count > 60)


  9. previousValues.RemoveAt(0);


  10.  


  11. if (previousValues.Count == 60 && (Math.Abs(currentAvg – avg) > 1500))


  12. {


  13. if (OnMovement != null)


  14. OnMovement();


  15.  


  16. previousValues.Clear();


  17. }




You have to save a given number (60 in this example) of previous computed average depths in the previousValues list. With this list, every time a new frame is available, you have to compare the current average with the average of values stored in the list and if the difference is bigger than a given threshold, an event is raised.

In response to this event, I choose to lock my computer with this simple interop code:






  1. readonly KinectIntelligentSecuritySystem kiss = new KinectIntelligentSecuritySystem();


  2.  


  3. [DllImport(“user32.dll”)]


  4. public static extern void LockWorkStation();


  5.  


  6. public MainWindow()


  7. {


  8. InitializeComponent();


  9. kiss.OnMovement += kiss_OnMovement;


  10.  


  11. depthImage.DataContext = kiss;


  12. }


  13.  


  14. void kiss_OnMovement()


  15. {


  16. LockWorkStation();


  17. }




And voila! You are now safe to get out without locking your computer because KISS watches over you Rire.

The complete code for KISS is the following:






  1. using System;


  2. using System.Linq;


  3. using System.Windows;


  4. using System.Windows.Media;


  5. using System.Windows.Media.Imaging;


  6. using Microsoft.Kinect;


  7. using System.Collections.Generic;


  8. using System.ComponentModel;


  9.  


  10. namespace KISS


  11. {


  12. class KinectIntelligentSecuritySystem : INotifyPropertyChanged


  13. {


  14. public event Action OnMovement;


  15. public event PropertyChangedEventHandler PropertyChanged;


  16.  


  17. public WriteableBitmap DepthBitmap { get; private set; }


  18.  


  19. private KinectSensor kinectSensor;


  20. readonly List<double> previousValues = new List<double>();


  21. short[] data;


  22. int[] data32;


  23.  


  24. void Kinects_StatusChanged(object sender, StatusChangedEventArgs e)


  25. {


  26. switch (e.Status)


  27. {


  28. case KinectStatus.Connected:


  29. if (kinectSensor == null)


  30. {


  31. kinectSensor = e.Sensor;


  32. Initialize();


  33. }


  34. break;


  35. case KinectStatus.Disconnected:


  36. if (kinectSensor == e.Sensor)


  37. {


  38. Clean();


  39. MessageBox.Show(“Kinect was disconnected”);


  40. }


  41. break;


  42. case KinectStatus.NotPowered:


  43. if (kinectSensor == e.Sensor)


  44. {


  45. Clean();


  46. MessageBox.Show(“Kinect is no more powered”);


  47. }


  48. break;


  49. }


  50. }


  51.  


  52. public KinectIntelligentSecuritySystem()


  53. {


  54. try


  55. {


  56. //listen to any status change for Kinects


  57. KinectSensor.KinectSensors.StatusChanged += Kinects_StatusChanged;


  58.  


  59. //loop through all the Kinects attached to this PC, and start the first that is connected without an error.


  60. foreach (KinectSensor kinect in KinectSensor.KinectSensors)


  61. {


  62. if (kinect.Status == KinectStatus.Connected)


  63. {


  64. kinectSensor = kinect;


  65. break;


  66. }


  67. }


  68.  


  69. if (kinectSensor == null)


  70. MessageBox.Show(“No Kinect found”);


  71. else


  72. Initialize();


  73.  


  74. }


  75. catch (Exception ex)


  76. {


  77. MessageBox.Show(ex.Message);


  78. }


  79. }


  80.  


  81. private void Initialize()


  82. {


  83. if (kinectSensor == null)


  84. return;


  85.  


  86. kinectSensor.Start();


  87.  


  88. kinectSensor.DepthStream.Enable(DepthImageFormat.Resolution80x60Fps30);


  89. kinectSensor.DepthFrameReady += kinectSensor_DepthFrameReady;


  90. kinectSensor.DepthStream.Range = DepthRange.Near;


  91. }


  92.  


  93. void Clean()


  94. {


  95. kinectSensor.Stop();


  96. kinectSensor = null;


  97. }


  98.  


  99. void DisplayData(DepthImageFrame frame)


  100. {


  101. for (int i16 = 0, i32 = 0; i16 < data.Length && i32 < data32.Length; i16++, i32++)


  102. {


  103. int realDepth = (data[i16] >> 3);


  104. byte intensity = (byte)(255 – (255 realDepth / 3000.0f));


  105.  


  106. data32[i32] = (intensity / 2) + ((intensity / 2) << 16) + ((intensity / 2) << 8) + (255 << 24);


  107. }


  108.  


  109. if (DepthBitmap == null)


  110. {


  111. DepthBitmap = new WriteableBitmap(frame.Width, frame.Height, 96, 96, PixelFormats.Bgra32, null);


  112. }


  113.  


  114. DepthBitmap.Lock();


  115.  


  116. int stride = DepthBitmap.PixelWidth DepthBitmap.Format.BitsPerPixel / 8;


  117. Int32Rect dirtyRect = new Int32Rect(0, 0, DepthBitmap.PixelWidth, DepthBitmap.PixelHeight);


  118. DepthBitmap.WritePixels(dirtyRect, data32, stride, 0);


  119.  


  120. DepthBitmap.AddDirtyRect(dirtyRect);


  121. DepthBitmap.Unlock();


  122.  


  123. if (PropertyChanged != null)


  124. {


  125. PropertyChanged(this, new PropertyChangedEventArgs(“DepthBitmap”));


  126. }


  127. }


  128.  


  129. void kinectSensor_DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e)


  130. {


  131. var frame = e.OpenDepthImageFrame();


  132.  


  133. if (frame == null)


  134. return;


  135.  


  136. if (data == null)


  137. {


  138. data = new short[frame.PixelDataLength];


  139. data32 = new int[frame.PixelDataLength];


  140. }


  141.  


  142. frame.CopyPixelDataTo(data);





  143. // Displaying frame


  144. DisplayData(frame);


  145.  


  146. frame.Dispose();





  147. // Computing depth average


  148. var avg = data.Average(pixel => pixel);


  149.  


  150. previousValues.Add(avg);


  151.  


  152. var currentAvg = previousValues.Average(value => value);


  153.  


  154. if (previousValues.Count > 60)


  155. previousValues.RemoveAt(0);


  156.  


  157. if (previousValues.Count == 60 && (Math.Abs(currentAvg – avg) > 1500))


  158. {


  159. if (OnMovement != null)


  160. OnMovement();


  161.  


  162. previousValues.Clear();


  163. }


  164. }


  165.  


  166. }


  167. }