Tag Archives: wpf

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.

WPF Toolkit Chart – Custom tooltip on LineSeries charts

NET%20LogoA simple code example about how easily customize your charts tooltip. In this example I just display the value of the X and Y axis in the tooltip.

<chartingToolkit:Chart Title="Demo Chart">
	<chartingToolkit:LineSeries  DependentValuePath="Value" 
									IndependentValuePath="Key" 
									ItemsSource="{Binding}" 
									IsSelectionEnabled="True">
		<chartingToolkit:LineSeries.DataPointStyle>
			<Style TargetType="chartingToolkit:DataPoint">
				<Setter Property="Background" Value="#0077CC" />
				<Setter Property="BorderBrush" Value="White"/>
				<Setter Property="BorderThickness" Value="2"/>
				<Setter Property="IsTabStop" Value="False"/>
				<Setter Property="Template">
					<Setter.Value>
						<ControlTemplate TargetType="chartingToolkit:LineDataPoint">
							<Grid x:Name="Root" Opacity="1">
								<ToolTipService.ToolTip>
									<StackPanel Margin="2,2,2,2">
										<ContentControl Content="{TemplateBinding IndependentValue}" ContentStringFormat="Date : {0}"/>
										<ContentControl Content="{TemplateBinding DependentValue}" ContentStringFormat="Count : {0:###,###,###}"/>
									</StackPanel>
								</ToolTipService.ToolTip>
								<Ellipse StrokeThickness="{TemplateBinding BorderThickness}" Stroke="{TemplateBinding BorderBrush}" Fill="{TemplateBinding Background}"/>
							</Grid>
						</ControlTemplate>
					</Setter.Value>
				</Setter>
			</Style>
		</chartingToolkit:LineSeries.DataPointStyle>
	</chartingToolkit:LineSeries>
</chartingToolkit:Chart>

WPF, Webbrowser & Content DataBinding

Aujourd’hui j’ai été confronté a un petit problème. Je voulais afficher les résultats d’une recherche avec une mise en forme un peut travailler à la manière des résultats retourné par les recherches dans l’ancien HelpViewer de la MSDN 2008. (Bien meilleur que celui actuel, malgré le retour en arrière fait sur le v1.1 qui reste énormément moins ergonomique à mon gout et beaucoup plus lent, fin’ bref…)

Pour cela j’avais l’idée de rajoute comme result viewer un webbrowser intégré dont le contenu serait bindé a un string contenant la page html avec les résultats mis en forme. Seul hic a cette solution, impossible actuellement de binder le contenu d’un webbrowser avec le moteur de binding wpf…

Voici donc une petite Attached Property qui vous permettra de passer outre ce petit problème.

public class WebBrowserHelper {
    public static readonly DependencyProperty BodyProperty =
           DependencyProperty.RegisterAttached("Body",
                                               typeof(string),
                                               typeof(WebBrowserHelper),
                                               new PropertyMetadata(OnBodyChanged));

    public static string GetBody(DependencyObject dependencyObject)
    {
        return (string)dependencyObject.GetValue(BodyProperty);
    }

    public static void SetBody(DependencyObject dependencyObject, string body)
    {
        dependencyObject.SetValue(BodyProperty, body);
    }

    private static void OnBodyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var webBrowser = (WebBrowser)d;
        webBrowser.NavigateToString((string)e.NewValue);
    }
}

Il suffit de rajouter la classe et de rajouter une référence vers son mainspace dans les xmlns de votre contrôle. Ensuite on l’utilise comme ceci :

<WebBrowser src:WebBrowserHelper.Body="{Binding MyHtml}" />

A vous les joie du binding avec un control WebBrowser en Wpf ^^

Inform@tiquement
Istace Emmanuel

Src : http://thomasfreudenberg.com/blog/archive/2010/08/01/binding-webbrowser-content-in-wpf.aspx

Mvvm Light : Partie 2 – Commandes & Events

Bonjour, voici la seconde partie de cette serie de 3 webcast sur mvvm light.

Cette deuxième vidées s’attache a la gestion des événements et la communication entre view et viewmodel.

NOTE : Le problème de stretch a 14″00 vennait du fait que l’host de la listbox est un stackpanel et pas un grid ‘–

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

AD&D – Another Drag & Drop en WPF

Bonjour à tous, si certains sont tombé ici en cherchant des informations sur le célèbre jeu de rôle Advanced Donjon et Dragon, ils peuvent passer leur chemin, par contre, si des fonctionnalités de drag’n’drop en WPF vous intéresse, ce qui vas suivre vas vous intéresser. Descendez dans le labo et tuez le gobelin Hobo qui garde la porte afin de récupéré le 6éme statuette du Dieu Khewl et ainsi déjouer ses plans démoniaque !

