Saturday, September 4, 2010

Show loading while downloading XAP modules in prism.

Show loading while downloading XAP modules in prism.

Scenario : To show progress bar when modules (xap’s) in prism are downloaded.

Problem : Currently in prism the default ModuleManager class does not expose a progress event that can be tracked to show some progressing of the modules (xap’s) being downloaded.

Solution : Prism’s ModuleManager internally uses instance of XapModuleTypeLoader to load modules (xap’s) asynchronously. Thanks to the community of prism for providing CreateDownloader() as virtual method.

Straight to the point I will jot down the steps to achieve the above mentioned goal.

Step 1 - Create an event and an event argument classes that will be used in custom class below

[sourcecode language="csharp"]

public class ModuleDownloadProgressEvent : CompositePresentationEvent<ModuleDownloadProgressArgs>
{
}

public class ModuleDownloadProgressArgs
{
public ModuleDownloadProgressArgs(int BytesReceived, bool IsComplete) { _BytesReceived = BytesReceived; _IsComplete = IsComplete; }
private int _BytesReceived;
public int BytesReceived { get { return _BytesReceived; } set { _BytesReceived = value; } }
private bool _IsComplete;
public bool IsComplete { get { return _IsComplete; } set { _IsComplete = value; } }
}

[/sourcecode]

Step 2 - Create a class that derives from IFileDownloader.

[sourcecode language="csharp"]

public class CustomFileDownloader : IFileDownloader
{
private readonly WebClient webClient = new WebClient();
private readonly IEventAggregator eventAgg;

public CustomFileDownloader()
{
eventAgg = ServiceLocator.Current.GetInstance<IEventAggregator>();
}

private event EventHandler<DownloadCompletedEventArgs> _downloadCompleted;
public event EventHandler<DownloadCompletedEventArgs> DownloadCompleted
{
add
{
if (this._downloadCompleted == null)
{
this.webClient.OpenReadCompleted += this.WebClient_OpenReadCompleted;
this.webClient.DownloadProgressChanged += this.DownloadProgressChanged;
}

this._downloadCompleted += value;
}

remove
{
this._downloadCompleted -= value;
if (this._downloadCompleted == null)
{
this.webClient.OpenReadCompleted -= this.WebClient_OpenReadCompleted;
this.webClient.DownloadProgressChanged -= this.DownloadProgressChanged;
}
}
}

void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
RaiseDownloadProgressChanged(e.ProgressPercentage, false);
}

private void RaiseDownloadProgressChanged(int val, bool isComplete)
{
eventAgg.GetEvent<ModuleDownloadProgressEvent>().Publish(new ModuleDownloadProgressArgs(val, isComplete));
}

public void DownloadAsync(Uri uri, object userToken)
{
this.webClient.OpenReadAsync(uri, userToken);
}

private void WebClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
this._downloadCompleted(this, ConvertArgs(e));
RaiseDownloadProgressChanged(0, true);
}

private static DownloadCompletedEventArgs ConvertArgs(OpenReadCompletedEventArgs args)
{
return new DownloadCompletedEventArgs(args.Error == null ? args.Result : null, args.Error, args.Cancelled, args.UserState);
}
}

[/sourcecode]

Step 3 - Create a class that derives from XapModuleTypeLoader and override its virtual method CreateDownloader returning your class object prepared in step 1

[sourcecode language="csharp"]

public class CustomXapModuleTypeLoader : XapModuleTypeLoader
{
protected override IFileDownloader CreateDownloader()
{
return new CustomFileDownloader();
}
}

[/sourcecode]

Step 4 - Create a class that derives from ModuleManager and override its virtual method ModuleTypeLoaders replacing default XapModuleTypeLoader with your class object prepared in step 2.

[sourcecode language="csharp"]

public class CustomModuleManager : ModuleManager
{
public CustomModuleManager(IModuleInitializer moduleInitializer, IModuleCatalog moduleCatalog, ILoggerFacade loggerFacade)
: base(moduleInitializer, moduleCatalog, loggerFacade) { }
private System.Collections.Generic.IEnumerable<IModuleTypeLoader> typeLoaders;
public override System.Collections.Generic.IEnumerable<IModuleTypeLoader> ModuleTypeLoaders
{
get
{
if (this.typeLoaders == null)
{
this.typeLoaders = new List<IModuleTypeLoader>()
{
new CustomXapModuleTypeLoader()
};
}

return this.typeLoaders;
}
set
{
this.typeLoaders = value;
}
}
}

[/sourcecode]

Step 5 - Register your custom ModuleManager class prepared in step 4, in overridden method ConfigureContainer() of your Bootstrapper class.

[sourcecode language="csharp"]

base.RegisterTypeIfMissing(typeof(IModuleManager), typeof(CustomModuleManager), true);

[/sourcecode]

Step 6 – Create a method in Shell that will respond to the ModuleDownloadProgressEvent event when published

[sourcecode language="csharp"]

public void ShowHide(ModuleDownloadProgressArgs e)
{
// Your Dream Goes Here
}

[/sourcecode]

Step 7 – Finally in the Shell subscribe to the ModuleDownloadProgressEvent event prepared in Step 1.

[sourcecode language="csharp"]

eventAggregator.GetEvent<ModuleDownloadProgressEvent>().Subscribe(ShowHide, ThreadOption.UIThread);

[/sourcecode]

That’s it!!!

Enjoy Prism.