Press "Enter" to skip to content

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

Generare e configurare la DataGrid visualizzando i dati uso di DataGridTextColumn e DataGridTemplateColumn

Nell’ Articolo precedente abbiamo generato dei dati di test da mostrare sulla nostra User Interface che utilizzeremo per verificare tutte le funzionalità che aggiungeremo alla nostra DataGrid e alla nostra applicazione in generale. In questa terza parte andremo a inserire i componenti visuali nella MainWindow per poter Visualizzare i dati generati.

Modifiche a MainWindow.xaml

<Window
	x:Class="WpfGridColors.MainWindow"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:local="clr-namespace:WpfGridColors"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	Title="Statistica"
	Width="900"
	Height="450"
	ContentRendered="MainWindow_ContentRendered"
	mc:Ignorable="d">

La prima modifica che effettuiamo all’interno del codice XAML di MainWindow.xaml è per aggiungere un Event Handler all’evento ContentRendered della Window. Questo evento viene scatenato dopo il caricamento della pagina (quindi dopo l’evento Load) e io usualmente lo utilizzo per 2 cose:

  1. Controllare che tutti i parametri funzionali per la window siano presenti avvisando immediatamente il programmatore (me medesima) se mi sono dimenticata di fornire alla finestra qualche dato fondamentale per il funzionamento, in questo caso non ci serve nulla visto che è la finestra principale.
  2. Caricare i dati nella Window se necessario.
ContentRendered="MainWindow_ContentRendered"

Una volta aggiunto il codice qui sopra riportato, per generare automaticamente l’Event Handler e spostarvi su di esso per aggiungervi il codice basta premere il tasto destro del mouse stando sopra al valore del nome dell’event handler e selezionare Go to Definition o il tasto F12

 

Ci troveremo di nuovo dentro a MainWindow.xaml.cs dove aggiungeremo il codice qui sotto indicato.

private void MainWindow_ContentRendered(object senderEventArgs e)
{
	LoadData();
}

Il metodo sarà già scritto e noi vi aggiungeremo la chiamata al metodo LoadData che abbiamo generato nell’articolo precedente provvedendo a caricare i dati di test.

Proseguiamo le modifiche alla MainWindow.Xaml aggiungendo la parte grafica della Window

<Grid>
 
	<Grid.RowDefinitions>
		<RowDefinition Height="*" />
		<RowDefinition Height="Auto" />
	</Grid.RowDefinitions>

Prima di tutto, definiamo all’interno della Grid vuota fornitaci dal template due righe in cui inseriremo i controlli della Window. Nella prima riga inseriremo la DataGrid, nella seconda inseriremo due controlli che utilizzeremo per visualizzare il contenuto della cella correntemente selezionata della DataGrid, una delle funzionalità che abbiamo inserito nella lista degli scopi di questa serie. L’altezza della prima riga, occupa tutto lo spazio disponibile, l’altezza della seconda è dimensionata in base al proprio contenuto.

<DataGrid
	Name="dgStat"
	Grid.Row="0"
	AutoGenerateColumns="False"
	CurrentItem="{Binding CurrentStatistic, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
	ItemsSource="{Binding Statistics, UpdateSourceTrigger=PropertyChanged}"		
	SelectionUnit="Cell">

Inseriamo la DataGrid nello XAML e inizializziamo i suoi attributi di base ovvero:

  • Name = nome della grid ci servirà nel codice
  • Grid.Row = attributo relativo alla Grid che contiene la DataGrid che indica che la DataGrid deve essere visualizzata sulla prima riga della Grid strutturale
  • AutoGenerateColumns = flag che poniamo a False perché definiremo noi le colonne (lo vedremo fra poco)
  • CurrentItem = è una proprietà della grid che possiamo collegare (e collegheremo) ad una property da noi definita in modo che questa property conterrà automaticamente (è una funzione che svolge la DataGrid), il record correntemente attivo.
  • ItemsSource = è la proprietà che dobbiamo collegare (e collegheremo) alla collection dei dati da noi predisposti.
  • SelectionUnit = indica che cosa verrà selezionato al click del mouse sulla grid. nel nostro caso, selezioniamo la cella ma potremmo selezionare anche la riga intera.

Ovviamente i due attributi principali sono quelli con la definizione più complessa, vediamo come definirli e perché.

Partiamo da CurrentItem, per capire bene questo attributo torniamo un attimo sulla MainWindow.xaml.cs e generiamo la property CurrentStatistic.

public const string FLD_CurrentStatistic = "CurrentStatistic";
 
