Press "Enter" to skip to content

Datagrid WPF standard un caso d’uso reale (parte 4)

Formattazione condizionale celle in base al contenuto usando i converter

Nell’Articolo precedente abbiamo costruito la bozza della nostra User Interface creando la DataGrid che useremo per gestire i nostri dati di test. Adesso iniziamo a implementare il necessario per le funzionalità più interessanti.

La nostra window, al momento ha il seguente aspetto:

La prima cosa che faremo sarà trasformare la colonna Trend da una semplice stringa in una immagine che renda meglio l’idea del suo significato a colpo d’occhio, per farlo per prima cosa generiamo un converter.

Se volete leggere una discussione più ampia sui converter, potete leggere questo articolo di qualche tempo fa che spiega alcune cose interessanti riguardo i converter.

Per creare il converter, utilizziamo il menu contestuale come abbiamo fatto nella prima parte.

 

Creiamo una nuova cartella posizionandoci sul Progetto e chiamiamola Converters.

Una volta creata la cartella Converters, ancora una volta usiamo il menu contestuale e Aggiungiamo una nuova classe.

Chiamiamo la classe TrendToImageConverter.cs negli esempi e nella documentazione, ho sempre trovato che i converter vengono chiamati con un nome che è per quanto possibile significativo di cosa converte e terminano con il nome Converter. La trovo una buona convenzione per capire a colpo d’occhio l’uso di queste classi. Vediamo ora cosa inserire nella nostra classe converter.

La classe TrendToImageConverter.cs

Prima di iniziare con il codice, creiamo un altra cartella, sempre a livello di progetto e chiamiamola Images al suo interno aggiungiamo le 2 immagini che useremo per sostituire i valori Up e Down. Le trovate nel progetto a corredo a partire da questo articolo, ma le inserisco qui in modo che possiate copiarle se necessario:

Credo che il loro significato sia esplicativo e quindi sia semplice capire quale immagine usare per ciascun valore.

Per aggiungere le immagini al progetto, posizioniamo il cursore sulla cartella Images e selezioniamo le due png dove le abbiamo salvate usando l’opzione Add Existing Item…

Una nota relativa alla finestra per selezionare un Existing Item, normalmente VisualStudio propone in automatico di selezionare un file .cs o .xaml, ricordatevi di usare la combobox accanto a FileName per selezionare gli Image Files così che vengano mostrate le png e le jpeg o bmp.

Una volta che le immagini sono state aggiunte alla cartella Images controlliamo che siano state inserite come Risorse di progetto in modo che vengano aggiunte all’interno del pacchetto applicativo e siano utilizzabili con gli URI applicativi.

Controllate che la Build Action sia settata come Resource in modo che l’immagine venga inclusa nell’Exe.

Ora possiamo passare a generare la classe converter vera e propria.

using System.Windows.Markup;
using System.Windows.Media.Imaging;
using WpfGridColors.Entities;

Se non sono presenti, aggiungiamo nella classe i NameSpace necessari.

[ValueConversion(typeof(Trends), typeof(BitmapImage))]
public class TrendToImageConverter : MarkupExtensionIValueConverter
{
 
