Press "Enter" to skip to content

Creare un’applicazione WPF per gestire Dati su un database SqlServer – parte 8 La classe AuthorsListWindow

Prima di iniziare a sviluppare la nostra User Interface, spendo due parole parlando di “filosofia” della User Interface.

In un database “gestionale” prendetelo in senso lato, gestionale comprende tutto il mondo delle interfacce di produzione, ovvero da quella delle attività di una fabbrica, a quelle fiscali, contabili, alla gestione clienti, magazzino, avanzamento della produzione ecc. Si tratta dei database che quotidianamente masticano nuovi dati prodotti dal personale (ed ora anche dalle macchine) dati che servono al funzionamento di una azienda, grande o piccola che sia. Il termine tecnico è Database OLTP (online transaction processing). L’altro tipo di database sono i database per l’analisi dei dati quelli in cui i dati provenienti da OLTP vengono “masticati” per produrre grafici e report che possono supportare le decisioni da parte della direzione aziendale riguardo il futuro. Il termine tecnico per questi ultimi è OLAP (Online Analytical Processing) ed anche DataWarehouse.

Il nostro Database di Test è un database OLTP, perché difficilmente vi verrà chiesto di sviluppare interfacce utente per un database OLAP, in quanto di solito in quel tipo di database i dati sono inseriti elaborando i dati provenienti da una o più fonti di tipo OLTP.

In un database OLTP ci sono solitamente 3 tipi di tabelle:

  1. Tabelle di supporto e configurazione, solitamente molto semplici contengono pochi dati e sono tabelle a basso indice di modifica. Per darvi un esempio semplice pensate alle aliquote iva, unità di misura, categorie articoli magazzino, categorie clienti, comuni d’Italia ecc.
    Queste tabelle hanno variazioni impercettibili nel tempo.
  2. Tabelle anagrafiche, solitamente più complesse e spesso contenenti molti dati in ogni riga, anche queste hanno un indice di modifica medio basso e solitamente ricevono inserimenti e raramente modifiche (tipicamente poco dopo l’inserimento per completare i dati). Di questo tipo di tabelle fa parte la nostra tabella Authors, così come fanno parte di questa categoria le tabelle contatti di un CRM, conti, clienti e fornitori ed articoli di magazzino di un gestionale contabile.
  3. Tabelle dati ad alta frequenza di modifica, spesso complesse e collegate in forma gerarchica, con molti dati e inserimenti e modifiche rapide. Di questa categoria fanno parte tutti i documenti di produzione (ordini, stati avanzamento, dichiarazioni produzione, carichi magazzino, scarichi materia prima ecc.), i documenti fiscali (ordini, bolle fatture), i documenti contabili (prima nota).

Come penso l’interfaccia di ogni diverso tipo di tabella?

Andando in ordine, le tabelle di tipo 1, di solito si gestiscono senza problemi con una lista modificabile.

Le tabelle di tipo 2 solitamente hanno parecchi dati e si gestiscono con una Window con una Lista per la ricerca e visualizzazione dei dati di base ed una Window di Dettagli che permette di modificare un record per volta.

Le tabelle di tipo 3 invece possono avere diversi tipi di interfaccia e solitamente deve essere progettata Ad Hoc. Certo ci possono essere delle semplici Master/Detail, ma in realtà non sono mai così semplici, perché da una Window relativa a questo tipo di dato, spesso ci possono essere più window di dettagli, connessione ad altre tabelle, intercomunicazione (ad esempio da una Fattura, potrei voler inserire un cliente o dall’inserimento di articoli in magazzino, creare un nuovo articolo non ancora codificato. Ecc.)

Ci possono anche essere delle tabelle correlate in forma di Albero, che interagiscono in modo gerarchico non semplice e così via. Per ognuna di queste, bisogna pensare, progettare, e spesso andare per tentativi. Cercando di disegnare l’interfaccia ascoltando gli utenti prima di scrivere codice.

Bene, dopo l’intermezzo filosofico, parliamo della nostra tabella Authors.

Abbiamo detto che è una tabella Anagrafica, ma avendo solo 9 campi, possiamo osare e fare in modo che le modifiche alla tabella si  facciano  sulla lista stessa.

Gestiremo con dei pulsanti l’inserimento e la cancellazione e proveremo a non entrare direttamente in modifica ma attivare la modifica con un pulsante.

La Datagrid WPF standard è un bel componente, si può manipolare abbastanza bene per varie esigenze, non è sofisticata come le grid di produttori di componenti, ma proveremo a sfruttarla al meglio.