private CustomerStats mCurrentStatistic;
 
public CustomerStats CurrentStatistic
{
	get
	{
		return mCurrentStatistic;
	}
	set
	{
		mCurrentStatistic = value;
		OnPropertyChanged(FLD_CurrentStatistic);
	}
}

Una volta definita la property vi invito a ricordare la riga di codice inserita nel costruttore che dice:
DataContext = this;
Questa riga fondamentale, collega i dati definiti all’interno della Window allo XAML.

CurrentItem="{Binding CurrentStatistic, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

Il codice XAML qui sopra definito può essere tradotto con la seguente frase: Collega il CurrentItem della DataGrid alla proprietà CurrentStatistics con modalità TwoWay ovvero se DataGrid modifica CurrentItem aggiorna CurrentStatistics e viceversa se il codice modifica CurrentStatistics modifica CurrentItem;  più difficile da scrivere che da fare. L’ultimo attributo, UpdateSourceTrigger indica che l’evento che comunica la variazione di CurrentStatistics è PropertyChanged che guarda caso noi scateniamo quando con il Set assegnamo il valore della property.

ItemsSource="{Binding Statistics, UpdateSourceTrigger=PropertyChanged}"	

Passiamo all’assegnazione della fonte dati alla DataGrid, anche in questo caso, quello che definiamo tradotto in italiano è: Collega ItemsSource alla property Statistics e aggiornati se tale property varia. in questo caso non c’è l’attributo Mode perché non ci può essere modifica nei due versi, ItemsSource viene solo fornita dal nostro codice.

Ed ora, definiamo la parte più succosa della DataGrid ovvero le colonne che la compongono.

<DataGrid.Columns>
	<DataGridTextColumn
		Width="100"
		Binding="{Binding Trend}"
		Header="Trend"
		IsReadOnly="true" />
	<DataGridTextColumn
		Width="60"
		Binding="{Binding Year}"
		Header="Year"
		IsReadOnly="true" />
	<DataGridTextColumn
		Width="300"
		Binding="{Binding CustomerName}"
		Header="Customer Name"
		IsReadOnly="true" />
	<DataGridTextColumn
		Width="100"
		Binding="{Binding SalesTotal, StringFormat={}{0:#,#.00}}"
		Header="Sales"
		IsReadOnly="true" />
	<DataGridTextColumn
		Width="100"
		Binding="{Binding BudgetTotal, StringFormat={}{0:#,#.00}}"
		Header="Budget"
		IsReadOnly="true" />
	<DataGridTextColumn
		Width="80"
		Binding="{Binding PercentSales, StringFormat={}{0:#,#.00}}"
		Header="% Sales"
		IsReadOnly="true" />
	<DataGridTextColumn
		Width="80"
		Binding="{Binding PercentBudget, StringFormat={}{0:#,#.00}}"
		Header="% Budget"
		IsReadOnly="true" />
</DataGrid.Columns>
</DataGrid>

Definiamo le colonne della DataGrid utilizzando l’oggetto DataGrid.Columns. Notate come anche i campi numerici siano stati definiti utilizzando una DataGridTextColumn questo perché la DataGrid standard WPF non implementa una colonna di tipo numerico. Ecco perché nella definizione del Binding relativo alle colonne numeriche abbiamo utilizzato l’attributo StringFormat che ci permette di definire come vogliamo che la stringa numerica sia stampata.

Tutte le colonne sono definite assegnando:

  • Width = larghezza della colonna
  • Binding = definizione della property dell’oggetto “riga” della collection mappata sulla colonna ed il suo eventuale formato usando StringFormat
  • Header = Titolo della colonna
  • IsReadOnly = flag per attivare, o nel nostro caso disattivare, la modifica del dato della colonna.

Fatto questo la versione 1.0 della DataGrid è stata generata e fra poco potremo finalmente compilare e provare il codice per vedere l’effetto che fa.

Prima di proseguire, predisponiamo anche l’interfaccia ove mostreremo il valore della cella correntemente selezionata sulla grid e implementiamo uno dei Bonus inseriti nella lista delle cose che faremo.

<Grid Grid.Row="1">
	<Grid.ColumnDefinitions>
		<ColumnDefinition Width="Auto" />
		<ColumnDefinition Width="*" />
	</Grid.ColumnDefinitions>

Per contenere una TextBlock ed una TextBox che metteremo sulla seconda riga della Grid principale, creiamo una seconda Grid (se lavorerete con XAML scoprirete che funziona come le scatole cinesi, si può inserire oggetti dentro oggetti dentro oggetti… all’infinito)

La Grid viene inserita indicando che si troverà alla riga 1 della Grid che la contiene e che conterrà una sola riga (standard se non si usa il RowDefinitions) e 2 colonne che abbiamo definito qui sopra, la prima colonna ha una dimensione che è definita dalla larghezza di ciò che contiene, la seconda ha una dimensione che occupa tutto lo spazio disponibile.

Vediamo ora la definizione dei 2 controlli contenuti nelle colonne di questa grid.

	<TextBlock
		Grid.Column="0"
		Margin="4,2,4,2"
		HorizontalAlignment="Left"
		VerticalAlignment="Center"
		Style="{DynamicResource ColorLabel}"
		Text="Contenuto della cella corrente:" />
	<TextBox
		Grid.Column="1"
		MinHeight="24"
		Margin="4,2,4,2"
		HorizontalAlignment="Stretch"
		VerticalAlignment="Center"
		HorizontalContentAlignment="Left"
		VerticalContentAlignment="Center"
		Text="{Binding SelectedCellValue, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Grid>

Il primo controllo che definiamo è un etichetta di testo, contiene il necessario per formattarla e stabilire come si posiziona nella cella della Grid gli attributi mi sembrano auto esplicativi, solo uno è degno di nota ovvero:
Style=”{DynamicResource ColorLabel}”
Questo attributo stabilisce che alla TextBlock verrà applicato lo stile chiamato ColorLabel che è una DynamicResource. Cosa è tale resource? si tratta di uno stile definito in App.Xaml all’interno dell’applicazione che può essere riutilizzato in tutte le Window o gli User Control Xaml dell’applicazione. Pertanto, andiamo in App.Xaml a definire lo stile ColorLabel.

<Application.Resources>
	<Style x:Key="ColorLabel" TargetType="{x:Type TextBlock}">
		<Setter Property="Foreground" Value="#FF003399" />
		<Setter Property="FontSize" Value="9pt" />
		<Setter Property="FontWeight" Value="DemiBold" />
	</Style>
</Application.Resources>

Questa è la definizione del nostro Stile riutilizzabile, ovvero uno stile che ha un nome assegnato con x:Key ed un tipo di controllo per cui questo stile è disegnato x:Type TextBlock per cui modifica alcune delle property ovvero: 

  • Foreground = colore del testo
  • FontSize = dimensione dei caratteri
  • FontWeight = grossezza dei caratteri

All’interno di App.Xaml in una applicazione complessa possiamo generare tutti gli stili condivisi in tutta l’applicazione.

Il secondo controllo che abbiamo definito, la TextBox definisce gli attributi visuali, quindi dove si trova, quanto è grande, come deve allineare se stessa rispetto alla Grid che la contiene e come deve allinearsi il testo contenuto. Inoltre, la sua troperty Text ci indica cosa conterrà:

Text="{Binding SelectedCellValue, UpdateSourceTrigger=PropertyChanged}"

Anche in questo caso, colleghiamo l’attributo Text della TextBox ad una property che definiamo nel codice di MainWindow.xaml.cs ovvero il nostro ViewModel.

public const string FLD_SelectedCellValue = "SelectedCellValue";
 
private string mSelectedCellValue;
 
public string SelectedCellValue
{
	get
	{
		return mSelectedCellValue;
	}
	set
	{
		mSelectedCellValue = value;
		OnPropertyChanged(FLD_SelectedCellValue);
	}
}

Anche in questo caso, descritto in italiano il nostro Binding è il seguente: Collega la property SelectedCellValue a Text e aggiornala se viene sollevato l’evento PropertyChanged.

Ci fermiamo qui per ora, perché proseguiremo l’implementazione delle funzionalità nelle parti successive, però, già a questo punto, la nostra applicazione inizia a mostrare qualcosa.

Compilando ed eseguendo l’applicazione da Visual Studio questo è il primo screenshot:

I dati vengono caricati nella nostra DataGrid, e appaiono l’etichetta e la TextBox in fondo alla window. 

La DataGrid, automaticamente ci fornisce anche la possibilità di ordinare i dati su ciascuna colonna cliccando sul titolo della stessa.

Anche in modo discendente cliccando 2 volte sul titolo per cambiare l’ordine.

Possiamo anche spostare le colonne afferrando il titolo e trascinandole in altra posizione, questo spostamento non è permanente ma viene resettato chiudendo e riaprendo l’applicazione, uno degli obiettivi di questa serie è anche mostrare come implementare in modo rapido la memorizzazione della parametrizzazione della grid effettuata dall’utente.