Category Archives: Visual Studio

Visual Studio 2008 et 2010

Integrate Syncrosoft Metro Studio 2 in your development process.

syncmslogoAs a lot of WPF developer, I love the free Metro Studio from Synchrosoft. Most of my WPF projects, professional or not, use it at a moment or another. But, out of the box, the integration between Metro Studio and Visual Studio is not great. At the end of this article you’ll be able to regenerate dynamically your resource file on project build. Also you’ll be able to do this on a build server as we don’t need Metro Studio to be installed on the build machine.

The issue

Here’s a usual situation:

  • Create a project in Metro Studio and pick icons.
  • Configure some basic styling stuff.
  • Copy paste the generated output to visual studio.
  • Adapt the templates to match the needs or just extract the path information
  • Tweak the styling.

There’s a lot of issues when working that way. If you want theming, if you want to add/remove/update icons, if you want to change the overall style… The generate xaml feature of MetroStudio is nice but it’s a real trap too. In fact, the only useful information from MetroStudio are the path. So our goal will be to retrieve them and manage on our side all the styling.

The solution

First, let’s take a look at how Metro Studio manage his project. Here’s a “.metrop” xml structure :

<IconProject Version="2.0" Name="LogiX">
    <Icon Name="AddApplicationPath" GroupName="Office" Data="M21.686602,15.029519C20.406802,... And so on" HasCharacterMap="false" IsDirty="false" ExportCommand="MetroGraphicsPackage.IconCommand">
        <Settings FontFamily="Webdings" Character=">" 
				  IconBrush="#FFFFFFFF" 
				  FlipCommand="MetroGraphicsPackage.IconCommand" 
				  FlipX="1" 
				  FlipY="1" 
				  ContentHeight="26" 
				  ContentWidth="26" 
				  SizeIndex="4" 
				  MainWidth="48" 
				  Angle="0" 
				  MainHeight="48" 
				  Padding="11" 
				  CustomSize="48" 
				  MaximumPadding="15" 
				  BackgroundBrush="#FF67C2EA" 
				  IconShape="Square" 
				  IsBackgroundVisible="true" />
    </Icon>
</IconProject> 

A metrop file is a basic xml file containing all the informations about an icon. Including the path data.

To retrieve them and produce a resource dictionary with them decided to use XSLT.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
	<xsl:template match="/">
		<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib">
			<xsl:for-each select="/IconProject/Icon">
				<system:String>
					<xsl:attribute name="x:Key">
						<xsl:value-of select="@Name"/>
					</xsl:attribute>
					<xsl:value-of select="@Data" />
				</system:String>
			</xsl:for-each>
		</ResourceDictionary>
	</xsl:template>
</xsl:stylesheet>

The path is stored in a string object, much more flexible than in a path. If you want to generate entire controls or use other attribute from the metrop file, you just need to adapt the xslt file. But you should be careful IMHO. If you decide to generate more complex code with the xslt you’ll need someone to maintain it too in the future. As it’s a WPF application the other persons working on the project will be able to maintain the styles using the path in string but not always able to maintain complex XLST sheets. Also it can be hard to debug and the debug can quickly require additional tools like Oxygen XML Developer or similar.

To apply the stylesheet to the metrop file I use msxsl.exe, a lightweight (25kb) tool from Microsoft allowing you to apply xslt to xml file. And to call the tool I simply add the following prebuild event line to execute it :

"$(ProjectDir)/Resources/Tools/msxsl.exe"
"$(ProjectDir)/Resources/Icons/LogiX.metrop"
"$(ProjectDir)/Resources/Icons/Generate.xslt"
-o "$(ProjectDir)/Resources/Icons/IconsPath.xaml"

In a generic way :

"<Path to msxsl.exe>" "<Metrop file path>" "<Xslt file path>" -o "<Output xaml file>"

Now, everytime you do modification in metro studio you’ll just need to rebuild the project.

Also don’t forget to include your output xaml file in your solution. For my part I usually also include the metrop file and add the generated xaml as a dependent generated item. To do this simply edit your csproj file like the following. For the parent metrop file, just include it as none action (in my case located in “Resources\Icons\LogiX.metrop”):

<None Include="Resources\Icons\LogiX.metrop" />

And here’s the entry for the generated xaml page, changes are in Bold. Basically we say to visual studio that this is an autogenerated item and the parent is LogiX.metrop :

