Sommaire
Vous pouvez créer des applications graphiques et les déployer sur vos sentinelles UI grâce à Constellation.
Chaque package UI pourra invoquer ou exposer des MessageCallbacks, consommer ou produire des StateObjects, etc…
Hello World WPF
Dans Visual Studio, vous créez un package WPF :
Le template est une application WPF classique :
Le IPackage de ce package est la classe “App” (App.xaml.cs). Ce package lance la fenêtre MainWindow au démarrage (méthode “OnStart”).
La “MainWindow” est une Window WPF classique à l’exception que dans le constructeur, on enregistre automatiquement les [StateObjectLink] et les [MessageCallback] de la classe. De plus on renvoie la description du package (dans le cas où vous avez ajoutez des MessageCallbacks).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public partial class MainWindow : Window { public MainWindow() { PackageHost.RegisterStateObjectLinks(this); PackageHost.RegisterMessageCallbacks(this); PackageHost.DeclarePackageDescriptor(); InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { this.Title = string.Format("IsRunning: {0} - IsConnected: {1} - IsStandAlone: {2}", PackageHost.IsRunning, PackageHost.IsConnected, PackageHost.IsStandAlone); PackageHost.WriteInfo("I'm running !"); } } |
Au chargement de la fenêtre on logge un message dans Constellation et on affiche quelques propriété sur l’état du package dans le titre de cette fenêtre !
Ajoutons un simple label “Hello World” au centre de notre fenêtre :
1 |
<Label x:Name="label" Content="Hello World" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="48"/> |
Le code XAML sera donc:
1 2 3 4 5 6 7 8 |
<Window x:Class="MonPackageWPF.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded"> <Grid> <Label x:Name="label" Content="Hello World" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="48"/> </Grid> </Window> |
Pour tester notre package en debug sans être connecté à Constellation : “F5”
Vous noterez que les WriteLog Constellation sont toujours afficher dans la fenêtre de sortie de Visual Studio.
Maintenant pour lançons le debug de notre package dans Visual Studio tout en le connectant à Constellation (raccourci Ctrl+Alt+F8)
Invoquer des MessageCallbacks
Vous pouvez invoquer ou exposer des MessageCallbacks comme n’importe quel package connecté dans votre Constellation.
Pour exposer des MessageCallbacks (des méthodes NET), il suffit d’ajouter l’attribut [MessageCallback] sur vos méthodes.
Pour invoquer des MessageCallbacks, il faut créer un scope et envoyer le message. Grace au proxy dynamique, vous pouvez invoquer un MessageCallback comme vous invoquerez une méthode .NET.
Dans cet exemple nous allons invoquer des MessageCallbacks des packages WindowsControl et GoogleTraffic. Vous pouvez suivre le guide ici pour déployer ces deux packages.
En vous rendant sur la page “MessageCallbacks Explorer” de la Console, vous pouvez explorer les MC exposés par les packages.
Par exemple, le package WindowsControl expose plusieurs MessageCallbacks pour arrêter, redémarrer, mettre en veille ou verrouiller l’ordinateur (= la sentinelle) sur lequel le package est déployé.
Le package GoogleTraffic expose un MessageCallback “GetRoute” pour calculer le temps de route en spécifiant un point de départ et d’arrivée. Ce MessageCallback est une saga pour vous retourner la réponse.
Pour simplifier le développement et éviter de travailler avec des types dynamiques, nous allons auto-générer le code.
Cliquez sur le bouton “Generate Code” :
Sélectionnez votre Constellation, cliquez sur “Discover” et sélectionnez les packages que vous souhaitez ajouter dans le code généré puis cliquez sur “Generate” :
Editez le code de la MainWindow (MainWindow.xaml.cs) pour ajouter le code généré des MessageCallbacks pour les packages GoogleTraffic et WindowsControl :
1 2 |
using MonPackageWPF.GoogleTraffic.MessageCallbacks; using MonPackageWPF.WindowsControl.MessageCallbacks; |
Dans la vue XAML, ajoutons deux boutons : l’un pour mettre en veille et l’autre pour faire un test d’itinéraire :
1 2 |
<Button x:Name="btSleep" Content="Sleep !" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="10" Width="75" Click="btSleep_Click"/> <Button x:Name="btTestRoute" Content="Test Route" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="10,0,0,10" Width="75" Click="btTestRoute_Click" /> |
Pour le premier bouton, nous allons sélectionner l’instance du package “WindowsControl” sur la sentinelle “PO_SEB” pour créer un scope afin d’invoquer le MessageCallback “Sleep” :
1 2 3 4 |
private void btSleep_Click(object sender, RoutedEventArgs e) { MyConstellation.PackageInstances.PO_SEB_WindowsControl.CreateWindowsControlScope().Sleep(); } |
Prenez garde à créer un scope sur une instance (sentinelle + package) car sinon, si vous créez un scope sur le package “WindowsControl”, toutes les sentinelles hébergeant ce package se mettront en veille !
Pour le deuxième bouton, nous allons demander les différentes routes pour un “Lille-Paris”. Comme il s’agit d’une saga (message avec réponse) nous allons l’invoquer en “async/await” et afficher dans le label la meilleure route :
1 2 3 4 5 6 7 |
private async void btTestRoute_Click(object sender, RoutedEventArgs e) { label.Content = "Calcul en cours ..."; var route = await MyConstellation.Packages.GoogleTraffic.CreateGoogleTrafficScope().GetRoutes("lille", "paris"); var bestRoute = route.OrderBy(r => r.TimeWithTraffic).FirstOrDefault(); label.Content = $"{bestRoute.Name}\nDistance:{bestRoute.DistanceInKm}km\nTemps : {bestRoute.TimeWithTraffic}"; } |
Lancer le debug dans Constellation : (ou Ctrl+Alt+F8).
Premier test, en cliquant sur Sleep, vous allez envoyer un message pour invoquer le MessageCallback “Sleep” du package “WindowsControl” sur la sentinelle ici nommée “PO-SEB”. Ainsi le Windows “PO-SEB” se mettra instantanément en veille !
Deuxième test, pour invoquer le MessageCallback “GetRoutes” du package GoogleTraffic :
Consommer des StateObjects dans votre vue XAML
Assurez-vous d’avoir dans votre Constellation au moins un package “HWMonitor” déployé sur une sentinelle. Au besoin, vous pouvez suivre ce guide ici.
Pour comprendre en détail, la consommation des StateObjects dans vos packages n’hésitez pas à relire cet article.
Par exemple, pour afficher en temps réel la consommation CPU (StateObject nommé “/intelcpu/0/load/0”) mesurée par le package HWMonitor sur sa sentinelle (ici “PO-SEB”), ajoutons un StateObjectLink :
1 2 |
[StateObjectLink("PO-SEB", "HWMonitor", "/intelcpu/0/load/0")] public StateObjectNotifier CPU { get; set; } |
Vous pouvez également générer du code en sélectionnant le package HWMonitor. Vous pourrez ensuite ajouter un “using” vers les StateObjects de ce package :
1 |
using MonPackageWPF.HWMonitor.StateObjects; |
Cela vous permettra d’utiliser un le “HWMonitorStateObjectLink” avec des énumérations générées pour vos sentinelles et nom de StateObjects :
1 2 |
[HWMonitorStateObjectLink(MyConstellation.Sentinels.PO_SEB, HWMonitorStateObjectNames._intelcpu_0_load_0)] public StateObjectNotifier CPU { get; set; } |
Comme vous le savez, le StateObjectNotifier implémente l’interface INotifyPropertyChanged. Vous pouvez donc lier cette propriété dans votre vue XAML pour voir votre StateObject en temps réel sur votre interface.
Nous allons modifier le label “Hello World” pour afficher en temps réel votre consommation CPU. Pour cela changeons le contenu (Content) du label avec la propriété “Value” du StateObject publié par le package HWMonitor.
Cette propriété contient la valeur (ici de l’utilisation du CPU) avec un nombre décimal. Nous ajoutons également l’attribut “ContentStringFormat” pour n’afficher que 2 chiffres après la virgule.
1 |
<Label x:Name="label" Content="{Binding Path=CPU.DynamicValue.Value}" ContentStringFormat="{}{0:N2}%" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="48"/> |
Pour pourvoir utiliser des propriétés .NET comme binding dans votre vue XAML, vous devez spécifier le DataContext de votre fenêtre vers votre classe MainWindow soit via le code (this.DataContent = this) ou soit directement dans votre vue XAML en ajoutant cette attribut sur l’élément Window :
1 |
DataContext="{Binding RelativeSource={RelativeSource Self}}" |
Résultat, vous pouvez suivre en temps réel le CPU ici de la machine “PO-SEB” :
Allons un peu plus loin en ajoutons dans notre code C#, un nouveau StateObjectLink :
1 2 |
[HWMonitorStateObjectLink(HWMonitorStateObjectNames._intelcpu_0_load_0)] public StateObjectCollectionNotifier CPUs { get; set; } |
Ce “lien” ne précise que le nom du StateObject (ici “/intelcpu/0/load/0”) et le package (HWMonitorStateObjectLink est la classe générée qui spécifie implicitement le package à “HWMonitor).
De ce fait, toutes les consommations CPU mesurées par les instances du package HWMonitor seront captées par ce “link” ! On utilisera donc un StateObjectCollectionNotifier car on aura autant de StateObjects qu’on a d’instance de ce package.
Dans la vue XAML, ajoutons un menu déroulant (combobox) pour afficher le nom des sentinelles (= le nom des machines) des StateObjects de votre collection “CPUs” :
1 |
<ComboBox x:Name="comboBox" ItemsSource="{Binding Path=CPUs}" DisplayMemberPath="Value.SentinelName" HorizontalAlignment="Left" Margin="10" VerticalAlignment="Top" /> |
Enfin, modifions une nouvelle fois notre label. Cette fois ci la valeur à afficher n’est pas celle du StateObject “CPU”, mais celle du StateObject sélectionné par la combobox :
1 |
<Label x:Name="label" Content="{Binding ElementName=comboBox, Path=SelectedItem.DynamicValue.Value}" ContentStringFormat="{}{0:N2}%" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="48"/> |
On obtient donc la possibilité de suivre la consommation de chaque machine (= sentinelle) où le package HWMonitor est déployé :
Pour terminer on pourrait également ajouter un StateObjectLink qui contiendrait TOUS les StateObjects produits par les packages HWMonitor, peut importe le nom du StateObject et la sentinelle :
1 2 |
[HWMonitorStateObjectLink] public StateObjectCollectionNotifier HWMonitor { get; set; } |
Pour afficher toutes ces données, on peut utiliser un DataGrid lié à votre collection de StateObject “HWMonitor” :
1 |
<DataGrid ItemsSource="{Binding HWMonitor}"></DataGrid> |
On obtiendrait une vue avec deux colonnes, la propriété DynamicValue et Value des StateObjectNotifier :
Pour rendre cela plus visuelle, définissions explicitement des colonnes avec le nom de la sentinelle, le nom du StateObject, la propriété “Name” de la valeur du StateObject (le nom du compteur), la “Value” et l’unité de la mesure (Unit).
En XAML cela se traduit par le code suivant :
1 2 3 4 5 6 7 8 9 |
<DataGrid ItemsSource="{Binding HWMonitor}" AutoGenerateColumns="False" Margin="0, 0, 0, 40"> <DataGrid.Columns> <DataGridTextColumn Header="Sentinel" Binding="{Binding Path=Value.SentinelName}"></DataGridTextColumn> <DataGridTextColumn Header="StateObject name" Binding="{Binding Path=Value.Name}"></DataGridTextColumn> <DataGridTextColumn Header="Counter name" Binding="{Binding Path=DynamicValue.Name}"></DataGridTextColumn> <DataGridTextColumn Header="Value" Binding="{Binding Path=DynamicValue.Value}"></DataGridTextColumn> <DataGridTextColumn Header="Unit" Binding="{Binding Path=DynamicValue.Unit}"></DataGridTextColumn> </DataGrid.Columns> </DataGrid> |
Le résultat :
Prenez garde car les StateObjects sont mis à jour dans le StateObjectsCollectionNotifier par des threads différents. Vous risquez donc d’avoir des exceptions du type “An ItemsControl is inconsistent with its items source”. Afin d’éviter ce genre d’erreur, utilisez la méthode “BindingOperations.EnableCollectionSynchronization”.
Pour cela, dans votre classe, ajoutez un objet de synchronisation :
1 |
private static object _syncLock = new object(); |
Puis dans le constructeur de votre fenêtre, après le InitializeComponent(), activez la synchronisation de la collection sur votre StateObjectCollectionNotifier (ici nommé ‘HWMonitor’) :
1 |
BindingOperations.EnableCollectionSynchronization(HWMonitor, _syncLock); |
Si lancez votre package dans une Constellation avec plusieurs instances du packages HWMonitor sur vos différentes sentinelles, vous aurez une vision temps réel de l’ensemble de vos machines Windows avec seulement ces quelques lignes de XAML et Constellation :
Déployez votre package UI
Publier le package
Le sujet a été traité dans le guide de démarrage, il suffit de cliquer-droit sur votre projet et sélectionner dans le menu Constellation “Publish On Constellation” ou directement depuis la toolbar :
Vous pourrez alors choisir le type de publication (Local ou Upload sur le serveur Constellation) :
Déployer le package sur une sentinelle UI
Vous devez impérativement déployer un package “UI” sur une sentinelle UI. Si vous tentez d’ajouter un package UI sur une sentinelle service, le package démarrera mais aucune fenêtre ne pourra être visible (le service ne peut pas interagir avec le bureau Windows).
Les sentinelles UI ont le suffixe “_UI” dans leurs noms. Ici pour cette Constellation, il y a deux sentinelles connectées, l’une de type “Service” et l’autre “UI”, toutes deux sur la même machine (nommé “PO-SEB”).
Pour ajouter notre package à la sentinelle UI, vous pouvez éditer la configuration de vos Constellation directement depuis Visual Studio :
Ou bien depuis la Console Constellation :
Pour déployer la configuration, cliquez sur le bouton “Save & Deploy” depuis la Console, ou directement sur la page des “Packages” cliquez sur “Reload & Deploy”.
Votre package UI sera démarré et vous pourrez le contrôler depuis la Console comme pour les autres packages.
Démarrer son package “manuellement”
Si vous créez une application à destination d’une borne d’affichage, comme un miroir, le package est démarré automatiquement par la sentinelle (comportement par défaut) et est relancé si le package plante !
Cependant, si votre package est destiné à être utilisé par un utilisateur comme une application Windows classique vous voudriez certainement ne pas la lancer automatiquement au démarrage de la sentinelle. Au contraire vous voudriez que ce soit l’utilisateur qui décide de la lancer en lançant un raccourci Windows par exemple sans devoir se connecter sur la Console de votre Constellation.
Pour cela vous pouvez lancer la sentinelle en passant un ordre en paramètre :
1 |
Constellation.Sentinel.UI.exe <action> <package> |
Les actions peuvent être :
-
Start
-
Stop
-
Restart
-
Reload
Par exemple, créons un raccourci sur le bureau vers :
1 |
Constellation.Sentinel.UI.exe Reload MonPackageWPF |
Ainsi dès que vous double-cliquerez sur ce raccourci, la sentinelle téléchargera la dernière version du package sur le serveur et lancera votre package (Reload) :
Bien entendu, l’état du package sera automatiquement synchronisé dans la Constellation. Vous pourrez donc contrôler l’état du package depuis la Console par exemple.
Par défaut, une sentinelle démarre tous les packages qui lui sont assignés. Si c’est un package destiné à être lancé manuellement par l’utilisateur, vous voudriez peut être ne pas lancer automatiquement le package. Vous pouvez donc définir l’attribut “autoStart” à false au niveau de la configuration de votre package.
Aussi l’ordre d’arrêt d’un package doit provenir du hub de contrôle de Constellation, qui se chargera de communiquer l’ordre au package lui même (de s’arrêter) et à sa sentinelle (de tuer le package si il ne s’est pas arrêté dans le temps imparti).
Seulement, dans un package UI de ce type, c’est à dire “application Windows classique”, l’utilisateur fermera naturellement l’application en cliquant sur la croix rouge en haut à droite !
La sentinelle détectera la mort du processus du package alors qu’elle n’a pas eu l’ordre de Constellation d’arrêter le package ! Du point de vue de la sentinelle, c’est un arrêt brutal !
Elle appliquera donc les options de récupération qui par défaut redémarre un package 30 secondes après un arrêt brutal :
Les options par défaut sont définis dans la configuration de la Constellation :
Dans notre cas, nous allons redéfinir ces options de récupération au niveau du package lui même pour ne pas redémarrer un package suite à un arrêt forcé et ne pas démarrer automatiquement notre package au démarrage. La configuration du package sera :
Démarrez la discussion sur le forum Constellation