Siccome normalmente nei miei progetti, le grid devono implementare rapidamente tutto il necessario a fornire: Filtri, Ricerca full text, ordinamento, spostamento delle colonne e altre funzionalità avanzate, io utilizzo le librerie di Syncfusion, una delle varie aziende che producono componenti per .Net che hanno già tutto il necessario per permetterci con pochi parametri di configurazione ed eventuali Template di colonna di sviluppare una grid modificabile. Non sono gli unici, ma hanno un ottimo supporto ed un ottima qualita prezzo/prestazioni. Ovviamente ciascuno può scegliere i componenti che preferisce. Potete dare un occhiata a quelli di XCeed, Telerik, Infragistics, ComponentOne ecc.

In questo caso, proveremo ad estrarre il massimo dal componente standard.

Direi che è opportuno partire con l’azione.

Per prima cosa generiamo la classe AuthorsListWIndow. Posizioniamoci sulla cartella Windows del nostro progetto, sul menu contestuale aperto con il tasto destro, selezioniamo:

Add -> New Item…

Selezioniamo nella tree sulla sinistra la cartella WPF e nella lista dei Template di Classe, selezioniamo Window, chiamiamo la nostra classe AuthorsListWindow.xaml e premiamo il tasto Add.

Come abbiamo già fatto per la ConfigWindow, ora dobbiamo andare a modificare la Window per inserirvi i componenti necessari. Sfortunatamente o Fortunatamente per voi, io non utilizzo mai l’editor Visuale di WPF perché crea solamente dei mostri. Ma se avete lavorato un pochino in HTML, XAML dovrebbe essere per voi una sciocchezza, visto che è fatto da programmatori e non da Grafici, quindi è davvero facile da usare per tutti noi.

La window che ci viene presentata contiene ben poco, la prima cosa che possiamo guardare, nel Tag Window è l’attributo Title, dove credo sia opportuno scrivere un titolo un po’ meno stringato del nome della classe., alla sua destra, ci sono Height e Width che sono le dimensioni che avrà la finestra quando si aprirà, considerato che si tratta di un elenco, magari possiamo ingrandire la finestra.

 

 Title="Lista degli Autori" Height="640" Width="960">

Modifichiamo quindi i 3 attributi del tag Window.

Il solo altro Tag a disposizione è un tag Grid. fate attenzione che in WPF, la Grid è un contenitore di altri componenti, il suo scopo è fornire la possibilità di creare degli spazi Tabulari in cui sistemare i componenti. Non è una DataGrid, quello è un componente diverso.

Altri componenti Contenitori, sono lo StackPanel, Il Canvas, il WrapPanel, il Border. I tag contenitore sono di 2 tipi, quelli che possono contenere più di un componente, e quelli che contengono un singolo componente. Ad esempio il Canvas che come dice il suo nome è una tavolozza su cui disegnare componenti, può contenere al massimo un solo componente, anche il Border ha lo stesso comportamento. Mentre Grid, StackPanel, WrapPanel, possono contenere più di un componente.

Il fatto di contenere un solo componente non è una limitazione perché il componente contenuto nel canvas o nel border può essere una Grid, o uno StackPanel e quindi possiamo poi inserirvi altri componenti.

La Grid io la trovo utile per creare un layout dinamico e preciso. Lo stack panel è invece adatto quando vogliamo contenere una collezione di oggetti (ad esempio i Button per simulare una Toolbar come abbiamo fatto nella MainWindow).

Per la nostra Window, utilizziamo la Grid e creiamo 3 righe anche se per ora useremo solo le prime due, la prima per i command button, e la seconda dove porremo la DataGrid.

 

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