<Page Include="Resources\Icons\IconsPath.xaml">
      <SubType>Designer</SubType>
      <AutoGen>True</AutoGen>
      <DependentUpon>LogiX.metrop</DependentUpon>
      <Generator>MSBuild:Compile</Generator>
</Page>

And here’s the result in the solution explorer:

screendepautogen

Also, to complete this easy integration, you can add metrop studio as a custom editor for the metrop files, allowing you to directly open the project from visual studio. Right click on the metrop file, “Open With…”, “Add”, browse MetroStudio executable, press “Ok”, now select the newly created item in the list and press “Set as default” and then Ok, now when you double click metrop files they’re opened with MetroStudio.

Example

Here’s a template for a button. Basically the button is filled with the icon. To use it, you’ll need to set the style and the path as the content of the button.


<Style TargetType="{x:Type Button}" x:Key="MetroIconButtonStyle">
	<Setter Property="Template">
		<Setter.Value>
			<ControlTemplate TargetType="Button">
				<Viewbox>
					<Grid Width="48" Height="48">
						<Grid>
							<Rectangle Fill="{DynamicResource IconBackground}" x:Name="bg"/>
						</Grid>
						<ContentPresenter x:Name="contentPresenter" Width="30" Height="30" />
					</Grid>
				</Viewbox>
				<ControlTemplate.Triggers>
					<Trigger Property="IsMouseOver" Value="True">
						<Setter TargetName="contentPresenter" Property="Opacity" Value="1" />
						<Setter TargetName="bg" Property="Opacity" Value=".7" />
						<Setter TargetName="bg" Property="Fill" Value="{DynamicResource IconBackgroundOver}" />
					</Trigger>
					<Trigger Property="IsMouseOver" Value="False">
						<Setter TargetName="contentPresenter" Property="Opacity" Value=".7" />
						<Setter TargetName="bg" Property="Opacity" Value="1" />
						<Setter TargetName="bg" Property="Fill" Value="{DynamicResource IconBackground}" />
					</Trigger>
					<Trigger Property="IsPressed" Value="True">
						<Setter TargetName="contentPresenter" Property="Opacity" Value="1" />
						<Setter TargetName="bg" Property="Opacity" Value="1" />
						<Setter TargetName="bg" Property="Fill" Value="{DynamicResource IconBackgroundPressed}" />
					</Trigger>
				</ControlTemplate.Triggers>
			</ControlTemplate>
		</Setter.Value>
	</Setter>
	<Setter Property="ContentTemplate">
		<Setter.Value>
			<DataTemplate>                
				<Path Data="{Binding}" Stretch="Uniform" Fill="{DynamicResource IconForeground}" RenderTransformOrigin="0.5,0.5" />
			</DataTemplate>
		</Setter.Value>
	</Setter>
</Style>

Usage

<Button Content="{DynamicResource AddApplicationIconPath}" Style="{StaticResource MetroIconButtonStyle}" />

Once it’s done…

Possible improvement would be to create a real MSBuild custom Task and integrate it to be able to use mscsl.exe as a custom generate build instead of a pre build event, but I’m not sure about the real benefit of the solution compared to the actual one, does it worth the price? It’s up to you J

That’s all! If you have questions to not hesitate to contact me.

Visual Studio Code Snippet : Dependency Property et NotifyPropertyChange

UPDATE 1 : Pour des raisons que je ne détaillerai pas ici, certaines snippets ne référençait pas “http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet&#8221; comme xmlns. Le code et le zip est màj avec le “bon” xmlns.

Voici une série de code snippet que je me propose de partager avec vous. Elles sont souvent simples et certaines peuvent sembler un peu outdated, mais comme souvent, le developpement de snippet passe après la documentation et le nettoyage du clavier (vive les sandwich “croustillants” 😉 ). J’ai dev ces snippets au passage à VS2010 et leur migration vers VS2012 s’est déroulée avec succès, j’espère qu’elles vous seront toutes aussi utiles qu’à moi 🙂 La première c’est une snippet de Dprop pour WPF, une configProperty et les 3 autres sont des 3 styles de NotifPropChange dépendant de la version et du contexte d’utilisation. Je reviendrai certainement dans les jours qui suivent avec d’autres.