	private static TrendToImageConverter _converter = null;
	public override object ProvideValue(IServiceProvider serviceProvider)
	{
		if (_converter == null_converter = new TrendToImageConverter();
		return _converter;
	}

Dichiariamo la classe e le agganciamo l’Attributo ValueConversion che la identifica come un converter, specificando i tipi di dati sorgente e destinazione.

La classe viene creata come derivata da MarkupExtension, questo ci permette di definire la variabile e il metodo qui sopra riportati che ci permetteranno un uso veramente semplice del converter nella nostra applicazione.

Implementiamo l’interfaccia IValueConverter, che si compone dei 2 metodi che effettuano la conversione. Convert e ConvertBack.

public object Convert(object valueType targetTypeobject parameterCultureInfo culture)
{
	BitmapImage result = new BitmapImage(new Uri("pack://application:,,,/Images/btn_064_518.png"));
	Trends input = Trends.Down;
	if (value != null)
	{
		input = (Trends)value;
	}
	if (input == Trends.Up)
	{
		result = new BitmapImage(new Uri("pack://application:,,,/Images/btn_064_814.png"));
	}
 
	return result;
}

public object ConvertBack(object valueType targetTypeobject parameterCultureInfo culture)
{
	//If you don't need a convert back this does nothing
	return value;
}

Il metodo che effettua la conversione predispone subito la freccia in discesa, in base al valore trasmesso al converter, se il Trend è Up genera invece la freccia in salita.

Ora dobbiamo tornare sulla classe MainWindow.xaml per implementare la nostra nuova colonna con l’immagine al posto del testo. Se ricordate l’articolo precedente, avevamo definito la colonna Trend come una DataGridTextColumn, questo tipo di colonna non è dotata di un immagine. Per ovviare al problema, la DataGrid fornisce un tipo di colonna che ci permette di definire le colonne in modo arbitrario. Si tratta della DataGridTemplateColumn. Vediamo come modificare la colonna Trend Per utilizzare questo tipo di colonna ed utilizzare l’immagine al posto del testo.

<DataGridTemplateColumn
	Header="Trend"
	IsReadOnly="true">
	<DataGridTemplateColumn.CellTemplate>
		<DataTemplate>
			<Image
				Width="32"
				Height="32"
				Source="{Binding Trend, UpdateSourceTrigger=PropertyChanged, Converter={lconv:TrendToImageConverter}}"
				ToolTip="{Binding Trend, UpdateSourceTrigger=PropertyChanged}" />
		</DataTemplate>
	</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

La nostra nuova colonna risulta essere un po’ complicata rispetto alla precedente ma vediamo di spiegare come funziona. Una DataGridTemplateColumn definisce per la colonna dati che visualizza un CellTemplate, ovvero il formato di visualizzazione delle celle della colonna.

nel caso della colonna Trend, abbiamo indicato prima di tutto nell’oggetto primario le seguenti cose:

  •  Header = titolo della colonna
  • IsReadOnly = la colonna non può essere modificata

Nel CellTemplate abbiamo definito un DataTemplate che contiene l’immagine da visualizzare. L’Oggetto Image è così configurato:

  • Width = 32 (larghezza 32)
  • Height = 32 (altezza 32)
  • ToolTip = collegato in Binding al campo Trend, mostra il valore “Up”, “Down” nel tooltip al mouse over.
  • Source= qui colleghiamo l’immagine al valore del campo Trend usando il converter appena creato. Per utilizzarlo, abbiamo fatto anche una ulteriore modifica all’interno del tag Window ed è la seguente:
xmlns:lconv="clr-namespace:WpfGridColors.Converters"

con l’istruzione qui sopra indichiamo alla Window che può cercare oggetti nella cartella appena generata Converters e questi oggetti saranno identificati con il prefisso lconv. infatti, il nostro attributo Source è definito così:

Source="{Binding Trend, UpdateSourceTrigger=PropertyChanged, Converter={lconv:TrendToImageConverter}}"

Il Converter viene agganciato per trasformare la stringa del Trend nell’immagine corrispondente. Dopo questa modifica, la Window si trasforma nel seguente modo.

Adesso il Trend è rappresentato dall’immagine e come vediamo nelle 2 immagini seguenti, il tooltip visualizza il valore.

Il tooltip per la freccia blu a sinistra
e quello per la rossa a destra

Dopo questa modifica, se provate a fare click sull’intestazione di colonna sfortunatamente l’ordinamento non viene effettuato, perché la colonna non ha un legame specifico con il campo che rappresenta che si trova invece all’interno del Template. Ma è facile rimediare alla cosa.

 

<DataGridTemplateColumn 
	Header="Trend" 
	IsReadOnly="true"
	SortMemberPath="Trend">

Implementare il sort sulle Template Column

Aggiungiamo alla dichiarazione della Column l’attributo SortMemberPath, in cui indichiamo il nome campo. Questo farà funzionare correttamente anche il Sort:

Adesso è possibile ordinare la colonna anche se è un immagine.

Formattazione condizionale del background delle celle usando un Converter

Passiamo ora alla formattazione condizionale delle celle numeriche della grid. In questo caso vogliamo evidenziarle con colori diversi le celle numeriche in base al valore contenuto, in modo da dare maggiore qualità alla presentazione dei dati.

Per fare questo dobbiamo per prima cosa creare due converter, uno per gli importi ed uno per le percentuali. Spostiamoci sulla cartella Converters e aggiungiamo il primo converter, usando la funzione Add… New Item creiamo una classe e la chiamiamo DecimalAmountToColorConverter.cs

In questo caso, creiamo una classe Converter che converte un valore decimal in un SolidColorBrush che useremo per colorare lo sfondo della cella.

La classe DecimalAmountToColorConverter.cs

Iniziamo a generare la nostra classe aggiungendo gli using necessari in cima alla classe:

using System;
using System.Windows.Data;
using System.Globalization;
using System.Windows.Markup;
using System.Windows.Media;

Assicuriamoci che tutte le clausole using qui sopra riportate siano presenti per poter scrivere il nostro converter.

[ValueConversion(typeof(decimal), typeof(SolidColorBrush))]
public class DecimalAmountToColorConverter : MarkupExtensionIValueConverter
{
 
	private static DecimalAmountToColorConverter _converter = null;
	public override object ProvideValue(
		IServiceProvider serviceProvider)
	{
		if (_converter == null_converter = 
			new DecimalAmountToColorConverter();
		return _converter;
	}

Anche in questo caso, usiamo l’attributo per specificare la classe sorgente e quella destinazione per il converter. e definiamo il metodo ProvideValue richiesto dal fatto che abbiamo derivato la classe converter dalla classe MarkupExtension, per permetterne un uso semplice nello XAML.

public object Convert(object valueType targetTypeobject parameterCultureInfo culture)
{
	SolidColorBrush result = new SolidColorBrush(Colors.Transparent);
	decimal input = 0.0m;
	if (value != null)
	{
		input = (decimal)value;
	}
	if (input <= 5000)
	{
		result = new SolidColorBrush(Colors.LightGray);
	}
	else if (input > 5000 && input <= 10000)
	{
		result = new SolidColorBrush(Colors.LightSalmon);
	}
	else if (input > 10000 && input <= 25000)
	{
		result = new SolidColorBrush(Colors.Yellow);
	}
	else if (input > 25000 && input <= 100000)
	{
		result = new SolidColorBrush(Colors.Orange);
	}
	else if (input > 100000)
	{
		result = new SolidColorBrush(Colors.LimeGreen);
	}
	return result;
}

Per questo converter, convertiamo l’oggetto passato in decimal se l’oggetto non è nullo, se fosse nullo, il suo valore sarebbe 0.0. quando abbiamo ottenuto il valore numerico lo esaminiamo e in base al suo valore generiamo un diverso SolidColorBrush da usare come sfondo. Non riporto il ConvertBack perché anche in questo caso non ci serve visto che convertiamo solo in un verso.

Prima di procedere alle modifiche al codice Xaml per applicare i converter, aggiungiamo anche il converter (praticamente identico fatto salvo per i valori utilizzati), per i due valori percentuali.

La classe DecimalPercentToColorConverter.cs

Questo converter è praticamente identico al precedente, fatto salvo per la parte numerica, pertanto per brevità riporto solo il metodo convert. Troverete l’intera classe nel codice a corredo.

public object Convert(object valueType targetTypeobject parameterCultureInfo culture)
{
	SolidColorBrush result = new SolidColorBrush(Colors.Transparent);
	decimal input =  0.0m;
	if (value != null)
	{
		input = (decimal)value;
	}
	if (input <= 1)
	{
		result = new SolidColorBrush(Colors.LightGray);
	}
	else if (input > 1 && input <= 5)
	{
		result = new SolidColorBrush(Colors.LightSalmon);
	}
	else if (input > 5 && input <= 10)
	{
		result = new SolidColorBrush(Colors.Yellow);
	}
	else if (input > 10 && input <= 15)
	{
		result = new SolidColorBrush(Colors.Orange);
	}
	else if (input > 15)
	{
		result = new SolidColorBrush(Colors.LimeGreen);
	}
	return result;
}

Come potete notare cambiano solo i range degli intervalli numerici ma resta quasi uguale. Se provate il codice, fate dei test con diversi colori e se volete proprio sperimentare, potete utilizzare anche un Gradiente (Es. LinearGradientBrush).

Per applicare i due Converter alle colonne opportune sulla DataGrid, dobbiamo modificare il tipo di colonna usata.

Dobbiamo infatti passare dalla DataGridTextColumn alla DataGridTemplateColumn. Questo perché la DataGridTextColumn ci permette di cambiare lo stile solo alla porzione del testo della cella, pertanto volendo un effetto più gradevole, preferiamo cambiare il colore del Background di tutta la cella. Ecco come modificare le colonne.

<DataGridTemplateColumn
	Header="Sales"
	IsReadOnly="true"
	SortMemberPath="SalesTotal">
	<DataGridTemplateColumn.CellTemplate>
		<DataTemplate>
			<Border
				Margin="0"
				Background="{Binding SalesTotal, Converter={lconv:DecimalAmountToColorConverter}}"
				BorderThickness="0">
				<TextBlock
				HorizontalAlignment="Right"
				VerticalAlignment="Center"
				Text="{Binding SalesTotal, StringFormat={}{0:#,#.00}}" />
			</Border>
		</DataTemplate>
	</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Anche in questo caso, mostrerò due delle quattro colonne perché le altre due sono identiche fatto salvo per il titolo e la property collegata in Binding. Vediamo cosa abbiamo definito.
Innanzi tutto i 3 attributi Header, IsReadOnly, SortMemberPath. uguali a quelli della colonna Trend, salvo il valore dell’intestazione colonna.

Poi abbiamo creato il Template della cella e vi abbiamo inserito:

  • Un oggetto Border, che ci disegna il rettangolo della cella. A questo oggetto assegneremo lo sfondo con il nostro converter perché se lo assegnassimo alla TextBlock verrebbe colorato solo il testo mentre il Border occupa tutta l’area della cella quindi colora tutta la cella.
  • Una TextBlock che contiene il valore della cella. ed il formato del numero, inoltre siamo in grado di allineare il valore a destra come previsto per i numeri.

Voglio solo far notare, che anche l’attributo Background viene collegato in Binding alla Property SalesTotal della riga della DataGrid (riga=CustomerStat) che fornisce al converter il numero poi convertito in Solid Color Brush.

Anche per la colonna percentuale, generiamo una DataGridTemplateColumn in cui variamo il Converter e la stringa di definizione del formato numerico.

Ripetiamo la modifica anche per le altre 2 colonne e andiamo a verificare come cambia la nostra DataGrid.

<DataGridTemplateColumn
	Header="% Sales"
	IsReadOnly="true"
	SortMemberPath="PercentSales">
	<DataGridTemplateColumn.CellTemplate>
		<DataTemplate>
			<Border
			Margin="0"
			Background="{Binding PercentSales, 
				Converter={lconv:DecimalPercentToColorConverter}}"
			BorderThickness="0">
				<TextBlock
				HorizontalAlignment="Right"
				VerticalAlignment="Center"
				Text="{Binding PercentSales, StringFormat={}{0:#.00}}" />
			</Border>
		</DataTemplate>
	</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Anche nella colonna con percentuale, applichiamo il converter al Border e formattiamo il numero nel modo più corretto allineandolo in modo corretto.

Vediamo quindi cosa succede alla nostra DataGrid.

Il risultato sono celle molto colorate, se vogliamo qualcosa di più sobrio, potremmo creare delle sfumature di un solo colore invece che dei colori molto sgargianti come fatto in questo caso. 

Ci fermiamo qui, nel prossimo articolo, aggiungeremo la parte che visualizza la cella corrente nella TextBox in calce alla DataGrid e poi passeremo alla parametrizzazione sfrenata.