Come indicato, definiamo all’interno della Grid principale della window 3 righe. L’oggetto RowDefinitions di una Grid, può contenere una collezione di oggetti RowDefinition, ognuno di questi dichiara la propria altezza all’interno dell’oggetto contenitore (in questo caso la Window.

la property Height dell’oggetto RowDefinition è un oggetto di tipo GridLength, viene usato sia per l’altezza delle righe che per la larghezza delle colonne. Contiene due proprietà, una è la lunghezza, l’altra è il tipo di unità in cui è espressa.  Le unità possono essere relative, come nel nostro caso, oppure possono essere espresse in pixel. Utilizzando le unità relative, la dimensione sarà calcolata in base alla dimensione dell’oggetto contenitore (in questo caso la window) e sarà ridimensionata alla modifica della dimensione dell’oggetto stesso.

Nel caso delle righe e colonne delle Grid, abbiamo questi tipi di valore usato:

  • Auto = la dimensione è quella dell’oggetto contenuto
  • * o nn* = dove nn è un numero, definisce la dimensione proporzionalmente allo spazio disponibile.

Il valore nel nostro caso è. per la prima e la terza riga, di adattarsi alla dimensione del contenuto, mentre la seconda riga, occuperà tutto lo spazio disponibile. 

<DataGrid
	x:Name="GrAuthors"
	Grid.Row="1"
	Margin="4,8,4,8"
	HorizontalAlignment="Stretch"
	VerticalAlignment="Stretch"
	AutoGenerateColumns="True"
	AutoGeneratingColumn="GrAuthors_AutoGeneratingColumns"
	CanUserAddRows="False"
	CanUserDeleteRows="False"
	CanUserReorderColumns="True"
	CanUserResizeColumns="True"
	CanUserResizeRows="True"
	CellEditEnding="GrAuthors_CellEditEnd"
	CurrentCellChanged="GrAuthors_CurrentCellChanged"
	ItemsSource="{Binding Authors, UpdateSourceTrigger=PropertyChanged}"
	RowHeaderWidth="20"
	SelectedItem="{Binding CurrentSelection, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
	SelectionMode="Single"
	SelectionUnit="CellOrRowHeader" />

Iniziamo inserendo la datagrid nella seconda riga, dove occuperà tutto lo spazio disponibile, poi aggiungeremo i pulsanti di comando ed eventualmente una barra di stato.

Vediamo cosa ho configurato sulla nostra DataGrid, per ora partiamo in modo semplice:

  • x:Name = GrAuthors : Questa property ci permette di poter accedere alla grid da codice utilizzando il suo Name. Non è indispensabile dare un nome a tutti i controlli, in questo caso lo faccio perché potrebbe servirmi.
  • Grid.Row = 1: Questa property non è della DataGrid, ma è una property che è stata agganciata alla DataGrid per il fatto di essere contenuta all’interno di una Grid. Ci permette di indicare su quale riga si deve posizionare il controllo. Come questa, ci sono altre property agganciate dalla Grid, ovvero Grid.Column, Grid.RowSpan, Grid.ColumnSpan. Da usarsi quando necessario.
  • Margin = 4,8,4,8: Questa property definisce il margine lasciato attorno al controllo, nel nostro caso contiene i valori dei 4 lati nell’ordine Left, Top, Right, Bottom. E’ possibile anche assegnare un solo valore che sarà assegnato a tutti i lati, oppure due valori che saranno assegnati il primo a Left e Right ed il secondo a Top e Bottom.
  • HorizontalAlignment = Stretch: Questa property indica che la DataGrid occuperà tutto lo spazio orizzontale assegnatole.
  • VerticalAlignment = Stretch: Questa property indica che la DataGrid occuperà tutto lo spazio verticale assegnatole.
  • AutoGenerateColumns = True: Per partire in modo semplice, facciamo generare le colonne automaticamente alla DataGrid, vedremo come manipolare alcune delle cose che le riguardano durante la creazione utilizzando un evento scatenato quando le colonne sono generate. in seguito vedremo come sia possibile creare le colonne assegnando tipo, forma, comportamento da Xaml.
  • AutoGeneratingColumn = GrAuthors_AutoGeneratingColumns: Questa è l’assegnazione di un Event handler all’evento di autogenerazione delle colonne per manipolarne forma e comportamento, vedremo poi il codice associato.
  • CanUserAddRows= False: Anche se è possibile permettere di aggiungere righe direttamente sulla DataGrid, noi gestiremo la generazione di un nuovo record con una azione da noi costruita, perché è indispensabile che venga attribuito un codice univoco all’Autore controllandolo prima dell’inserimento. Pertanto per ora questo flag resta false.
  • CanUserDeleteRows = False: Anche per la cancellazione è opportuno fare una azione personalizzata che permetta di effettuarla solo se fattibile (se l’autore non ha libri ad esempio.)
  • CanUserReorderColumns= True: Invece, con questa Property attiviamo la possibilità per l’utente di riordinare le colonne (spostarle trascinando per il titolo). Non è una operazione stabile, la posizione non viene memorizzata, e non fa parte degli obiettivi della serie, però in seguito potremo vedere come fare a salvare posizione e forma di una grid su un file di configurazione e caricarla alla successiva esecuzione.
  • CanUserResizeColumns=True: Con questa property permettiamo all’utente di ridimensionare le colonne della DataGrid, anche in questo caso al successivo avvio non viene conservata.
  • CanUserResizeRows= True: Con questa property possiamo permettere all’utente di ridimensionare le righe della DataGrid.
  • CellEditEnding= GrAuthors_CellEditEnd: Questo è un Event Handler (un metodo eseguito allo scatenarsi dell’evento CellEditEnding della DataGrid) che gestirà il salvataggio dei dati modificati in una cella.
  • CurrentCellChanged= GrAuthors_CurrentCellChanged: Questo è un Event Handler che ci permette di inizializzare la riga dati corrente anche quando l’utente seleziona una singola cella.
  • ItemsSource= {Binding Authors, UpdateSourceTrigger=PropertyChanged}: La property ItemsSource, contiene la Lista delle righe di oggetti AuthorItem visualizzata sulla DataGrid. Tale Lista è agganciata tramite il Binding, inoltre abbiamo informato la grid che i dati possono essere modificati e in caso di modifica, scatenano l’evento PropertyChanged per avvisare la DataGrid di aggiornarsi.
  • RowHeaderWidth=20: Predispone la larghezza dell’intestazione riga, un elemento che ci permette di selezionare una riga intera della grid.
  • SelectedItem={Binding CurrentSelection, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}: Questa property, ci permette di collegare la riga correntemente selezionata della DataGrid ad una property che ne riflette il valore e che useremo per il salvataggio.
  • SelectionMode=Single: Indichiamo che permettiamo solo la selezione di un singolo elemento.
  • SelectionUnit=CellOrRowHeader: Con questo valore, l’utente può selezionare una sola cella con un click oppure l’intera riga con un click sull’intestazione di riga.  

Ho descritto come è stata configurata la DataGrid, che abbiamo predisposto per poter modificare i dati della tabella Authors. pertanto vediamo subito cosa facciamo rispondendo agli eventi che abbiamo mappato sulla datagrid a vario scopo.

private void GrAuthors_AutoGeneratingColumns(object senderDataGridAutoGeneratingColumnEventArgs e)
{
	try
	{
		DataGridTextColumn txt = e.Column as DataGridTextColumn;
		if (txt != null)
		{
			string hd = txt.Header as string;
			switch (hd)
			{
				case "AuthorID":
					txt.Header = "ID Autore";
					break;
				case "AuthorLastName":
					txt.Header = "Cognome Autore";
					break;
				case "AuthorFirstName":
					txt.Header = "Nome Autore";
					break;
				case "Phone":
					txt.Header = "Telefono";
					break;
				case "Address":
					txt.Header = "Indirizzo";
					break;
				case "City":
					txt.Header = "Città";
					break;
				case "State":
					txt.Header = "Stato";
					break;
				case "Zip":
					txt.Header = "CAP";
					break;
 
			}
		}
		else
		{
			DataGridCheckBoxColumn chk = e.Column as DataGridCheckBoxColumn;
			if (chk != null)
			{
				switch (chk.Header)
				{
					case "Contract":
						chk.Header = "Contratto";
						break;
				}
			}
 
		}
		e.Column.IsReadOnly = true;
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.ToString(), "ERRORE"MessageBoxButton.OKMessageBoxImage.Error);
	}
 
}