Dependency Property

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>dprop</Title>
      <Shortcut>dprop</Shortcut>
      <Description>Code snippet for dependecy property</Description>
      <Author>Emmanuel Istace</Author>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>name</ID>
          <ToolTip>Property name</ToolTip>
          <Default>Property</Default>
        </Literal>
        <Literal>
          <ID>type</ID>
          <ToolTip>Property Type</ToolTip>
          <Default>object</Default>
        </Literal>
        <Literal Editable="false">
          <ID>classname</ID>
          <ToolTip>Class name</ToolTip>
          <Function>ClassName()</Function>
          <Default>ClassNamePlaceholder</Default>
        </Literal>
      </Declarations>
      <Code Language="csharp">
        <![CDATA[
        
        public static readonly DependencyProperty $name$Property = DependencyProperty.Register("$name$", typeof($type$), typeof($classname$));

        public $type$ $name$
        {
            get { return ($type$)GetValue($name$Property); }
            set { SetValue($name$Property, value); }
        }

        $end$
]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

Configuration Property

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>configprop</Title>
      <Shortcut>configprop</Shortcut>
      <Description>Add a configuration property</Description>
      <Author>Emmanuel Istace</Author>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
    </Header>
    <Snippet>
      <Declarations>
          <Literal>
            <ID>Name</ID>
            <ToolTip>Property Name</ToolTip>
            <Default>MyProperty</Default>
          </Literal>    
          <Literal>
            <ID>type</ID>
            <ToolTip>Property Type</ToolTip>
            <Default>string</Default>
          </Literal>
      </Declarations>
      <Code Language="csharp">
        <![CDATA[
        [ConfigurationProperty("$Name$", IsRequired = true)]
        public $type$ $Name$
        {
          get { return ($type$)this["$Name$"]; }
          set { this["$Name$"] = value; }
        }
        $end$
]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

INotifyPropertyChanged simple implementation

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>nchangeimpl</Title>
      <Shortcut>nchangeimpl</Shortcut>
      <Description>Code snippet for INotifyPropertyChanged Simple implementation</Description>
      <Author>Emmanuel Istace</Author>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
    </Header>
    <Snippet>
      <Code Language="csharp">
        <![CDATA[
        #region INotifyPropertyChanged Implementation
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(string sProp)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(sProp));
            }
        }
        #endregion]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

Property Changed Event raising

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
	<CodeSnippet Format="1.0.0">
		<Header>
			<Title>nprop</Title>
			<Shortcut>nprop</Shortcut>
			<Description>Code snippet for property with property change event raised</Description>
			<Author>Istace Emmanuel</Author>
			<SnippetTypes>
        <SnippetType>Expansion</SnippetType>
			</SnippetTypes>
		</Header>
		<Snippet>
			<Declarations>
				<Literal>
					<ID>type</ID>
					<ToolTip>Type exposed by the dependency property</ToolTip>
					<Default>object</Default>
				</Literal>
				<Literal>
					<ID>name</ID>
					<ToolTip>Name of the dependency property</ToolTip>
					<Default>dprop</Default>
				</Literal>
			</Declarations>
			<Code Language="csharp">
				<![CDATA[
				
				#region $name$ property
				private $type$ _$name$ { get; set; }
				public $type$ $name$
				{
					get { return _$name$; }
					set
					{
					 if(value != _$name$)
					 {
					  _$name$ = value;
					  NotifyPropertyChanged("$name$");
					 }
					}
				}
				#endregion
				$end$
				
				]]>
			</Code>
		</Snippet>
	</CodeSnippet>
</CodeSnippets>

MVVM Property Changed event raising

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
	<CodeSnippet Format="1.0.0">
		<Header>
			<Title>mvvmlightprop</Title>
			<Shortcut>mvvmlightprop</Shortcut>
			<Description>Code snippet for MVVM view model property with property change event raised</Description>
			<Author>Istace Emmanuel</Author>
			<SnippetTypes>
        <SnippetType>Expansion</SnippetType>
			</SnippetTypes>
		</Header>
		<Snippet>
			<Declarations>
				<Literal>
					<ID>type</ID>
					<ToolTip>Type exposed by the dependency property</ToolTip>
					<Default>object</Default>
				</Literal>
				<Literal>
					<ID>name</ID>
					<ToolTip>Name of the dependency property</ToolTip>
					<Default>dprop</Default>
				</Literal>
			</Declarations>
			<Code Language="csharp">
				<![CDATA[
				
				#region $name$ property
				private $type$ _$name$ { get; set; }
				public $type$ $name$
				{
				  get { return _$name$; }
				  set
				  {
					  if(value != _$name$)
					  {
						  $type$ oldValue = _$name$;
						  _$name$ = value;
						  RaisePropertyChanged<$type$>("$name$", oldValue, value, false);
					  }
				  }
				}
				#endregion
				$end$
				]]>
			</Code>
		</Snippet>
	</CodeSnippet>
</CodeSnippets>

Télécharger l'archive contenant les snippets. (SkyDrive)Télécharger l’archive contenant les snippets. (SkyDrive)

WebSocket avec ASP.NET 4.5 et Microsoft.Websockets

Websocket est un standard qui permet de « développer un canal de communication bidirectionnel et full-duplex sur un socket TCP pour les navigateurs et les serveurs web. » C’est que l’on fait actuellement lorsque l’on utilise des XMLHttpRequest avec Ajax. En dotnet SignalR permettait déjà de profiter de ces features, maintenant il est possible de le faire en dotnet avec ASP.NET 4.5 à certaines conditions :

–          Application sur un IIS 8 avec la feature WebSockets

–          .NET 4.5

–          Un client compatible (IE10, Chrome 13+, Safari 5+, Opera 5+)

Premièrement, assurez-vous que le WebSocket est activé sur IIS.  Ensuite, créez un projet ASP.net et ajoutez le package NuGet Microsoft.Websockets. C’est la manière la plus simple d’obtenir l’assembly vu le DLL Hell de Microsoft.Websockets. Ajoutez aussi le package JQuery (1.8.2 dans ce cas-ci), il nous fournit une API JavaScript nous permettant de consommer des WebSockets.

Pour le reste il nous faut 3 fichiers ici. Un index.html qui contiendra le formulaire de chat ainsi que le code javascript pour l’établissement de la connexion et l’envoi/reception des données avec le websocket, un HTTPhandler qui fournira le websocket au httpcontext et notre implémentation du websocket basé sur Microsoft.Web.WebSockets.WebSocketHandler qui se base sur l’API Websocket du W3C. Pour le reste, je vous laisse lire les commentaires détaillés du code. La solution est également disponible via mon skydrive.

index.html

<!doctype html>
    <head>
        <script src="Scripts/jquery-1.8.2.min.js" type="text/javascript"></script>
        <script type="text/javascript">
            
            $(document).ready(function () {
                // Obtention du pseudo
                var name = prompt('Pseudo :');
                
                // Url du websocket, pour de l'https utiliser wss://
                var url = 'ws://' + window.location.hostname + window.location.pathname.replace('index.htm', 'socket.ashx') + '?name=' + name;
                
                //Affichage de l'url de connection et création websocket
                alert('Connecting to: ' + url);
                
                ws = new WebSocket(url);
        
                // On Websocket open, notification utilisateur
                ws.onopen = function () {
                    $('#messages').prepend('Connected <br/>');
                    // On Click on Send, envoi du contenu de txtMessage
                    $('#cmdSend').click(function () {
                        ws.send($('#txtMessage').val());
                        $('#txtMessage').val('');
                    });
                };
        
                // On Receive message on websocket, Ajouter le message à la chat box
                ws.onmessage = function (e) {
                    $('#chatMessages').prepend(e.data + '<br/>');
                };

                // On Click on Leave Button, fermer le websocket
                $('#cmdLeave').click(function () {
                    ws.close();
                });

                // On Close websocket, notification utilisateur
                ws.onclose = function () { $('#chatMessages').prepend('Closed <br/>'); };

                // On Error on the websocket, notification utilisateur
                ws.onerror = function (e) { $('#chatMessages').prepend('Oops something went wront <br/>'); };

            });

        </script>
    </head>
    <body>
        <input id="txtMessage" />
        <input id="cmdSend" type="button" value="Send" />
        <input id="cmdLeave" type="button" value="Leave" />
        <br />
        <div id="chatMessages" />
    </body>
</html>

TestWebSocketHandler.cs

using Microsoft.Web.WebSockets;

namespace Istace.Blog.WebSockets
{
    /// <summary>
    /// WebSocket Handler
    /// </summary>
    /// <remarks>
    /// Implémentation de la W3C WebSocket API
    /// http://www.w3.org/TR/2009/WD-websockets-20091222/
    /// </remarks>
    public class TestWebSocketHandler : WebSocketHandler
    {
        /// <summary>
        /// List of the connected connectedClients.
        /// </summary>
        private static readonly WebSocketCollection ConnectedClients = new WebSocketCollection();

        /// <summary>
        /// Socket Name
        /// </summary>
        private string _name;

        /// <summary>
        /// Occurs when WebSocket open.
        /// </summary>
        public override void OnOpen()
        {
            // Get the name
            _name = WebSocketContext.QueryString["name"];
            // Add client to the client list
            ConnectedClients.Add(this);
            // Broadcast to connected connectedClients the connection message
            ConnectedClients.Broadcast(_name + " has connected.");
        }

        /// <summary>
        /// Occurs when WebSocket receieve message
        /// </summary>
        /// <param name="message"></param>
        public override void OnMessage(string message)
        {
            // broadcast the message to the registred connectedClients
            ConnectedClients.Broadcast(string.Format("{0} said: {1}", _name, message));
        }

        /// <summary>
        /// Occurs when the WebSocket close
        /// </summary>
        public override void OnClose()
        {
            // Remove the instance from the connected client list.
            ConnectedClients.Remove(this);
            // Broadcast the deconnection message to the registred clients.
            ConnectedClients.Broadcast(string.Format("{0} has gone away.", _name));
        }
    }
}

socket.ashx

using System.Web;
using Microsoft.Web.WebSockets;

namespace Istace.Blog.WebSockets
{
    /// <summary>
    /// WebSocket HTTP Handler
    /// </summary>
    public class WSHttpHandler : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            if (context.IsWebSocketRequest)
                context.AcceptWebSocketRequest(new TestWebSocketHandler());
        }

        public bool IsReusable { get { return false; } }
    }
}

