Press "Enter" to skip to content

5 – MiniSqlAgent – Avviare e fermare un servizio da una console WPF – Uso dei DispatcherTimer, dei RoutedCommands e dei Converters

In questo post illustriamo come costruire il codice necessario ad avviare e fermare un servizio da una applicazione WPF e nell’effettuare questa dimostrazione spieghiamo come costruire alcuni converter, come modificare lo stile di un controllo in base alla variazione di un dato sul View Model del contesto, e spiega come utilizzare i Command di WPF per pilotare i menu, sia agganciando i Command di sistema forniti dalle librerie, sia creando dei command specifici per le esigenze delle nostre applicazioni.

Nei post precedenti questo, abbiamo visto come creare un servizio windows, che per ora scrive esclusivamente delle stringhe su un file di log, abbiamo visto come creare dei parametri di configurazione per vedere come il servizio funziona. Abbiamo iniziato a costruire una console di amministrazione del servizio per dare una interfaccia utente per la modifica dei parametri di configurazione del servizio. Facciamo un passo avanti ed aggiungiamo alla nostra console la possibilità di avviare e fermare il servizizo a cui è collegata. Vediamo come:

Il progetto DnwBaseServices

Considerato che nel nostro futuro potremmo creare molti servizi Windows, ma che non tutte le nostre applicazioni avranno la necessità di poter controllare dei servizi, creiamo una libreria contenente le classi helper che servono allo scopo ma separata dalla libreria di base principale, in modo da non inserire codice inutile nelle applicazioni in cui non serve.

Vediamo come è fatto il progetto che sarà aggiunto alla soluzione DnwLibraries.

ConsoleSolution_02_01

Abbiamo implementato due classi nel progetto.

ConsoleSolution_02_02

Abbiamo dato il nome alla nuova libreria secondo il pattern da noi deciso per DotNetwork ed abbiamo predisposto il namespace di default e l’icona per la libreria.

ConsoleSolution_02_03

Abbiamo poi effettuato le solite modifiche al post build event per pubblicare la libreria e riutilizzarla, abbiamo firmato la libreria e attivato la generazione della documentazione XML come per tutte le librerie della soluzione. Vediamo ora le due classi che abbiamo inserito nella libreria.

La classe ServiceManager

Questa classe contiene il necessario a controllare un servizio, è stata definita in questa libreria soprattutto perché è in grado di gestire qualsiasi servizio e perché ne potremmo dovere usare più di una se ad esempio facessimo una console che controlla più di un servizio.

public class ServiceManager
{
ServiceController mController;
public void Restart()
	{
		Stop();
		Start();
	}
public ServiceManager(string service)
	{
		mController = new System.ServiceProcess.ServiceController(service, System.Net.Dns.GetHostName());
	}
public void Start()
	{
if (!this.CanStop)
		{
			mController.Start();
		}
	}
public void Stop()
	{
if (this.CanStop)
		{
			mController.Stop();
		}
	}
public bool CanStop
	{
get
		{
return this.mController.CanStop;
		}
	}
public ServiceControllerStatus Status
	{
get
		{
return (this.mController.Status);
		}
	}
}

Si tratta di una classe molto semplice, che instanzia un ServiceController per il servizio indicato al costruttore (Attenzione che è il ServiceName quindi controllate bene), funziona sulla macchina locale, quindi si aspetta di girare sulla macchina su cui il servizio è installato e per quello che vogliamo fare noi al momento è logico. Una console di servizio si installa assieme al servizio, in futuro potremmo anche implementare una console remota ma per ora limitiamoci a camminare.

Usando il controller, la classe è in grado di fornire informazioni o di fermare, avviare o riavviare il servizio.

La classe ServiceHelper