Il primo evento che intercettiamo è l’AutogeneratingColumn questo evento viene scatenato dalla DataGrid quando la stessa genera una colonna leggendone le caratteristiche dalla classe Item della collection che noi abbiamo messo in Binding sulla DataGrid (nel nostro caso l’oggetto AuthorItem). La datagrid è abbastanza intelligente da riconoscere alcuni tipi di dati specifici, come ad esempio i Bool generando una colonna di tipo CheckBox. Nel nostro caso, tutte le altre colonne sono stringhe, pertanto vengono generate delle colonne di tipo Text.

Questo evento viene scatenato da ciascuna delle colonne dell’oggetto AuthorItem che noi abbiamo definito.

La DataGrid, per quanto sia un oggetto potente, sfortunatamente è anche un oggetto piuttosto “grezzo”, il solo modo che abbiamo per riconoscere le colonne è tramite il loro Titolo, che troviamo nella property Header e che è tradotto con il nome della property.  Come esercizio di configurazione delle colonne, abbiamo usato l’evento per modificare il titolo delle colonne. Ma potremo modificare completamente il tipo di cella utilizzata e molte altre proprietà, come ad esempio rendere o meno modificabile una colonna, cambiarne la larghezza eccetera. Nel nostro caso, io rendo tutte le colonne non modificabili, cosa che può sembrare contraddittoria, visto che nelle proprietà della DataGrid abbiamo specificato che l’utente può modificare i dati. Ma visto che vogliamo evitare che l’utente modifichi i dati per caso solo aprendo la Window, vogliamo implementare un tasto che permetta di attivare la modifica o disattivarla.

private void GrAuthors_CellEditEnd(object senderDataGridCellEditEndingEventArgs e)
{
	try
	{
		AuthorItem itm = e.Row.Item as AuthorItem;
		//TODO: Qui salveremo il record al termine della modifica di ogni cella
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.ToString(), "ERRORE"MessageBoxButton.OKMessageBoxImage.Error);
	}
 
}

Il secondo evento scatenato dalla DataGrid che intercettiamo è quello che ci segnala la fine della modifica di una cella. Viene tipicamente scatenato quando da una cella in modifica l’utente si sposta su una cella diversa o su una riga diversa. Questo scatena l’evento e qui verificheremo la modifica del dato e salveremo il valore sul database.

private void GrAuthors_CurrentCellChanged(object senderEventArgs e)
{
	try
	{
		DataGrid grid = sender as DataGrid;
		if (grid != null)
		{
			if (grid.CurrentCell.Item is AuthorItem)
			{
				CurrentSelection = grid.CurrentCell.Item as AuthorItem;
			}
			
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.ToString(), "ERRORE"MessageBoxButton.OKMessageBoxImage.Error);
	}
 
}

Il terzo evento scatenato dalla DataGrid che intercettiamo è lo spostamento da una cella ad un altra, dove implementiamo il codice che modifica la Property che contiene la riga corrente. Se ben ricordate, abbiamo messo in binding sulla property SelectedItem della DataGrid, la property CurrentSelection. Ma, avendo impostato la selezione in modo che possa essere selezionata anche la singola cella, la property CurrentSelection non sarebbe correttamente modificata quando la selezione è una singola cella. Di qui la necessità da parte nostra di modificare manualmente tale selezione.

Andiamo ora a definire il resto della window, ovvero, creiamo i pulsanti di comando sulla prima riga della Grid di base della nostra Window.

<Grid Grid.Row="0">
	<Grid.ColumnDefinitions>
		<ColumnDefinition Width="Auto" />
		<ColumnDefinition Width="*" />
		<ColumnDefinition Width="Auto" />
	</Grid.ColumnDefinitions>
	<StackPanel Grid.Column="0" Orientation="Horizontal">
		<!-- Qui vanno inseriti i button che descriviamo dopo -->
	</StackPanel>
	<StackPanel Grid.Column="2" Orientation="Horizontal">
		<Border
			MinWidth="80"
			MaxWidth="80"
			Margin="1"
			Padding="4"
			HorizontalAlignment="Center"
			VerticalAlignment="Stretch"
			BorderBrush="#FFE0E0E0"
			BorderThickness="1">
			<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
				<Image
					Width="32"
					Height="32"
					HorizontalAlignment="Center"
					VerticalAlignment="Center"
					Source="{Binding FormEditStateImage, UpdateSourceTrigger=PropertyChanged}"
					ToolTip="{Binding FormEditState, UpdateSourceTrigger=PropertyChanged}" />
					</StackPanel>
		</Border>
 
	</StackPanel>
</Grid>

Essendo una parte abbastanza complessa, ne spiego prima la parte di “struttura” e poi i pulsanti di azione. La generiamo come una specie di Toolbar per contenere i Pulsanti di azione a cui aggiungiamo anche un Immagine di stato dove segnaleremo all’utente se si trova in modifica oppure no.

Come è costruito il contenitore per la “toolbar” metto le virgolette perché non è davvero una toolbar, la Toolbar è un controllo WPF che potete usare nei vostri progetti senza problemi, in questo caso io preferisco strutturare i comando come piace a me, con le immagini della grandezza che preferisco. Per fare questo come vedete ho creato una Grid, che ha al suo interno 3 Colonne definite usando l’oggetto Grid.ColumnDefinitions. Questo oggetto permette di definire il numero ed il comportamento delle colonne di una Grid. Nel nostro caso, la prima e la terza sono grandi come il loro contenuto (Auto) mentre la seconda occupa tutto lo spazio disponibile. Questa configurazione mi permette di porre i pulsanti di comando sulla sinistra e l’Immagine di Stato sulla destra.

Per contenere i pulsanti di comando, che descriverò fra poco, ho creato uno StackPanel che ho disposto sulla colonna 0 ed ho dichiarato che il flusso degli oggetti contenuti sarà orizzontale.

Per contenere l’immagine, ho creato un secondo StackPanel, anche questo dichiarato con flusso orizzontale. L’immagine non è costituita dal semplice oggetto Image, ma è contenuta in un oggetto Border, che contiene anche un ulteriore StackPanel. Questo anche per dimostrarvi come costruire il layout dei vostri oggetti in Xaml può essere una operazione non proprio lineare come era mettere gli oggetti in una form in Windows Forms, ma vi da il controllo completo su come i controlli dovranno apparire sulla User Interface.

<Button
	MinWidth="80"
	MaxWidth="80"
	Margin="1"
	Padding="4"
	HorizontalAlignment="Center"
	VerticalAlignment="Stretch"
	VerticalContentAlignment="Top"
	Background="White"
	Click="New_Click"
	ToolTip="Aggiunge un nuovo Autore">
	<StackPanel>
		<Image
		Width="32"
		Height="32"
		Source="pack://application:,,,/Images/new.png" />
		<TextBlock
		Margin="4,2,4,2"
		HorizontalAlignment="Center"
		VerticalAlignment="Center"
		Text="Nuovo"
		TextAlignment="Center"
		TextWrapping="Wrap" />
	</StackPanel>
</Button>
<Button
	MinWidth="80"
	MaxWidth="80"
	Margin="1"
	Padding="4"
	HorizontalAlignment="Center"
	VerticalAlignment="Stretch"
	VerticalContentAlignment="Top"
	Background="White"
	Click="Edit_Click"
	ToolTip="Attiva/Disattiva la modifica dati">
	<StackPanel>
		<Image
		Width="32"
		Height="32"
		Source="pack://application:,,,/Images/edit.png" />
		<TextBlock
		Margin="4,2,4,2"
		HorizontalAlignment="Center"
		VerticalAlignment="Center"
		Text="Modifica"
		TextAlignment="Center"
		TextWrapping="Wrap" />
	</StackPanel>
</Button>
<Button
	MinWidth="80"
	MaxWidth="80"
	Margin="1"
	Padding="4"
	HorizontalAlignment="Center"
	VerticalAlignment="Stretch"
	VerticalContentAlignment="Top"
	Background="White"
	Click="Delete_Click"
	ToolTip="Cancella il record corrente">
	<StackPanel>
		<Image
		Width="32"
		Height="32"
		Source="pack://application:,,,/Images/delete.png" />
		<TextBlock
		Margin="4,2,4,2"
		HorizontalAlignment="Center"
		VerticalAlignment="Center"
		Text="Cancella"
		TextAlignment="Center"
		TextWrapping="Wrap" />
	</StackPanel>
</Button>

Veniamo ora ai tre pulsanti di comando. Anche in questo caso ci limitiamo alle basi, pertanto creiamo un bel button che contiene un immagine e del testo ma usiamo il semplice evento Click per pilotare l’azione, lasciamo l’uso dei Command ad una puntata successiva.

Come potete notare guardando lo xaml dei Button, un button non è altro che un contenitore esattamente come il Border, Lo StackPanel, la Grid. Al suo interno la property “Content” può essere inizializzata con un valore (ad esempio una stringa) oppure, come nei nostri button qui sopra, può contenere un controllo contenitore con ulteriori oggetti al suo interno. In questo caso, io ho usato uno StackPanel con flusso verticale, che  contiene 2 oggetti, Un’immagine ed un Testo. Vediamo cosa abbiamo inizializzato nei nostri controlli, per darvi alcune idee su come funzionano.

