[Windows 8] Some code samples about Windows 8 development

April 12th, 2012 | Posted by Tom in .NET | Article | HTML5 | Windows 8 | WinJS - (2 Comments) | Read: 5,577

In my day to day job, I have the chance to work on some Windows 8 apps thus, I wanted to share with you some code I’ve found/used and which could be, I hope, useful for you too !

Access files located in installation folder:

const string file = @”Images\Logo.jpg”;

 

var installFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;

var logoFile = await installFolder.GetFileAsync(file);

if(logoFile != null)

{

    //

}

Detect Design Mode:

var isInDesignMode = Windows.ApplicationModel.DesignMode.DesignModeEnabled;

 

Get license information (expiration date, trial mode, etc.):

LicenseInformation licenseInfo = null;

 

#if DEBUG

    // CurrentAppSimulator is use to simulate an object allowing to access to the data

    licenseInfo = Windows.ApplicationModel.Store.CurrentAppSimulator.LicenseInformation;

#else

    licenseInfo = Windows.ApplicationModel.Store.CurrentApp.LicenseInformation;

#endif

    var expirationDate = licenseInfo.ExpirationDate;

    var isTrial = licenseInfo.IsTrial;

Convert HTML text to simple text:

string text = Windows.Data.Html.HtmlUtilities.ConvertToText(

“<div class=\”siteLogo\”><a href=\”/en-us/windows/apps\” title=\”Dev Center -  Metro style apps\”><img src=\”http://i.msdn.microsoft.com/Hash/6b67f1378af22a94e4336f5bfdd136bd.png\” alt=\”Dev Center -  Metro style apps\” title=\”Dev Center -  Metro style apps\” /><span>Dev Center -  Metro style apps</span></a></div>”);

Get all connected devices information:

var deviceWatcher = Windows.Devices.Enumeration.DeviceInformation.CreateWatcher();

deviceWatcher.Added += async (watcher, deviceInformation) =>

{

    var glyphThumbnail = await deviceInformation.GetGlyphThumbnailAsync();

    if(glyphThumbnail.Size > 0)

    {

        // Access to the glyph thumbnail

    }

 

    var thumbnail = await deviceInformation.GetThumbnailAsync();

    if(thumbnail.Size > 0)

    {

        // Access to the thumbnail

    }

};

deviceWatcher.Removed += (watcher, deviceInformation) =>

{};

deviceWatcher.Start();

Detect if keyboard, mouse or touch device is available:

// 1: Present

// 0: Not Present

var isKeyboardPresent = new Windows.Devices.Input.KeyboardCapabilities().KeyboardPresent;

var isMousePresent = new Windows.Devices.Input.MouseCapabilities().MousePresent;

var isTouchPresent = new Windows.Devices.Input.TouchCapabilities().TouchPresent;

Change image used in LockScreen:

var filePicker = new Windows.Storage.Pickers.FileOpenPicker();

filePicker.FileTypeFilter.Add(“.png”);

var selectedFile = await filePicker.PickSingleFileAsync();

 

if(selectedFile != null)

{

    var authPicker = Windows.System.UserProfile.LockScreen.SetImageFileAsync(selectedFile);

}

Get information about connected user (and, optionally, set its pictures):

var userInformation = Windows.System.UserProfile.UserInformation.DisplayName;

Update badge on tile:

var badgeUpdate = Windows.UI.Notifications.BadgeUpdateManager.CreateBadgeUpdaterForApplication();

var template = Windows.UI.Notifications.BadgeUpdateManager.GetTemplateContent(BadgeTemplateType.BadgeNumber);

var textNode = template.SelectSingleNode(“/badge”);

textNode.Attributes[0].NodeValue = “99″;

var badge = new BadgeNotification(template);

 

badgeUpdate.Update(badge);

 

Happy coding!

[Windows 8] How to geolocalize your users ?

April 2nd, 2012 | Posted by Tom in .NET | Windows 8 - (2 Comments) | Read: 2,039

If you are developing some applications with Windows Phone 7, you may know that it’s possible, for you, to find the position of your users and thus, use it in your application.

Windows 8 now integrate the same concept, thanks to the class Geolocator, which is similar to the GeoCoordinateWatcher class from Windows Phone:

image

So, using this new API is pretty simple:

var geolocator = new Geolocator(); geolocator.PositionChanged += geolocator_PositionChanged; geolocator.GetGeopositionAsync();

Here, I just subscribe to the event PositionChanged and, once this one occurred, I get the position and the coordinates (latitude and longitude):

void geolocator_PositionChanged(Geolocator sender, PositionChangedEventArgs args) { var position = args.Position; string pos = string.Format("Country:{0} Latitude: {1} Longitude: {2}", position.CivicAddress.Country, position.Coordinate.Latitude, position.Coordinate.Longitude); var action = new Action(() => tb.Text = pos); InvokedHandler handler = (s, ia) => action(); Dispatcher.InvokeAsync(CoreDispatcherPriority.Normal, handler, this, null); }

(Notice the usage of the Dispatcher, to be able to set the Text property of a TextBlock).

So, if you have a service with a method that take in parameters this coordinates, it’s easy for you to display to the user the best results that are near him.

You may wonder how the position is retrieved right ? In fact, Windows will get the information from the GPS that is installed/used. But, as not everybody will have a GPS, the location will be derived from available network information !

To make it possible to work, don’t forget to add the require capacity (location) from the manifest:

image

Now, if you execute your app, Windows will ask user to confirm the use of it’s location:

Screenshot

If user confirm, then the location is displayed correctly:

Screenshot (2)

 

Happy geocoding!

Windows 8 Consumer Preview is available since 2 weeks now and a lot of developers have started to look at how to develop applications using HTML5 / Javascript or XAML / C#.

I’ve got the chance to work with a few customers to help them create their application for Windows 8 Consumer Preview. For one of them, a need was the capacity to show something that could work as a ChildWindow (the one that exists in Silverlight). As nothing is available out of box in WinJS, I created my own control and I would like to share it with the community.

I have to say that I’m not an expert in Javascript so feel free to share your opinions !

Here is the definition of the control:

// For an introduction to the HTML Fragment template, see the following documentation:
// http://go.microsoft.com/fwlink/?LinkId=232511
(function () {
    "use strict";

    WinJS.Namespace.define("Controls", {
        ChildWindow: WinJS.Class.define(function (element, options) {
            this._element = element || document.createElement("div");
            var centeredOverlay = this.element;
            centeredOverlay.winControl = this;

            this._overlayBackgroundColor = (options && options.overlayBackgroundColor) ? options.overlayBackgroundColor : "#FFFFFF";
            this._overlayBackgroundOpacity = (options && options.overlayBackgroundOpacity) ? options.overlayBackgroundOpacity : "1";
            this._overlayContainerBackgroundColor = (options && options.overlayContainerBackgroundColor) ? options.overlayContainerBackgroundColor : "white";
            this._rootElement = (options && options.rootElement) ? options.rootElement : document.body;
            this._contentTemplate = (options && options.contentTemplate) ? options.contentTemplate : null;
            this._mustClickOnButtonsToClose = (options && options.mustClickOnButtonsToClose) ? options.mustClickOnButtonsToClose : "true";

            this._initialize(options);
        }, {
            _elementBase: null,
            _elementOverlay: null,

            _element: null,
            _rootElement: null,
            _overlayBackgroundColor: null,
            _overlayBackgroundOpacity: null,
            _overlayContainerBackgroundColor: null,
            _contentTemplate: null,
            _mustClickOnButtonsToClose: null,
            _okButton: null,
            _cancelButton: null,

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

            // Must be in the following format: #RGB or RGB
            overlayBackgroundColor: {
                get: function () {
                    return this._overlayBackgroundColor;
                },
                set: function (data) {
                    this._overlayBackgroundColor = data;
                }
            },

            overlayBackgroundOpacity: {
                get: function () {
                    return this._overlayBackgroundOpacity;
                },
                set: function (data) {
                    this._overlayBackgroundOpacity = data;
                }
            },

            overlayContainerBackgroundColor: {
                get: function () {
                    return this._overlayContainerBackgroundColor;
                },
                set: function (data) {
                    this._overlayContainerBackgroundColor = data;
                }
            },

            rootElement: {
                get: function () {
                    return this._rootElement;
                },
                set: function (data) {
                    this._rootElement = data;
                }
            },

            contentTemplate: {
                get: function () {
                    return this._contentTemplate;
                },
                set: function (data) {
                    this._contentTemplate = data;
                }
            },

            mustClickOnButtonsToClose: {
                get: function () {
                    return this._mustClickOnButtonsToClose;
                },
                set: function (data) {
                    this._mustClickOnButtonsToClose = data;
                }
            },

            _initialize: function (options) {
                var that = this;

                this._elementBase = this._rootElement;

                this._elementOverlay = document.createElement("div");
                this._elementOverlay.id = "elementOverlay";
                //this._elementOverlay.style.zIndex = 99;
                this._elementOverlay.onmousedown = function (eventInfo) {
                    if ((!that.mustClickOnButtonsToClose || that.mustClickOnButtonsToClose == "false") && (eventInfo.srcElement.id == "elementOverlay")) {
                        that.hide();
                    }
                }

                var rootContainer = document.createElement("div");
                rootContainer.style.backgroundColor = this._overlayContainerBackgroundColor;
                rootContainer.style.display = "-ms-grid";
                rootContainer.style.msGridRows = "1fr 50px";
                rootContainer.style.msGridColumnAlign = "center";
                rootContainer.style.msGridRowAlign = "center";

                if (this._contentTemplate) {
                    var templateWidth = this._contentTemplate.style.width ? WinJS.Utilities.convertToPixels(this._contentTemplate, this._contentTemplate.style.width) : WinJS.Utilities.convertToPixels(this._contentTemplate.children[0], this._contentTemplate.children[0].style.width);
                    templateWidth = templateWidth || 250; // default width of 250px

                    rootContainer.style.msGridColumns = templateWidth + "px";

                    rootContainer.appendChild(this._contentTemplate);
                }

                var buttonsPanelDiv = document.createElement("div");
                this._okButton = document.createElement("input");
                this._okButton.type = "button";
                this._okButton.value = "OK";
                this._okButton.style.color = this.overlayContainerBackgroundColor == "white" ? "black" : "white";
                this._okButton.style.borderColor = this.overlayContainerBackgroundColor == "white" ? "black" : "white";

                this._cancelButton = document.createElement("input");
                this._cancelButton.type = "button";
                this._cancelButton.value = "Cancel";
                this._cancelButton.style.marginLeft = "10px";
                this._cancelButton.style.color = this.overlayContainerBackgroundColor == "white" ? "black" : "white";
                this._cancelButton.style.borderColor = this.overlayContainerBackgroundColor == "white" ? "black" : "white";

                buttonsPanelDiv.appendChild(this._okButton);
                buttonsPanelDiv.appendChild(this._cancelButton);

                buttonsPanelDiv.style.msGridColumn = "2";
                buttonsPanelDiv.style.msGridRow = "2";
                buttonsPanelDiv.style.marginRight = "10px";

                rootContainer.appendChild(buttonsPanelDiv);

                this._elementOverlay.appendChild(rootContainer);

                //this._elementBase.parentElement.insertBefore(this._elementOverlay, this._elementBase);
                this._elementBase.appendChild(this._elementOverlay);

                this.hide();
            },

            convertHexToNumber: function (data) {
                if (data.charAt(0) == "#") {
                    data = data.slice(1); //Remove the '#' char - if there is one.
                }

                data = data.toUpperCase();
                var hex_alphabets = "0123456789ABCDEF";
                var value = new Array(3);
                var k = 0;
                var int1, int2;
                for (var i = 0; i < 6; i += 2) {
                    int1 = hex_alphabets.indexOf(data.charAt(i));
                    int2 = hex_alphabets.indexOf(data.charAt(i + 1));
                    value[k] = (int1 * 16) + int2;
                    k++;
                }
                return (value);
            },

            show: function (validationCallBack, cancellationCallBack) {
                var that = this;

                this._okButton.onclick = function () {
                    that.hide();

                    if (validationCallBack) {
                        validationCallBack();
                    }
                };

                this._cancelButton.onclick = function () {
                    that.hide();

                    if (cancellationCallBack) {
                        cancellationCallBack();
                    }
                };

                this._elementOverlay.onkeypress = function (eventObject) {
                    var pressedKeyCode = window.event.keyCode;

                    if (eventObject.key === "Enter") { // User press Enter key
                        that.hide();

                        if (validationCallBack) {
                            validationCallBack();
                        }
                    }
                };

                var height = WinJS.Utilities.getTotalHeight(document.body);
                var width = WinJS.Utilities.getContentWidth(document.body);

                //var templateWidth = this._contentTemplate.style.width ? WinJS.Utilities.convertToPixels(this._contentTemplate, this._contentTemplate.style.width) : WinJS.Utilities.convertToPixels(this._contentTemplate.children[0], this._contentTemplate.children[0].style.width);
                //var templateHeight = this._contentTemplate.style.height ? WinJS.Utilities.convertToPixels(this._contentTemplate, this._contentTemplate.style.height) : WinJS.Utilities.convertToPixels(this._contentTemplate.children[0], this._contentTemplate.children[0].style.height);

                this._elementOverlay.style.height = height + "px";
                this._elementOverlay.style.width = width + "px";
                //this._elementOverlay.style.paddingLeft = ((width / 2) - (templateWidth / 2))+ "px";
                //this._elementOverlay.style.paddingTop = ((height / 2) - (templateHeight / 2))+ "px";
                //this._elementOverlay.style.opacity = this._overlayBackgroundOpacity;
                this._elementOverlay.style.opacity = 1;
                this._elementOverlay.style.backgroundColor = "rgba(" + this.convertHexToNumber(this._overlayBackgroundColor)[0] + ", " + this.convertHexToNumber(this._overlayBackgroundColor)[1] + ", " + this.convertHexToNumber(this._overlayBackgroundColor)[2] + ", " + this._overlayBackgroundOpacity + ")";
                this._elementOverlay.style.msGridColumns = "1fr";
                this._elementOverlay.style.msGridRows = "1fr";
                this._elementOverlay.style.display = "-ms-grid";

                this._contentTemplate.style.display = "inline";
            },

            hide: function () {
                var that = this;

                WinJS.UI.Animation.fadeOut(this._elementOverlay).done(function () {
                    that._elementOverlay.style.opacity = 0;
                    that._contentTemplate.style.display = "none";
                    that._elementOverlay.style.display = "none";
                });
            }
        })
    });
})();

Here are the options available on the control:

  1. overlayBackgroundColor: which define the background color of the ChildWindow
  2. overlayBackgroundOpacity: which define the opacity of the ChildWindow
  3. overlayContainerBackgroundColor: which is used to define the color of the container that will contains the template
  4. rootElement: is the element that will be used to center the ChildWindow
  5. contentTemplate: is the template that will define the content of the ChildWindow
  6. mustClickOnButtonsToClose: is a property that allows you to specify if user must click on the buttons to close the ChildWindow or if the window can be closed if user click anywhere on the screen

To use it, just follow the way you do for all WinJS applications:

<div id="saveSearchTemplate" data-win-control="WinJS.Binding.Template">
        <div style="display: -ms-grid; -ms-grid-rows: 50px 30px 50px; width: 300px; margin-left: 10px;
            margin-top: 15px">
            <h2 style="-ms-grid-row: 1">
                Searh</h2>
            <h4 style="-ms-grid-row: 2">
                Give a name to your search</h4>
            <input id="searchNameValue" style="-ms-grid-row: 3; height: 25px; width: 475px" />
        </div>
    </div>
    <div id="rootElement" class="fragment searchpage">
        <div id="flyout" data-win-control="Controls.CenteredFlyout" data-win-options="{rootElement: select('#flyout'), contentTemplate: select('#saveSearchTemplate'), overlayBackgroundColor: '#898989', overlayBackgroundOpacity: '.3', mustClickOnButtonsToClose: 'true'}">
        </div>
    </div>

As you can see, the content of the ChildWindow is defined using a WinJS Template (to take advantage of the fact that the template are hidden by default).

Hope you’ll like it !

 

Happy coding!

The videos of the sessions I’ve animated during the last TechDays 2012 in Paris, France have been published and are now available:

  1. MVVM de A à Z
  2. WPF 4.5: Quoi de neuf pour les développeurs ?

The last session (“Améliorez votre productivité XAML en entreprise”) has not been published but I’ll update my post as soon as  you’ll be able to see it.

 

Happy coding!

As I’ve explained before, I’ll be presenting 3 sessions during the next TechDays in France.

One of the session is entitled “Améliorer votre productivité XAML en entreprise” and we are currently working on it. If you plan to attend to our session (or to TechDays), we want to learn from you: we give you the opportunity to let us know what you want to see in the session !

If you have any particular needs or if you have some subjects that you want to see covered in the session, let me know (in a comment) and we’ll try do our best !

Thanks and don’t forget: if you plan to come in TechDays 2012, feel free to come to see me.

 

Happy coding!

Next month (7, 8 and 9 February), Microsoft France will organize the most important IT event of the year in the country: the Microsoft TechDays 2012 !

During the event, I’ll be presenting 3 sessions:

  1. WPF 4.5 : Quoi de neuf pour les développeurs ? (RDA105)
  2. MVVM de A à Z (RDA106)
  3. Améliorer votre productivité XAML en entreprise ! (RDA201)

If you are in Paris and attending the event, feel free to come to meet me and talk about WPF, Silverlight, Windows Phone and other XAML stuff !

 

See you there!

[Wix] How to use accents in the name of application or shortcut ?

December 19th, 2011 | Posted by Tom in .NET | Article | Wix - (0 Comments) | Read: 1,632

Wix is a very good tool to create Windows Installers used to deployed applications. You can use it to deploy files, create shortcuts, add registry keys, etc.

By default, you’ll get an error if you try to use accents in your Wix file (Product.wxs):

image

This is mostly true for European languages (such as French) but this can happened for other languages. The error is displayed in the “Error List” of Visual Studio:

image

Indeed, by default, the codepage for your current project is 1252 but the language attribute is 1033, which correspond to English. And, in English, there are no accent. So, in order to correct the error, you need to change the Language property to something compatible (1036 is for French for example):

image

If you recompile your project, you’ll notice that there are no longer any errors !

 

Happy coding!

Since Visual Studio 2008 (and maybe before), it’s possible for developers to generate sample data for the database they are using to develop applications. This feature is really useful to test some scenarios like pagination, load test, etc.

To start, take a look at the following diagram that represent the tables which we are going to use in the article:

image

It’s a pretty simple example but, as you can see, there is a foreign key between the 2 tables and that’s the good part: Visual Studio will be able to generate data and take care of the foreign keys for you !

To populate your database, you first need to add a SQL Server Database Project:

image

Import the content of your database in your project then, right click on the project that has just been created and choose to add a new data generation plan:

image

The screen that appear display all the tables in your database and allow you to specify how many row you want to insert:

image

If your right click on one of the previous line, you can even display a preview of the data that will be generated in your database:

image

As you can see, the “Id” column will not be generated because its value is auto-generated by the database during each insertion:

image

Now, if you press F5, you’ll see that the data are generated for your database:

image

A look in the database values (using SQL Server Management Studio) can confirm that all is OK:

image

We can see that the tables are correctly populated, even for the “FrequencyId”, which is the foreigh key but well filled in !

This is very performant and useful but imagine that some of your SQL tables already contains some data: you don’t want to delete them before generating the sample data:

image

If you try to run your data generation plan, you’ll encounter the following error:

image

Of course, you need to have selected “No” in the MessageBox asking you if you want to delete the data that are currently in the database:

image

After thinking a bit, it appears that the error is normal. Indeed, our data generation plan will insert some data in the “Frequency” table and so, the first index will start from 1. But, as we have already have some data in the table, we cannot insert a duplicate key.

To prevent this error, we need to specify that 0 row will be inserted:

image

But it’ not enough. Indeed, we need to ensure that the data inserted in the column ”FrequencyId” contains good data (i.e. good reference to the data from the Frequency table). To do that, select  the line “NewsPaper” and you’ll see that the “FrequencyId” is actually a foreign key:

image

Change that to select “Data bound generator” and, in the properties (F4), specify the connection information. This property is used to indicate to Visual Studio where to get the data to populate this column. Then, in the “Select Query” property, enter the SQL request that will be used to get the id corresponding to FrequencyId:

image

Automatically, the data generation plan know that to populate the FrequencyId column of the NewsPaper table, it need to get a random value from the SQL request:

image

Now, press the F5 key (choose to don’t delete the data from the database) to insert the data:

image

If you look in the database, you can see that the data has been effectively correctly inserted and, the most important, that the foreign key values (the “FrequencyId” column) contains only values taken from the “Frequency” table:

image

As you’ve seen in the article, generating sample data for your database is pretty simple and a lot of more complex scenarios can be covered….. Maybe I’ll talk about them a bit later !

Happy coding!

It’s a common usage, these days, to develop applications that allow users to access their personal data and Windows Live (documents, pictures, etc.) is a good example of the kind of data someone might want to access from everywhere.

Until now, developers needed to develop custom systems to access the data on the cloud, when it was possible. But thanks to Microsoft and it’s Live SDK, it’s possible to access any Windows Live data from Windows Phone or Windows 8 application (HTML or XAML).

First thing to do is to create Client ID for the application you want to develop. This ID will be used to authenticate your application and ensure that user can trust it. To create your Client ID, go to https://manage.dev.live.com and create an application (simply provide its name and its language):

image

Once the application is created, don’t forget to edit its settings to indicate that it’s a mobile app and not a desktop app:

image

Now, it’s start to develop the Windows Phone application. Add a reference to the 2 DLLs provided in the SDK (C:\Program Files (x86)\Microsoft SDKs\Live\v5.0\Windows Phone\References):

  1. Microsoft.Live.Controls.dll
  2. Microsoft.Live.dll

Now, in your XAML file, add the following code, which will provide you a button to allow users to connect to their Live account:

<Live:SignInButton ClientId="XXXXXXX"
                   Scopes="wl.basic wl.photos wl.skydrive wl.offline_access wl.signin wl.skydrive_update"
                   RedirectUri="http://oauth.live.com/desktop"
                   Branding="Skydrive"
                   TextType="SignIn"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   SessionChanged="OnSessionChanged" />

There are a lot of properties that you can changed (you can see the whole documentation here: http://msdn.microsoft.com/en-us/library/hh243650.aspx) but the most important are:

  1. ClientId: This is the number that you’ve generated in the first part
  2. Scopes: The list of “features” that your application will be allowed to manipulate. In our example, the application will get access to basic data, photos, Skydrive, ability to reconnect, etc.

In the code-behind, add the following code, used to be notified when the user is connected and then, get access to its name:

private void OnSessionChanged(object sender, Microsoft.Live.Controls.LiveConnectSessionChangedEventArgs e)
{
    if(e.Error == null)
    {
        if (e.Status == LiveConnectSessionStatus.Connected)
        {
            this._currentSession = e.Session;

            var client = new LiveConnectClient(this._currentSession);
            client.GetCompleted += (o, args) =>
            {
                if (args.Error == null)
                {
                    MessageBox.Show(Convert.ToString(args.Result["name"]));
                }
            };
            client.GetAsync("me");
        }
    }
}

Now, when user execute your application, it get a button he can use to log on Windows Live:

image image

Once logged in, a page display which data the application will be able to use:

image

Then, the user is authenticated and the code to retrieved its name is executed:

image

Of course, this sample is pretty simple and it’s possible to perform more complex task such as upload/downloading pictures, etc.

 

Feel free to take a look by yourself!

 

Happy coding!

Prism is a well-know framework to develop applications that support modularity, navigation, communication between loosely coupled components and more !

Each module is loaded automatically (OnDemand property of Module attribute set to false) or on demand (OnDemand set to true) but in all case, you can’t define in which order the modules are loaded. By default, they are loaded by using their name and you don’t have any way to change that.

In my case, I wanted to be able to load my modules in specific order. So, after taking a look on Internet, I’ve found this post on StackOverFlow: http://stackoverflow.com/questions/1296642/how-to-control-the-order-of-module-initialization-in-prism

The code work fine except for modules that are asked to be loaded on demand. So I needed to find a way to be able to specify the loading order of each module, even if they have to be started on demand) and, for that, I’ve been inspired by the previous code.

Indeed, I first create the Priority attribute:

/// <summary>
/// Allows the order of module loading to be controlled.  Where dependencies
/// allow, module loading order will be controlled by relative values of priority
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public sealed class PriorityAttribute : Attribute
{
    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="priority">the priority to assign</param>
    public PriorityAttribute(int priority)
    {
        this.Priority = priority;
    }

    /// <summary>
    /// Gets or sets the priority of the module.
    /// </summary>
    /// <value>The priority of the module.</value>
    public int Priority { get; private set; }
}

Then, I created a dedicated ModuleCatalog, inspired by the PrioritizedDirectoryCatalog (from the post of StackOverFlow) and the DirectoryModuleCatalog (from the Prism’s sources):

public class PrioritizedDirectoryModuleCatalog : DirectoryModuleCatalog
{
    protected override void InnerLoad()
    {
        if (string.IsNullOrEmpty(this.ModulePath))
            throw new InvalidOperationException("The ModulePath cannot contain a null value or be empty.");

        if (!Directory.Exists(this.ModulePath))
            throw new InvalidOperationException(
                string.Format(CultureInfo.CurrentCulture, "Directory {0} was not found.", this.ModulePath));

        AppDomain childDomain = this.BuildChildDomain(AppDomain.CurrentDomain);

        try
        {
            List<string> loadedAssemblies = new List<string>();

            var assemblies = (
                                 from Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()
                                 where !(assembly is System.Reflection.Emit.AssemblyBuilder)
                                    && assembly.GetType().FullName != "System.Reflection.Emit.InternalAssemblyBuilder"
                                    && !String.IsNullOrEmpty(assembly.Location)
                                 select assembly.Location
                             );

            loadedAssemblies.AddRange(assemblies);

            Type loaderType = typeof(ModulePriorityLoader);

            if (loaderType.Assembly != null)
            {
                var loader =
                    (ModulePriorityLoader)
                    childDomain.CreateInstanceFrom(loaderType.Assembly.Location, loaderType.FullName).Unwrap();
                loader.LoadAssemblies(loadedAssemblies);
                this.Items.AddRange(this.Sort(loader.GetModuleInfos(this.ModulePath)));
            }
        }
        finally
        {
            AppDomain.Unload(childDomain);
        }
    }

    /// <summary>
    /// Sort modules according to dependencies and Priority
    /// </summary>
    /// <param name="modules">modules to sort</param>
    /// <returns>sorted modules</returns>
    protected override IEnumerable<ModuleInfo> Sort(IEnumerable<ModuleInfo> modules)
    {
        Dictionary<string, int> priorities = GetPriorities(modules);

        //call the base sort since it resolves dependencies, then re-sort 
        var result = new List<ModuleInfo>(base.Sort(modules));
        result.Sort((x, y) =>
        {
            string xModuleName = x.ModuleName;
            string yModuleName = y.ModuleName;

            //if one depends on other then non-dependent must come first
            //otherwise base on priority
            if (x.DependsOn.Contains(yModuleName))
                return 1; //x after y
            else if (y.DependsOn.Contains(xModuleName))
                return -1; //y after x
            else
                return priorities[xModuleName].CompareTo(priorities[yModuleName]);
        });

        return result;
    }

    /// <summary>
    /// Get the priorities
    /// </summary>
    /// <param name="modules"></param>
    /// <returns></returns>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")]
    public Dictionary<string, int> GetPriorities(IEnumerable<ModuleInfo> modules)
    {
        //retrieve the priorities of each module, so that we can use them to override the 
        //sorting - but only so far as we don't mess up the dependencies
        var priorities = new Dictionary<string, int>();
        var assemblies = new Dictionary<string, Assembly>();

        foreach (ModuleInfo module in modules)
        {
            if (!assemblies.ContainsKey(module.Ref))
            {
                //LoadFrom should generally be avoided appently due to unexpected side effects,
                //but since we are doing all this in a separate AppDomain which is discarded
                //this needn't worry us
                assemblies.Add(module.Ref, Assembly.LoadFrom(module.Ref));
            }

            Type type = assemblies[module.Ref].GetExportedTypes()
                .Where(t => t.AssemblyQualifiedName.Equals(module.ModuleType, StringComparison.Ordinal))
                .First();

            var priorityAttribute =
                CustomAttributeData.GetCustomAttributes(type).FirstOrDefault(
                    cad => cad.Constructor.DeclaringType.FullName == typeof(PriorityAttribute).FullName);

            int priority;
            if (priorityAttribute != null)
            {
                priority = (int)priorityAttribute.ConstructorArguments[0].Value;
            }
            else
            {
                priority = 0;
            }

            priorities.Add(module.ModuleName, priority);
        }

        return priorities;
    }

    /// <summary>
    /// Local class to load assemblies into different appdomain which is then discarded
    /// </summary>
    private class ModulePriorityLoader : MarshalByRefObject
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        internal void LoadAssemblies(IEnumerable<string> assemblies)
        {
            foreach (string assemblyPath in assemblies)
            {
                try
                {
                    Assembly.ReflectionOnlyLoadFrom(assemblyPath);
                }
                catch (FileNotFoundException)
                {
                    // Continue loading assemblies even if an assembly can not be loaded in the new AppDomain
                }
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        internal ModuleInfo[] GetModuleInfos(string path)
        {
            DirectoryInfo directory = new DirectoryInfo(path);

            ResolveEventHandler resolveEventHandler =
                delegate(object sender, ResolveEventArgs args) { return OnReflectionOnlyResolve(args, directory); };

            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += resolveEventHandler;

            Assembly moduleReflectionOnlyAssembly =
                AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().First(
                    asm => asm.FullName == typeof(IModule).Assembly.FullName);

            Type IModuleType = moduleReflectionOnlyAssembly.GetType(typeof(IModule).FullName);

            IEnumerable<ModuleInfo> modules = GetNotAllreadyLoadedModuleInfos(directory, IModuleType);

            var array = modules.ToArray();
            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= resolveEventHandler;

            return array;
        }

        private static IEnumerable<ModuleInfo> GetNotAllreadyLoadedModuleInfos(DirectoryInfo directory, Type IModuleType)
        {
            List<FileInfo> validAssemblies = new List<FileInfo>();
            Assembly[] alreadyLoadedAssemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();

            var fileInfos = directory.GetFiles("*.dll")
                .Where(file => alreadyLoadedAssemblies
                                   .FirstOrDefault(
                                   assembly =>
                                   String.Compare(Path.GetFileName(assembly.Location), file.Name,
                                                  StringComparison.OrdinalIgnoreCase) == 0) == null);

            foreach (FileInfo fileInfo in fileInfos)
            {
                Assembly assembly = null;
                try
                {
                    assembly = Assembly.ReflectionOnlyLoadFrom(fileInfo.FullName);
                    validAssemblies.Add(fileInfo);
                }
                catch (BadImageFormatException)
                {
                    // skip non-.NET Dlls
                }
            }

            return validAssemblies.SelectMany(file => Assembly.ReflectionOnlyLoadFrom(file.FullName)
                                        .GetExportedTypes()
                                        .Where(IModuleType.IsAssignableFrom)
                                        .Where(t => t != IModuleType)
                                        .Where(t => !t.IsAbstract)
                                        .Select(type => CreateModuleInfo(type)));
        }

        private static Assembly OnReflectionOnlyResolve(ResolveEventArgs args, DirectoryInfo directory)
        {
            Assembly loadedAssembly = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().FirstOrDefault(
                asm => string.Equals(asm.FullName, args.Name, StringComparison.OrdinalIgnoreCase));
            if (loadedAssembly != null)
            {
                return loadedAssembly;
            }

            AssemblyName assemblyName = new AssemblyName(args.Name);
            string dependentAssemblyFilename = Path.Combine(directory.FullName, assemblyName.Name + ".dll");
            if (File.Exists(dependentAssemblyFilename))
            {
                return Assembly.ReflectionOnlyLoadFrom(dependentAssemblyFilename);
            }
            return Assembly.ReflectionOnlyLoad(args.Name);
        }

        private static ModuleInfo CreateModuleInfo(Type type)
        {
            string moduleName = type.Name;
            List<string> dependsOn = new List<string>();
            bool onDemand = false;
            var moduleAttribute =
                CustomAttributeData.GetCustomAttributes(type).FirstOrDefault(
                    cad => cad.Constructor.DeclaringType.FullName == typeof(ModuleAttribute).FullName);

            if (moduleAttribute != null)
            {
                foreach (CustomAttributeNamedArgument argument in moduleAttribute.NamedArguments)
                {
                    string argumentName = argument.MemberInfo.Name;
                    switch (argumentName)
                    {
                        case "ModuleName":
                            moduleName = (string)argument.TypedValue.Value;
                            break;

                        case "OnDemand":
                            onDemand = (bool)argument.TypedValue.Value;
                            break;

                        case "StartupLoaded":
                            onDemand = !((bool)argument.TypedValue.Value);
                            break;
                    }
                }
            }

            var moduleDependencyAttributes =
                CustomAttributeData.GetCustomAttributes(type).Where(
                    cad => cad.Constructor.DeclaringType.FullName == typeof(ModuleDependencyAttribute).FullName);

            foreach (CustomAttributeData cad in moduleDependencyAttributes)
            {
                dependsOn.Add((string)cad.ConstructorArguments[0].Value);
            }

            ModuleInfo moduleInfo = new ModuleInfo(moduleName, type.AssemblyQualifiedName)
            {
                InitializationMode =
                    onDemand
                        ? InitializationMode.OnDemand
                        : InitializationMode.WhenAvailable,
                Ref = type.Assembly.CodeBase,
            };
            moduleInfo.DependsOn.AddRange(dependsOn);

            return moduleInfo;
        }
    }
}

Now, in your Bootstrapper, you simply have to use your new ModuleCatalog:

protected override IModuleCatalog CreateModuleCatalog()
{
    return new PrioritizedDirectoryModuleCatalog { ModulePath = ".\Modules" };
}

Then, decorate each of your module with this new attribute:

[Module(ModuleName = "RootModule", OnDemand = false)]
[Priority(-1)]
public class RootModule : IModule

[Module(ModuleName = "ModuleA", OnDemand = true)]
[Priority(1)]
public class ModuleAModule : IModule

All that’s all ! If you enumerate all the modules, you will be able to see that they are ordered as you define it: RootModule will be the first, then, ModuleA (even if the sort on the name will produce a different result):

[Dependency]
public IModuleCatalog ModuleCatalog { get; set; }

[Dependency]
public IModuleManager ModuleManager { get; set; }

foreach (var module in this.ModuleCatalog.Modules.Where(m => m.InitializationMode != InitializationMode.WhenAvailable))
{
    this.ModuleManager.LoadModule(module.ModuleName);
}

 

Happy coding!