Websockets with ASP.net 4.5 and Visual Studio 2012
WebSocket on MSDN
W3C WebSocket API Reference

File lock lors de build avec Visual Studio

Voici un petit workaround aux soucis de process qui continuent de s’executer après la fin de la debugging sessions sous visual studio.

 

Unable to copy file “obj\Debug\MyAddin1.dll” to “bin\MyAddin1.dll”. The process cannot access the file ‘bin\MyAddin1.dll’ because it is being used by another process.

Comme beaucoup (du moin j’imagine) quand le soucis se produisait je retournais dans le task manager killer les process responsable ou utilisait un des outils de sysinternals pour trouver quel process gardait un lock sur le fichier.

Le workaround est assez simple. Rajoutez dans le Pre Build event de la solution les lignes suivantes :

if exist "$(TargetPath).locked" del "$(TargetPath).locked"
if exist "$(TargetPath)" if not exist "$(TargetPath).locked" move "$(TargetPath)" "$(TargetPath).locked"

Maintenant tout fonctionne (il me semble que les deux lignes parles suffisamment d’elles-mêmes que pour ne pas êtres détaillées)

Désactiver l’hebergement automatique des library de services WCF

Cette feature serait apparament en backlog pour la prochaine release/update? de visual studio si on s’en tiens a : http://connectbeta.microsoft.com/VisualStudio/feedback/details/533316/cannot-disable-wcf-service-host