Button:

  • MinWidth = 80 Limitiamo la dimensione minima del button per vedere quello che vi abbiamo inserito all’interno.
  • MaxWidth = 80 Limitiamo anche la dimensione massima del button per farli tutti uguali.
  • Margin = 1 Lasciamo un margine uguale attorno al bottone di 1 unità (attenzione, se non espressamente specificato, tutte le unità dimensionali sono standard e se non erro corrispondono ad un novantaseiesimo di pollice indipendentemente dalla risoluzione della device che ospita il programma).
  • Padding = 4 creiamo un margine interno per i controlli contenuti peri a 4 unità.
  • HorizontalAlignment = Center in questo caso è ininfluente visto che siamo in uno StackPanel.
  • VerticalAlignment = Stretch fa in modo che il button occupi tutto lo spazio verticale disponibile (in modo che si adatti alla dimensione del Button più alto.
  • VerticalContentAlignment = Top questa property fa in modo che i controlli dentro ai Button siano tutti allineati in alto.
  • Background = White Il colore di sfondo del button (ovviamente potete usare una rappresentazione ARGB del tipo #FFFFFFFF = bianco #FFFA0000 Rosso ecc.)
  • Click = “NomeEventHandler_Click” è il comando da eseguire al click.
  • ToolTip = Il tooltip che appare quando il mouse è sopra al button

StackPanel:

Non ha alcuna proprietà specifica settata perché per default è un contenitore verticale, quello che ci serve in questo caso.

Image:

L’immagine inserita sul pulsante, è limitata alla dimensione 32 x 32 unità ed è stata inserita come risorsa all’interno del progetto. Pertanto viene embedded nell’eseguibile ed è utilizzabile usando un URI specifico di programma ovvero, nel caso del primo Button: 

pack://application:,,,/Images/edit.png

  • pack:// Indica che si trova nel “package” dell’applicazione
  • application,,, indica che è nell’eseguibile in realtà ci potrebbero essere ulteriori informazioni se invece di essere nell’eseguibile fosse in una diversa Dll.
  • /Images/ indica la cartella dove si trova l’immagine.
  • edit.png è il nome dell’immagine.

TextBlock:

Il testo del Button ha al suo interno una serie di property che stabiliscono come si posiziona e come scrive il proprio testo.

  • Margin = 4,2,4,2 margine attorno al testo Left, Top, Right Bottom
  • HorizontalAlignment = Center (rispetto al contenitore)
  • VerticalAlignment = Center (rispetto al contenitore)
  • Text=Testo visualizzato (Nuovo, Modifica, Cancella).
  • TextAlignment = Center Allineamento del testo
  • TextWrapping = Wrap (se il testo deve andare a capo se fosse più largo del Button.

Ora che abbiamo guardato la parte visuale della Window, andiamo a vedere il codice implementato al suo interno per caricare i dati dal DB e rispondere alle Azioni scatenate al click dei button.

La prima cosa che troviamo nel codice è la definizione della classe ed il suo costruttore, oltre ad un paio di variabili create a livello di classe.

public partial class AuthorsListWindow : WindowINotifyPropertyChanged
{
	BitmapImage mViewImage = new BitmapImage(new Uri("pack://application:,,,/Images/isview.png"));
	BitmapImage mEditImage = new BitmapImage(new Uri("pack://application:,,,/Images/isedit.png"));
	public AuthorsListWindow()
	{
		InitializeComponent();
		DataContext = this;
		Authors = new ObservableCollection<AuthorItem>();
		IsInEditMode = false;
		
	}

Anche se in modalità basica, la nostra Window implementa MVVM, il Model della nostra finestra è la collection di oggetti AuthorItem. la View è la nostra Window, ed il ViewModel è la stessa Window. Questo è il modo più semplice ed efficace di implementare MVVM in una applicazione senza complicarsi la vita con pattern complessi.

Se notate, la nostra AuthorsListWindow è una classe derivata da Window ed implementa l’interfaccia INotifyPropertyChanged. Questa interfaccia implica che noi implementiamo nella classe Window l’evento PropertyChanged e che tutte le Property che interagiscono con la User Interface scatenino tale evento. Se ben ricordate quando abbiamo implementato l’oggetto AuthorItem, anche quella classe implementa l’interfaccia INotifyPropertyChanged, in questo modo le property della classe AuthorItem comunicheranno automaticamente alla User Interface le loro variazioni.

Dopo la dichiarazione della classe e delle sue parent e interface, dichiariamo 2 variabili a livello di classe che contengono due immagini, le immagini sono mappate così come quelle su XAML e contengono rispettivamente l’Immagine che indica lo stato “Visualizzazione” e lo stato “Modifica” della Window.

Nel costruttore, che segue le bitmap abbiamo inserito 3 cose:

  • DataContext = this Questo è il comando che inizializza il viewmodel della Window a se stessa pertanto la sorgente dati per il binding dei controlli è la window stessa.
  • Authors = new ObservableCollection<AuthorItem>() Questa è l’inizializzazione della collection di oggetti AuthorItem che farà da sorgente dati alla DataGrid. Chi ha già giocato un po’ con WPF potrebbe notare che invece di utilizzare un oggetto List<AuthorItem> abbiamo utilizzato una ObservableCollection<AuthorItem> si tratta di una collection specifica per l’uso in applicazioni DataDriven, infatti la collection implementa già di suo gli eventi che informano la user interface (la Datagrid in questo caso) quando un oggetto viene inserito o cancellato dalla collection. List non implementa questi eventi pertanto non è adatta a questo contesto.
  • IsInEditMode = false questo è il flag (dato) che informa la User Interface quando passiamo dalla visualizzazione alla modifica. Vedremo la property fra poco. E vedremo come tale property piloterà la modifica dell’immagine di stato.

Nel codice della window, troverete anche una Property che conterrà la stringa di connessione al database, tale property viene inizializzata dalla MainWindow prima di chiamare questa finestra e da modo alla stessa di interagire con il Database.

 

public bool IsInEditMode
{
	get
	{
		return mIsInEditMode;
	}
	set
	{
		mIsInEditMode = value;
		OnPropertyChanged(FLD_IsInEditMode);
		OnPropertyChanged(FLD_FormEditState);
		OnPropertyChanged(FLD_FormEditStateImage);
 
	}
}

Non metterò tutto il codice delle property, ma solo la parte essenziale, ogni property che interagisce con la UI, ha oltre alla variabile locale che ne contiene il valore, anche una costante con il suo nome che utilizziamo quando solleviamo l’evento On Property Changed. Come credo di avere scritto almeno 100 volte, questo è il mio modo di lavorare, potete usare semplicemente le Stringhe o altro modo in base a come più vi piace.

La property IsInEditMode, pilotà lo stato della Window, pertanto quando viene modificata, scatena anche la modifica della descrizione dello stato della form e della immagine dello stato della form.

 

public BitmapImage FormEditStateImage
{
	get
	{
		if (!IsInEditModereturn mViewImage;
		
		return mEditImage;
	}
	
}
public string FormEditState
{
	get
	{
 
		if (IsInEditModereturn "La datagrid è in modalità modifica";
 
		return "La datagrid è in modalità visualizzazione.";
	}
	
}

La property FormEditState, in base allo stato di IsInEditMode, ritorna una stringa di tooltip adatta allo stato.

public ObservableCollection<AuthorItemAuthors
{
	get
	{
		return mAuthors;
	}
	set
	{
		mAuthors = value;
		OnPropertyChanged(FLD_Authors);
	}
}
public AuthorItem CurrentSelection
{
	get
	{
		return mCurrentSelection;
	}
	set
	{
		mCurrentSelection = value;
		OnPropertyChanged(FLD_CurrentSelection);
	}
}
public event PropertyChangedEventHandler PropertyChanged;
 
protected virtual void OnPropertyChanged(string propertyName)
{
	if (PropertyChanged != null)
		PropertyChanged(thisnew PropertyChangedEventArgs(propertyName));
}

Il codice qui sopra, rappresenta l’implementazione dell’evento PropertyChanged richiesto dall’interfaccia INotifyPropertyChanged.

private void AuthorsList_ContentRendered(object senderEventArgs e)
{
	try
	{
		DpPubs dp = new DpPubs(CnString);
		List<AuthorItemauthors = dp.Authors_Select();
		Authors.Clear();
		foreach (AuthorItem item in authors)
		{
			Authors.Add(item);
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.ToString(), "ERRORE"MessageBoxButton.OKMessageBoxImage.Error);
	}
}

Qui sopra, l’implementazione dell’event handler per l’evento ContentRendered della Window, questo evento viene scatenato quando tutti i controlli della Window sono inizializzati e la window è visibile. Corrisponde all’evento Shown delle Windows Forms per chi le avesse usate. In questo evento, carichiamo il contenuto della DataGrid. Fate attenzione, che questa è la prima bozza della Window, che implementeremo ulteriormente nei prossimi articoli.

Nel codice che abbiamo scritto, istanziamo la classe DpPubs passando la stringa di connessione che ci è stata passata dalla MainWindow, leggiamo gli Authors dalla loro tabella e riempiamo la observable collection che è in Binding con la DataGrid, con i dati ricevuti. Questa operazione ci aspettiamo riempia la lista degli autori.

 

private void New_Click(object senderRoutedEventArgs e)
{
	try
	{
		MessageBox.Show("TODO: Aggiungi");
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.ToString(), "ERRORE"MessageBoxButton.OKMessageBoxImage.Error);
	}
 
}
private void Edit_Click(object senderRoutedEventArgs e)
{
	try
	{
		foreach (DataGridColumn col in GrAuthors.Columns)
		{
			IsInEditMode = !IsInEditMode;
 
			if (col is DataGridTextColumn)
			{
 
				DataGridTextColumn txt = col as DataGridTextColumn;
				if (txt != null)
				{
					string hd = txt.Header as string;
					if ( hd == "ID Autore")
					{
						continue;
					}
					col.IsReadOnly = !col.IsReadOnly;
				}
			}
			else if (col is DataGridCheckBoxColumn)
			{
				DataGridCheckBoxColumn chk = col as DataGridCheckBoxColumn;
				if (chk != null)
				{
					col.IsReadOnly = !col.IsReadOnly;
				}
			}
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.ToString(), "ERRORE"MessageBoxButton.OKMessageBoxImage.Error);
	}
 
}

Il pulsante di Attivazione/Disattivazione modifica, invece è stato implementato, quello che viene fatto all’interno del codice è la seguente cosa:

  • Invertire il flag IsInEditMode (passando da Visualizzazione a Modifica o viceversa.
  • Effettuare un ciclo sulle colonne della datagrid modificando il flag IsReadOnly di tutte le colonne fatto salvo ID Autore, che è la chiave Primaria della tabella Authors e quindi non può essere modificata o scatenerebbe errori se l’autore ha dei libri collegati.

Come potete notare, le colonne si riconoscono dal loro titolo e questo non è esattamente bellissimo ma è quello che passa il “convento”.

private void Delete_Click(object senderRoutedEventArgs e)
{
	try
	{
		MessageBox.Show("TODO: Cancella");
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.ToString(), "ERRORE"MessageBoxButton.OKMessageBoxImage.Error);
	}
 
}

Anche il pulsante di cancellazione non è ancora implementato, ma lo sarà nei prossimi articoli.

E questo conclude la prima parte dell’implementazione della finestra Authors, per ora, quando lanciate il progetto e cliccate il pulsante Autori, questo è quel che succede. (Ho tradotto in italiano i bottoni della MainWindow).

Come potete notare, appare la lista degli autori contenuti nel Db Pubs. Viene visualizzata l’icona di Visualizzazione nell’angolo in alto a destra.

E possiamo entrare in modifica di una cella con un doppio click.

Qui sotto il link per il download del codice a corredo dell’applicazione a questo punto del codice.