public static class ServiceHelper
	{
	
public static ServiceControllerStatus CheckServiceState(string pServiceName)
	{
try
		{
ServiceControllerStatus ret = ServiceControllerStatus.Stopped;
try
			{
ServiceManager serviceManager = new ServiceManager(pServiceName);
				ret = serviceManager.Status;
			}
catch
			{
//Ignora le eccezioni
			}
	
return (ret);
		}
catch (Exception ex)
		{
EventLogger.SendMsg(ex);
throw;
		}
	}
	
public static ServiceControllerStatus SwitchService(
ServiceControllerStatus pStartState, string pServiceName)
	{
try
		{
ServiceManager serviceManager = new ServiceManager(pServiceName);
string msg = string.Empty;
if (pStartState == ServiceControllerStatus.Running)
			{
if (serviceManager.CanStop)
				{
					serviceManager.Stop();
					msg = string.Format(Properties.Resources.warSHLPServiceStopRequested, pServiceName);
				}
			}
else
			{
if (!serviceManager.CanStop)
				{
					serviceManager.Start();
					msg = string.Format(Properties.Resources.warSHLPServiceStartRequested, pServiceName);
				}
			}
EventLogger.SendMsg(msg, MessageType.Info);
return (serviceManager.Status);
		}
catch (Exception ex)
		{
EventLogger.SendMsg(ex);
throw;
		}
	}
}

Si tratta di una classe Helper in grado di controllare lo stato e di fermare o far ripartire un servizio in base allo stato in cui si trova.

La User Interface WPF

Adesso che abbiamo il necessario a creare le funzionalità di controllo del servizio dobbiamo applicarle alla User Interface, per fare questo ne approfittiamo per spiegare un concetto proprio di WPF, l’uso dei Routed Commands per pilotare i menu o i bottoni dell’interfaccia utente. Per la spiegazione dettagliata di cosa sono e come funzionano, vi rimando a due articoli interessanti che trovate su MSDN:

Entrambi gli articoli sono tradotti anche in italiano, ma chi segue questo blog conosce le nostre preferenze relative ai concetti.

Facciamo un riassunto delle cose che interessano a noi nell’uso dei Routed Commands.

  • I Routed Commands, sono uno speciale tipo di evento WPF che ci permette di scrivere in un unico punto il codice relativo ad una azione che l’interfaccia utente esegue e agganciare questo codice a uno o più elementi dell’interfaccia utente che la scateneranno.
  • Ci sono tutta una serie di Routed commands di uso comune che sono già definiti in WPF, possiamo utilizzarli senza doverli ridefinire in tutte le nostre applicazioni. Nel nostro esempio, utilizzeremo uno degli ApplicationCommands per gestire la chiusura dell’applicazione.
  • Possiamo comunque definire dei RoutedCommands nostri, che riguardano la specifica applicazione oppure di uso generico per tutte le nostre applicazioni. Nel nostro esempio scriveremo 4 routed commands, uno per la gestione dell’interfaccia di configurazione dell’applicazione, altri tre per la gestione di comandi di tipo semaforico (Start, Stop, Pause).

consolesolution_03_01

Partiamo dai commands personalizzati che abbiamo creato per le nostre applicazioni. Siamo quasi certi che in tutte le applicazioni WPF che scriveremo avremo bisogno di un sistema per la configurazione dell’applicazione, quindi abbiamo creato una classe con il primo comando, per aprire la finestra di configurazione.

namespace Dnw.Base.Wpf.Commands
{
 
public static class ToolsCommands
	{
public static readonly RoutedCommand OpenConfig = new RoutedCommand();
	}
}

Definire un routed command è ancor più facile che definire una DependencyProperty. Quello che dobbiamo fare è instanziarlo staticamente una volta al primo uso. Da quel momento sarà disponibile a tutta la User Interface.

La seconda classe di commands si occupa di fornire quelli che ho chiamato dei comandi semaforici, ovvero Start, Stop, Pause.

public static class SemaphoreCommands
{
public static readonly RoutedCommand Start = new RoutedCommand();
public static readonly RoutedCommand Pause = new RoutedCommand();
public static readonly RoutedCommand Stop = new RoutedCommand();
}

Anche in questo caso la classe è molto semplice. Ora che abbiamo i nostri comandi personalizzati, modifichiamo la console del servizio per utilizzarli.

Le modifiche alla User Interface della Console del Servizio

console_UI_03_01

Questo è il risultato finale delle modifiche alla MainWindow della console vediamo che cosa abbiamo modificato.

Il Menu del Servizio