Mais pour le supprimer le seul moyen est d’aller supprimer le ProjectTypeGUID et le ProjectExtensions dans le fichier .csproj de la lib. Le GUID est 3D9AD99F-2412-4246-B90B-4EAA41C64699.

Il faut donc le supprimer de :

    <ProjectTypeGuids>{3D9AD99F-2412-4246-B90B-4EAA41C64699};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>

Et supprimer la section suivante dans la fin du fichier :

  <ProjectExtensions>
    <VisualStudio>
      <FlavorProperties GUID="{3D9AD99F-2412-4246-B90B-4EAA41C64699}">
        <WcfProjectProperties>
          <AutoStart>True</AutoStart>
        </WcfProjectProperties>
      </FlavorProperties>
    </VisualStudio>
  </ProjectExtensions>

Background Worker en C# : Et Dieu papota

Salut à tous ! Aujourd’hui, nous allons parler des travailleurs de l’ombre, les threads. Mais pas n’importe quel thread, le background worker, une encapsulation des threads classiques très utile dans le cas d’utilisation d’interface graphique. Ras-le-bol d’avoir un freeze de votre IHM lors d’une connexion à une DB ou un traitement lourd ? Alors le background workers est fait pour vous ^^ (Intervention divine en sus)

Au commencement il n’y avait que le néant et Dieu en haut à gauche, près de la machine à café