Présentation du Drag’n’Drop

Le drag’n’drop consiste à transporter des informations de manière visuelle à l’aide d’un pointeur. Dans la plupart des cas il s’agira d’une souris, mais si vous avez la chance d’avoir une device tactile, votre doigt pourra également faire ce drag’n’drop. On peut décomposer en 2 le drag’n’drop, le drag, qui consiste à choisir les données, souvent déclenché par le clic gauche de la souris et le drop qui dépose les données, le plus souvent au moment où l’on relâche le bouton gauche de la souris.

L’application

Notre application comprend 3 éléments de type label. Deux représentant des armes un le gobelin Hobo qui garde la porte. Le but étant d’attaquer le gobelin en déplaçant l’arme choisie sur le gobelin. A ce moment-là, le label de Hobo indiquera qu’il a reçu une attaque et quel arme l’a attaqué. Pendant le déplacement une status bar dans le bas se mettra a jours.


Et voici le code XAML, assez simple :

<Window x:Class="DND.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="AD&amp;D - Another Drag And Drop" Height="250" Width="350">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="28"/>
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Name="data1"
Grid.Row="0" Grid.Column="0"
Content="Rapière" Foreground="CornflowerBlue"  />
<Label Name="data2"
Grid.Row="0" Grid.Column="1"
Content="Fronde" Foreground="Crimson"/>
<Label Name="receiver"
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
Content="Hobo le goblin, endormi devant la porte du laboratoire..."  Foreground="Lime" Background="Black"/>
<StatusBar Name="Status"
Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" >
<StatusBarItem Name="StatusContent" />
</StatusBar>
</Grid>
</Window>

Création du Drag

Pour réaliser le Drag il suffit de rajouter un gestionnaire d’évènement sur le mouse move. Ainsi, si la souris survole l’élément, l’évènement sera déclenché.
Voici à quoi ressemblent nos deux labels après cela :

<Label Name="data1"
 Grid.Row="0" Grid.Column="0"
 Content="Rapière" Foreground="CornflowerBlue"
 MouseMove="pickData_MouseMove" />

 <Label Name="data2"
 Grid.Row="0" Grid.Column="1"
 Content="Fronde" Foreground="Crimson"
 MouseMove="pickData_MouseMove" />

Maintenant, il faut coder notre gestionnaire d’évènement

private void pickData_MouseMove(object sender, MouseEventArgs e)
 {
 if (e.LeftButton == MouseButtonState.Pressed)
 {
 DragDropEffects effects;
 DataObject obj = new DataObject();
 Label source = (Label)sender;
 obj.SetData(typeof(string), source.Content);
 effects = DragDrop.DoDragDrop(source, obj, DragDropEffects.Copy | DragDropEffects.Move);
 }
 }

Dans un premier temps on vérifie l’état de la souris afin de savoir sur le bouton gauche est pressé. Si oui on crée un objet de donnée dans lequel on stockera la valeur du label cliqué puis on « copiera » les données dans le presse papier avec la source et des options définissant les actions possible pour le drag’n’drop. Si vous rajouter un Messagebox, vous verrez qu’il s’affichera quand vous cliquerez puis, tout en laissant la souris cliqué, bougerez la souris.

Création du Drop

Pour le drop nous allons utiliser deux évènements, un premier qui est le DragOver et le second le Drop.

Voici le code Xaml du contrôle avec le gestionnaire d’évènements en plus :

<Label Name="receiver"
 Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
 Content="Hobo le goblin, endormi devant la porte du laboratoire..."
 Foreground="Lime" Background="Black"
 AllowDrop="True" DragOver="receiver_DragOver" Drop="receiver_Drop"/>

Le premier sera utilisé pour notifier dans la statusbar l’action qui va se produire, dans le cas ou le presse papier contient des données et que le type de donnée est celui accepté.

private void receiver_DragOver(object sender, DragEventArgs e)
 {
 if (e.Data.GetDataPresent(typeof(string)))
 this.StatusContent.Content = "Action : Hobbo vas se prendre un coup...";
 else
 e.Effects = DragDropEffects.None;
 }

Dans le second on récupère les données et on met a jours le label ainsi que la statusbar dans le cas ou des données du bon type sont présente dans le presse papier.

private void receiver_Drop(object sender, DragEventArgs e)
 {
 if (e.Data.GetDataPresent(typeof(string)))
 {
 e.Effects = DragDropEffects.Copy;
 string weapon = (string)e.Data.GetData(typeof(string));
 this.receiver.Content += ("\n... se prend un coup de " + weapon);
 this.StatusContent.Content = "";
 }
 else
 e.Effects = DragDropEffects.None;
 }

Résultat

Voici ce que cela nous donne, dans une petite vidéo de 20 secondes :