<MenuItem Header="{x:Static p:Resources.mnuMWService}"  	VerticalAlignment="Center" Margin="10,2,10,2"  	Padding="10,4,10,4"  Height="32" >
<MenuItem  	Header="{x:Static p:Resources.mnuMWStartService}"  	VerticalAlignment="Center"  	Margin="2,4,2,2" Height="32"  	Command="{x:Static gcmds:SemaphoreCommands.Start}">
<MenuItem.Icon>
<Image Source="/MiniSqlAgentConsole;component/Images/btn_032_226.png"     Height="24" Width="24"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem  	Header="{x:Static p:Resources.mnuMWStopService}"  	VerticalAlignment="Center"  	Margin="2,4,2,2" Height="32"  	Command="{x:Static gcmds:SemaphoreCommands.Stop}">
<MenuItem.Icon>
<Image Source="/MiniSqlAgentConsole;component/Images/btn_032_227.png"     Height="24" Width="24"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>

Abbiamo aggiunto il menu per le opzioni relative al servizio inserito fra il menu File ed il menu Tools. Abbiamo evidenziato il fatto che invece di utilizzare il classico Click delle versioni precedenti a questo post, abbiamo invece utilizzato il Command del MenuItem assegnandogli una risorsa contenuta in un namespace, gcmds che troverete definito nel tag Window dello XAML:

xmlns:gcmds ="clr-namespace:Dnw.Base.Wpf.Commands;assembly=Dnw.Base.Wpf.v4.0"

Il namespace include i Commands che abbiamo definito nella libreria di base.

Proseguiamo con le modifiche visuali alla Window:

<Grid Grid.Row="2" Margin="0" Background="#FFECECEC">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="33*"/>
<ColumnDefinition Width="33*"/>
<ColumnDefinition Width="33*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal" FlowDirection="LeftToRight">
<Image Source="{Binding ServiceStatus,  	Converter={StaticResource serviceStatusToImageConverter}}"     Width="24" Height="24" Margin="5,2,5,2" VerticalAlignment="Center" />
<TextBlock Style="{StaticResource statusStyle}" Text="{Binding ServiceStatus,  	Converter={StaticResource serviceStatusToStringConverter}}"  	HorizontalAlignment="Stretch" VerticalAlignment="Center"  	Margin="5,2,5,2" Padding="5" />
</StackPanel>
</Grid>

La barra di stato, per così dire per l’applicazione, in cui visualizziamo un icona ed una stringa che mostrano qual’è lo stato del servizio. In questo segmento di codice, abbiamo utilizzato il Binding alla property ServiceStatus della finestra ed abbiamo utilizzato due converters, che trasformano lo stato del servizio in una immagine in un caso, e in una stringa nell’altro. I converters sono uno degli strumenti più utili per rendere l’interfaccia utente più utile senza la necessità di creare codice complesso a livello di Model. Oltre ai converters abbiamo anche definito uno stile per il Textblock, che abbiamo definito nelle risorse della Window:

<Window.Resources>
<lconv:ServiceStatusToImageConverter x:Key="serviceStatusToImageConverter"/>
<lconv:ServiceStatusToStringConverter x:Key="serviceStatusToStringConverter"/>
<lconv:ServiceStatusToBoolConverter x:Key="serviceStatusToBoolConverter" />
<Style TargetType="{x:Type TextBlock}" x:Key="statusStyle">
<Setter Property="Background" Value="White" />
<Setter Property="Foreground" Value="Navy" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<DataTrigger Binding="{Binding ServiceStatus,  	Converter={StaticResource serviceStatusToBoolConverter}}" Value="False">
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontWeight" Value="Bold" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>

Nelle risorse della Window, abbiamo dichiarato i tre converters, che andremo a illustrare più avanti in questo post, ed abbiamo definito una stile per il TextBlock che abbiamo chiamato statusStyle ed ha una peculiarità, infatti ha un DataTrigger, questo tipo di trigger ci permette di dire: Lo stile cambia quando il valore di ServiceStatus viene modificato. Ovvero Se il valore dello status del servizio, convertito in un boolean è true il testo è blu su sfondo bianco, se il valore dello status del servizio convertito in boolean è invece false, il background è giallo, il carattere è rosso e grassetto.

Vediamo come abbiamo modificato anche le opzioni di menu già usate nei post precedenti:

<MenuItem Header="{x:Static p:Resources.mnuMWFile}"  	VerticalAlignment="Center" Margin="10,2,10,2"  	Padding="10,4,10,4" Height="32" >
<MenuItem  	Header="{x:Static p:Resources.mnuMWExit}"  	VerticalAlignment="Center"  	Command="ApplicationCommands.Close"
	Margin="2,4,2,2" Height="32" >