Pour commencer à jouer de manière confortable avec les background workers, pas besoin de se retaper la théorie sur les locks, la synchronisation et tout le tsouin-tsouin habituel des threads, 3 events, 2 attributs et 3 méthodes suffisent pour rendre votre application fluide comme un long fleuve tranquille 😀

Pour utiliser les BackgroundWorkers vous devez ajouter une référence à System.Threading. Dans le cadre de ce how to, l’exemple choisi est un chronomètre threadé qui tourne séparément, l’interface étant mise à jour séquentiellement.

Voici le code XAML de l’interface en WPF : http://pastebin.com/TQGDNjJZ

Et voici le code-behind en C# que nous allons compléter au fur et à mesure : http://pastebin.com/QcndwztY

Et le code final qu’il serait bien d’utiliser pour suivre les explications ^^ : http://pastebin.com/2xuy7v2j

Ce qui nous donnera :

Au niveau du code, on a un timespan qui représentera la durée affichée à l’utilisateur et notre bgworker que l’on abordera au point suivant.

Dans l’IHM on a donc un label, qui affichera le temps et trois boutons, start, pour (re)lancer le chronomètre, pause et reset. Le chronomètre pouvant être mis en pause puis reprise ou remis à zéro avec reset.

Une petite précision au passage, il se peut que le chronomètre mette quelques centièmes de seconde à s’arrête ; en fait, le chronomètre s’est bien arrêté au moment du clic, il s’agit juste que la mise à jour du label qui subit un peu de latence. (Elle peut être visible si votre machine rame un peu, ce qui arrive toujours quand on a Outlook, firefox, Word, Visual Studio, le debugger de lancée attaché à l’application et de la musique avec winamp 😉 )

Lors du premier jour Dieu dit « définit les attributs seront » et définis les attributs furent (jeune jedi)

Pour commencer on va initialiser notre objet de manière standard.
Ceci étant fait, il y a deux principaux attributs, ReportProgress et SupportConcelation.

Le premier nous permettra, pendant l’exécution du thread, d’appeler une fonction que l’on définira après. Cette fonction sera appel avec la méthode ReportProgress. Elle est très utile pour mettre à jour pendant le traitement l’interface graphique. Par exemple pour une progress bar ou, dans notre cas, afficher le temps.

En effet, notre chronomètre sera en fait un thread infini qui ne s’arrêtera que quand on le mettra en pause ou quand on le resettera, donc afin de pouvoir mettre à jour l’interface, on utilisera dans cette boucle un appel à cette fonction.

Le second permet d’arrêter à tout moment le thread, sans cela, le thread ne s’arrêtera que lorsque son traitement sera terminé. Ce qui nous arrange pas des masses dans ce cas-ci vu que l’on stoppera le thread à la main ^^:/

On va donc mettre ces deux attributs à true.

Lors du second jour, Dieu dit « codés tes handler seront » et codés les handler furent.

Ensuite on rentre dans le vif du sujet, les backgrounds workers reposent sur les événements. Si vous n’êtes pas familiarisés avec cette notion, reportez-vous à  la msdn sur <link MSDN TODO>les routed events</link>. Pour tout de même suivre la suite, les événements peuvent être dans leur forme basique (mais qui suffira ici) des sortes de variables qui prennent comme valeur une méthode. Cette méthode sera exécutée au moment où il sera déclenché au moyen par exemple d’une autre méthode. Par exemple, on peut avoir un événement Work qui une fois déclenché exécute la méthode DisplayNames() et est déclenché par DoWork(), ainsi quand on appelleDoWork, l’événement déclenché exécute la méthode associée DisplayName. L’avantage étant que l’on peut définir de manière dynamique la méthode exécutée par l’événement et, par exemple, l’adapter à la situation.

Ces méthodes, qui sont exécutées lors du déclenchement d’un événement, sont appelées des handlers, que l’on peut traduire par gestionnaires car ils gèrent l’action effectuée lors du déclenchement de l’événement.

Pour les backgrounds workers on a accès à 4 handlers dont 3 seront utilisés ici.

Je vais tout d’abord présenter succinctement les événements et donc leur handler afin d’avoir une vision d’ensemble du processus.

Le premier, le principal, est doWork. C’est le handler qui réalisera le cœur de la tâche que l’on veut threader. Dans notre cas, il incrémentera le chronomètre. Les handlers doivent vous être familiers si vous avez déjà travaillé avec wpf par exemple car très grandement utilisés. Ce thread ne peut dialoguer avec le thread gérant l’ihm, mais les gens de chez Microsoft n’étant pas des branques, ils ont tout prévu et nous verrons plus loin comment contourner de manière élégante ce petit problème.

Ensuite viens le handler reportProgress, ce handler permet de dialoguer directement avec l’ihm. C’est à cet endroit que l’on pourra par exemple mettre à jour l’ihm.

Il existe encore deux événements, RunWorkerCompleted, lui, est exécuté lorsque le bgworker s’est exécuté jusqu’au bout et le dernier, disposed, est exécuté lorsque l’on aura libéré le worker une fois fini.

Le handler pour doWork prend deux arguments, le premier est le bgworker lui-même. L’intérêt de faire cela est que l’on peut ainsi, de l’intérieur du thread obtenir son état ainsi que ses méthodes et événements. Le second argument est un doWorkEventArgs. Il comporte trois champs. Le premier, Arguments, peut être utilisé pour transmettre des arguments au thread lors de son lancement, le second, Cancel, permet de savoir si le thread doit ou non être stoppé et enfin Result permet de stocker la valeur de retour de notre thread qui sera principalement utilisée dans RunWorkerCompleted.

Nous avons maintenant presque tout pour comprendre notre méthode doWork, à la première ligne on récupère le worker passé en argument et on le cast. Ensuite on crée un objet DateTime qui représentera le moment ou le chronomètre est lancé. Ensuite, on entre dans une boucle infinie. Dans cette boucle un if est présent. Il teste la valeur CancellationPending qui sera à true si l’on demande l’arrêt du thread. Si c’est le cas, on sort de la boucle avec un break, sinon on appelle ReportProgress dont voici le contenu :

Report Progress est similaire à DoWork excepté qu’il prend en argument un integer qui représente le pourcentage où en est la tâche et des arguments « libres ». Dans notre cas, on n’utilisera pas le pourcentage, on peut donc le laisser à un à chaque appel. Dans le cas où l’on aurait une progressbar par exemple, cette valeur serait très utile afin de définir l’état d’avancement de la barre. Ensuite on peut passer un second argument de type object, en gros ce qu’on veut.

Le handler prend en argument un ProgressEventChangedArgs qui contient deux arguments, un premier avec la valeur du pourcentage et le second avec l’objet passé en argument. Cette méthode peut interagir avec l’ihm, c’est donc  ici que l’on mettra à jour notre label sans que le debugger le hurle à la mort que l’on viole une quelconque restriction de portée 😀
L’objet passé en argument étant de type timespan, on le re-cast, le convertit en string et l’affectons à la valeur du Text du label.

Retournons maintenant sur doWork, pour obtenir le décompte. C’est assez simple, on prend le temps au moment de l’appel de la fonction ProgressChanged moins le temps au moment du début du thread. Et afin de pouvoir relancer le chronomètre, on y ajoute la valeur de last qui est la valeur qu’avait le thread lors de la mise en pause que nous allons voir tout de suite. A chaque itération, on met le thread en pose sinon il va utiliser tout le CPU et bien que l’action soit parallélisée, elle aura tendance à freezer la machine. Ici je mets un sleep de 10 millisecondes ce qui est suffisant pour permettre aux autres process de s’exécuter. (à part quelques chanceux munis de FPGA ou d’un cluster de calcul, on reste quand à quelques process réellement parallèles, 8 étant déjà beaucoup, et quand on sait qu’il y en a des 10énes qui tournent simultanément).

Lorsque l’utilisateur demande à stopper le thread, on sort de la boucle via le break. Ensuite on assigne la valeur courante de, comme pour un progress changed a Result. Ceci permettra de récupérer cette valeur une fois dans la méthode RunWorkerComplete. Notre méthode doWork se termine et c’est maintenant, la dernière, runWorkerComplete qui s’exécute.

Ce qui est intéressant ici c’est le RunWorkerCompletedEventArgs qui contient trois attributs, tout d’abord Cancelled qui permet de savoir si l’opération a été annulée ou non, Error qui récupère les erreurs possibles lors de l’exécution du thread et enfin Result qui contient la valeur que nous avons affectée précédemment dans doWork. Je pourrais très bien ne rien faire ici dans notre cas, mais voulant permettre de mettre en pause le chronomètre, je vais ici stocker dans last la valeur actuelle du chronomètre afin de pouvoir par la suite l’additionner au temps lors de la reprise. Addition que nous effectuons donc dans DoWork au moment du ReportProgress.

Bon, bien y a plus qu’à tester tout cela.

Lors du troisième jour Dieu dit « DoWork tu feras ». Et DoWork il fit.

Pour lancer le bgworker, rien de plus simple :

On vérifie qu’il n’est pas déjà lancé, et si ce n’est pas le cas, on le lance avec ma méthode runWorkerAsync. Et voilà…  Il tourne ^^ Au moment où on lance cela, l’événement DoWork est déclenché et donc le handler associé est exécuté.

Ensuite pour le mettre en pause il suffit de vérifier qu’il est bien lancé (il n’appréciera pas que l’on veuille mettre en pause un worker qui ne fait rien) et si c’est le cas on appelle CancelAsync .
Le reste est déjà codé, c’est dans DoWork, via  CancellationPending, que l’on sortira de la boucle et vous connaissez la suite.

Enfin, pour le stopper, c’est-à-dire le réinitialiser à zéro, idem que pour la pause sauf que l’on remet ensuite last à nul et, pour une question d’esthétique, le label à « 0 ».

Chacune de ces commandes sont bien entendu bindées dans le code XAML et appelées via un des buttons control. Je prépare en fait un second article, plus ciblé sur WPF, dans lequel je réutiliserai cet exemple de chronomètre, d’où l’utilisation de command et command binding plutôt que le simple mouseclick event handler. Mais ça fonctionnera tout aussi bien avec un event handler, je vous rassure ^^

Lors du quatrième jour, Dieu en glanda pas deux… En utilisant un background worker, il avait gagné la moitié de son temps.

Et voilà, it works ! Pour ceux qui utilisaient les threads classiques, je pense que l’efficacité des bgworkers et surtout leur simplicité d’utilisation n’est plus à démontrer. Avec quelques handlers assez simples on a un thread pleinement fonctionnel. Il est vrai que l’exemple ici est très simple et inutile, mais c’est dans le but de ne pas rajouter à la compréhension des bgworkers, la compréhension de la tâche à exécuter qui n’a pas de rapport avec le sujet. Si j’avais pris un exemple avec une requête sur un serveur de base de données, un traitement lourd et long, un processus serveur/client, une parallélisassion de programme, … la complexité inhérente à la compréhension de la tâche effectuée aurait fortement handicapé ce how to, expliquer le calcul du chronomètre en deux lignes ok, mais tout LinqToX… c’est autre chose…

En résumé, les bgworkers c’est :

3 attributs : Est-ce que je peux le stopper manuellement ? Est-ce qu’il rapporte l’avancement ? Est-ce qu’il attend d’être arrêté ?

4 events : La tâche, le rapport de l’avancement de la tâche, la fin de la tâche, la libération du worker

3 méthodes : RunAsync pour débuter, CancelAsync pour stopper, IsBusy pour savoir son état

Une fois ces quelques éléments en tête, il n’y a plus grand-chose de compliqué.

Lors des 5, 6 et 7émes jours… faut combler… Chabbat bien et vous ?

Le prochain article, qui se basera sur celui-ci, parlera de l’utilisation des notificationt askbar de windows 7 avec WPF4 afin de proposer des contrôles dans la miniature de prévisualisation. Ceci en complément de mon précédent article sur le drag’n’drop.

Et pour répondre aux posts, non je ne vis pas reclus depuis deux semaines, oui je sais, cela fait 2 semaines que je suis à presque 2 articles par jour et surtout non ce n’est pas du copié/collé, d’ailleurs, j’ai vu il y a 3 jours que c’était l’inverse (hein Rodt-G :p )

Inform@tiquement

Istace Emmanuel – Manu404