<MenuItem.Icon>
<Image Source="/MiniSqlAgentConsole;component/Images/btn_032_157.png"     Height="24" Width="24"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem  Header="{x:Static p:Resources.mnuMWTools}"  	VerticalAlignment="Center" Margin="10,2,10,2"  	Padding="10,4,10,4"  Height="32" >
<MenuItem  Header="{x:Static p:Resources.mnuMWConfig}"  	VerticalAlignment="Center"  	Command="{x:Static gcmds:ToolsCommands.OpenConfig}"  	Margin="2,4,2,2" Height="32" >
<MenuItem.Icon>
<Image Source="/MiniSqlAgentConsole;component/Images/btn_032_160.png"  	Height="24" Width="24"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>

Anche in questo caso abbiamo agganciato alle opzioni di menu un command, nel caso della finestra di configurazione è quello da noi definito, quindi è uguale a quelli introdotti per le nuove opzioni di menu, ma nel caso del menu Exit invece abbiamo utilizzato uno dei Command forniti da WPF, assegnandolo semplicemente indicando il suo nome, senza alcun tipo di Binding, questo perché i command sono definiti a livello di namespace base e quindi già disponibili all’uso.

Per far funzionare i Command inseriti sui controlli ovviamente dobbiamo anche andare ad indicare loro qual’è il codice che devono eseguire, questo lo dobbiamo effettuare nella parte iniziale della definizione della Window esattamente nel tag Window.CommandBindings:

<Window.CommandBindings>
<CommandBinding  	Command="{x:Static gcmds:ToolsCommands.OpenConfig}"
	Executed="OpenConfig_Executed"/>
<CommandBinding
	Command="{x:Static gcmds:SemaphoreCommands.Start}"
	CanExecute="Start_CanExecute"
	Executed="StartStop_Executed" />
<CommandBinding
	Command="{x:Static gcmds:SemaphoreCommands.Stop}"
	CanExecute="Stop_CanExecute"
	Executed="StartStop_Executed" />
<CommandBinding  	Command="ApplicationCommands.Close"
	Executed="Close_Executed"/>
</Window.CommandBindings>

Ogni Routed command è costruito in modo da scatenare due eventi: CanExecute ed Executed.

  • CanExecute è eseguito ogni volta che il controllo che usa il command assume il focus, in questo evento inseriremo il codice di controllo per attivare o disattivare il comando.
  • Executed è l’evento in cui inserire il codice da eseguire.

Nel nostro caso abbiamo stabilito che: OpenConfig e Close sono sempre eseguibili, infatti non vi sono controindicazioni in merito. Invece Start e Stop hanno ciascuno un metodo di controllo, Start_CanExecute e Stop_CanExecute, che disattivano l’opzione di menu se non può essere usata. Tutti e quattro i command hanno un event handler collegato ad Executed.

Il CodeBehind della Window

Le modifiche alla soluzione, oltre ad avere aggiunto le immagini necessarie ai nuovi menu item e alla segnalazione dello status del servizio, abbiamo aggiunto anche quattro classi, 3 converters e il ViewModel per la MainWindow.

consolesolution_03_02

La classe MainWindow

La prima modifica che abbiamo deciso di effettuare alla nostra MainWindow, visto che cominciamo a costruire del codice non banale dietro al suo funzionamento, è quella di introdurre un ViewModel per custodirne la logica e le property da mettere in Binding con i controlli. Vediamo quindi come abbiamo cambiato il contenuto della classe della finestra:

public partial class MainWindow : Window
{
MainWindowModel mWindowModel;
public MainWindow()
	{
		InitializeComponent();
this.Icon = BitmapFrame.Create(new Uri("pack://application:,,,/msaIco.ico", UriKind.RelativeOrAbsolute));
		mWindowModel = new MainWindowModel();
this.DataContext = mWindowModel;
		
	}

Abbiamo creato il MainWindowModel, e lo abbiamo assegnato al data context della Window.

private void Window_Loaded(object sender, RoutedEventArgs e)
{
	mWindowModel.StartCheckServiceStatus();
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
	mWindowModel.StopCheckServiceStatus();
}

Abbiamo introdotto due event handler per il caricamento e la chiusura della finestra, In questi due eventi attiviamo e disattiviamo un timer che aggiorna lo stato del servizio. La sua logica è implementata nel ViewModel e la vedremo fra poco, concludiamo prima l’ispezione di cià che è nel code behind della finestra principale:

private void OpenConfig_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
	mWindowModel.OpenConfig(this);
}
private void Start_CanExecute(object sender, System.Windows.Input.CanExecuteRoutedEventArgs e)
{
	e.CanExecute = mWindowModel.ServiceStatus != System.ServiceProcess.ServiceControllerStatus.Running;
}
private void Stop_CanExecute(object sender, System.Windows.Input.CanExecuteRoutedEventArgs e)
{
	e.CanExecute = mWindowModel.ServiceStatus == System.ServiceProcess.ServiceControllerStatus.Running;
}
private void StartStop_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
	mWindowModel.SwitchService();
}
private void Close_Executed(object sender, ExecutedRoutedEventArgs e)
{
this.Close();
}

Nella finestra principale abbiamo anche gli event handler per tutti i command, anche se la loro logica è scritta nel ViewModel, vediamo comunque il controllo per l’attivazione e disattivazione dello Start e dello Stop del servizio e il comando per farlo partire o farlo fermare, inoltre il comando per chiamare la window di configurazione e per chiudere l’applicazione.

La classe MainWindowModel

public class MainWindowModel : INotifyPropertyChanged
{
private ChildWindowsCollection mOpenWindows;
private DispatcherTimer mCheckServiceTimer;
private ServiceControllerStatus mServiceStatus;
public MainWindowModel()
	{
		mOpenWindows = new ChildWindowsCollection();
		mCheckServiceTimer = new DispatcherTimer();
		mCheckServiceTimer.Tick += CheckServiceTimer_Tick;
	}

Nel View Model abbiamo spostato la collezione delle finestre figlie della nostra MainWindow e la logica ad essa collegata, abbiamo inoltre un DispatcherTimer che useremo per verificare lo stato del servizio e mdoficare di conseguenza lo stato della finestra principale, il DispatcherTimer è un timer che si trova nel namespace System.Threading ed ha un funzionamento equivalente a quello del Timer delle Windows Forms.

void CheckServiceTimer_Tick(object sender, EventArgs e)
{
this.mServiceStatus = ServiceHelper.CheckServiceState(ServiceContext.SERVICE_NAME);
	OnPropertyChanged(FLD_ServiceStatus);
}
public void StartCheckServiceStatus()
{
	mCheckServiceTimer.Interval = new TimeSpan(0, 0, 10);
	mCheckServiceTimer.Start();
}
public void StopCheckServiceStatus()
{
	mCheckServiceTimer.Stop();
}

I tre metodi che permettono di gestire il Timer, il primo è l’event handler dell’evento Tick, che va ad aggiornare lo stato del servizio utilizzando l’helper che abbiamo discusso all’inizio di questo post. Gli altri due metodi fanno partire e fermano il timer e sono quelli chiamati dai due event handler della finestra principale. Abbiamo arbitrariamente indicato 10 secondi come intervallo di controllo dello stato del servizio, questo è uno di quei parametri che si prestano ad essere inseriti fra i parametri di configurazione applicativi.

public ServiceControllerStatus ServiceStatus
{
get
	{
return mServiceStatus;
	}
}

L’unica property del ViewModel, lo stato del servizio, che è in sola lettura e non implementa il PropertyChanged, come fa a funzionare? Perché l’evento Tick del timer, la aggiorna e solleva l’evento in vece sua.

public void RemoveChild(string childName)
{
ChildWindow child = mOpenWindows[childName];
if (child != null)
	{
		mOpenWindows.Remove(child);
	}
}
public ChildWindow GetChild(string childName)
{
return (mOpenWindows[childName]);
}
public void AddChild(string childName, Window win)
{
	mOpenWindows.Add(new ChildWindow(childName, win));
}

I tre metodi che gestiscono la collezione delle child window (li abbiamo discussi nel post precedente questo).

public void OpenConfig(Window owner)
{
ChildWindow child = GetChild(WIN_Config);
ConfigWindow win = null;
if (child == null)
	{
		win = new ConfigWindow();
		win.Icon = owner.Icon;
		win.Owner = owner;
		win.Closed += delegate { RemoveChild(WIN_Config); };
		AddChild(WIN_Config, win);
		win.Show();
	}
else
	{
		win = (ConfigWindow)child.Window;
		win.BringIntoView();
	}
}

Il metodo di gestione della Window di configurazione, che abbiamo trasferito dalla MainWindow, modificandone solo l’event handler dell’evento closed che è divenuto un metodo anonimo.

public void SwitchService()
{
try
	{
this.mServiceStatus =
ServiceHelper.SwitchService(this.mServiceStatus,
ServiceContext.SERVICE_NAME);
	}
catch (InvalidOperationException)
	{
MessageBox.Show("The operation required is not possible at the moment, retry in a few seconds.",
"Warning!", MessageBoxButton.OK, MessageBoxImage.Warning);
	}
catch (Exception ex)
	{
EventLogger.SendMsg(ex);
throw;
	}
}

Il metodo che fa partire o ferma il servizio, che viene chiamato dalla MainWindow negli event handler dei command. Usa semplicemente l’helper da noi inserito nelle librerie dedicate ai servizi per la sua operazione.

Le tre classi converter

ValueConversion(typeof(ServiceControllerStatus), typeof(string))]
public class ServiceStatusToStringConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
	{
string result = null;
ServiceControllerStatus input = (ServiceControllerStatus)value;
if (input == ServiceControllerStatus.Running)
		{
			result = Properties.Resources.txtSSTSCServiceIsRunning;
		}
else
		{
			result = Properties.Resources.txtSSTSCServiceIsNotRunning;
		}
return result;
	}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
	{
return value;
	}
}

Il converter che genera una stringa in base allo stato del servizio, ed è utilizzato per il Text del Textblock della finestra di stato.

[ValueConversion(typeof(ServiceControllerStatus), typeof(BitmapImage))]
public class ServiceStatusToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
	{
BitmapImage result = null;
ServiceControllerStatus input = (ServiceControllerStatus)value;
	
if (input == ServiceControllerStatus.Running)
		{
	
			result = new BitmapImage(new Uri("pack://application:,,,/MiniSqlAgentConsole;component/Images/btn_032_135.png")); ;
	
		}
else
		{
			result = new BitmapImage(new Uri("pack://application:,,,/MiniSqlAgentConsole;component/Images/btn_032_134.png")); ;
		}
	
	
return result;
	
	}
	
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
	{
	
return value;
	
	}
}

Il converter che genera l’immagine del semaforo in base allo stato del servizio, lo utilizziamo per il valore Source dell’Image dello stato sulla User Interface.

[ValueConversion(typeof(ServiceControllerStatus), typeof(bool))]
public class ServiceStatusToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
	{
string param = parameter as string;
if (!(param == null))
		{
			param = param.ToLower().Trim();
		}
bool result = false;
ServiceControllerStatus input = (ServiceControllerStatus)value;
if (input == ServiceControllerStatus.Running)
		{
			result = true;
		}
if (param == "invert")
		{
return !result;
		}
return (result);
	}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
	{
return value;
	}
}

Il converter che utilizziamo per lo stile del TextBlock dello stato, ritorna True o False in base al fatto che il servizio sia partito o meno. Ha però una peculiarità rispetto agli altri, ovvero è in grado di poter funzionare anche come Inverter, ovvero se avessimo la necessità di avere ad esempio 2 controlli che si attivassero l’uno quando il servizio funziona l’altro quando il servizio è fermo, basterebbe usare il converter qui sopra scritto, in un caso senza parametro, nell’altro con il parametro = “invert”.

Conclusioni

In questo lungo Post abbiamo illustrato i seguenti concetti:

  • Come controllare lo stato, far partire o fermare un servizio windows da una applicazione .Net.
  • Come creare dei RoutedCommands personalizzati per le nostre applicazioni.
  • Come utilizzare i RoutedCommands personalizzati e quelli invece forniti da WPF in una delle nostre User Interface
  • Come creare dei converter per trasformare un valore in qualcosa di diverso e collegarlo ad una porzione della user interface.
  • Come definire uno stile i cui contenuti sono Triggerati da un valore all’interno del datamodel collegato al contesto.

 

Il codice del progetto con le modifiche discusse in questo articolo è disponibile al link seguente:

Il codice delle librerie di uso comune utilizzate nel progetto sono disponibili al link seguente: