Press "Enter" to skip to content

12 – Lavorare con i dati – User Interface come gestire una tabella

In questo articolo vedremo come iniziare a generare la User Interface per la gestione della tabella Users del nostro database, prima di iniziare a scrivere XAML e codice C# però parliamo un attimo delle varie tecniche che possiamo utilizzare per modificare una tabella database.

Un po’ di idee sparse

Per creare un interfaccia utente che permetta di modificare una singola tabella database, ci sono ovviamente varie tecniche che possiamo usare vediamone alcune:

  • Possiamo creare una Window con la lista degli elementi ed una seconda window di dettaglio che ne permetta inserimento e modifica.
  • Possiamo creare una Window unica che contiene sia la lista degli elementi che il dettaglio per la modifica.
  • Possiamo creare una Window con una lista degli elementi modificabile ed usare la lista stessa per inserire e modificare i dati.

La prima tecnica è adatta a tabelle complesse, con decine di campi dove inserire i dati su un unica Window renderebbe difficile il lavoro a causa dello spazio limitato.

La seconda tecnica è adatta a tabelle singole non troppo complesse, dove anche su una macchina con una risoluzione limitata sia possibile costruire lista e dettaglio in modo chiaro.

La terza tecnica è adatta a tabelle molto piccole, come ad esempio le tabelle codice e descrizione, unità di misura, valute, nazioni, categorie ecc.

Nel nostro caso, visto il numero contenuto di campi della tabella, utilizzerò la seconda modalità.

Per gestire le modifiche alla nostra tabella, quando riceveremo gli oggetti User in essa contenuti, dovremo tener conto di tutti i dati inseriti, modificati, cancellati sulla User Interface in modo da passare poi le modifiche al database quando l’utente premesse il tasto Salva.

Per costruire la User Interface, utilizzando WPF, potremo scegliere due diversi tipi di “fisionomia” se posso definirla così, una interfaccia a Finestra singola con un sistema di Tab control e Tab Item, con un singolo ribbon. Un sistema multi window, con una finestra diversa per ogni tabella. Un sistema che prevede un Dock Manager, evoluzione dell’interfaccia MDI per costruire un interfaccia a “pannelli” come ad esempio è Visual Studio.

Nel nostro caso, faremo la cosa più semplice, ovvero un interfaccia multi finestra, ove ogni finestra è una scatolina a se stante con tutte le sue funzionalità. In futuri articoli, se richiesto, vedremo come utilizzare le tecniche più complesse, che riguardano ovviamente progetti complessi.

Considerato che non abbiamo mai parlato del Ribbon, ovvero il sistema di menu in stile Office, non prenderò neppure in considerazione di utilizzarlo nella nostra User Interface, perché da solo avrebbe bisogno di una serie di articoli per essere spiegato completamente.

Pertanto, lo “simuleremo” e ci metto le virgolette perché più che un ribbon simuleremo una toolbar, e creeremo dei button che forniranno le funzionalità necessarie.

Questo primo step nel mondo delle User interface, ci permetterà di concentrarci un po’ di più sull’aspetto funzionale (come modificare la tabella) che è lo scopo di questi articoli, piuttosto che sull’aspetto visuale della User Interface, di cui avremo modo di parlare in futuro.

Il view model

Sono certa che più di qualcuno di coloro che leggono questo articolo, hanno sentito parlare o hanno letto del materiale su MVVM ovvero Model View, ViewModel, che è il paradigma di programmazione che è stato “creato” per così dire per WPF. Vi rimando a Wikipedia per leggere la teoria che lo riguarda.

Nella nostra applicazione, il Model dell’MVVM è ciò che ci fornisce la libreria dei servizi, quindi i dati veri e propri. View è la nostra Window, quindi l’oggetto che interagisce con l’utente. ViewModel è invece la classe che fa interagire questi due, pertanto fornisce i dati alla Window e riceve le modifiche da parte dell’utente che poi trasmette al Model che ovviamente poi interagisce con il database vero e proprio.

Se avete letto alcuni degli altri articoli che riguardano il lavoro con WPF di questo blog, sapete già che il ViewModel se si tratta di porzioni di User Interface molto specializzate e semplici non deve necessariamente essere posto in una classe diversa da quella della Window o dello UserControl che compongono l’applicazione, può essere utilizzata la classe stessa. Se in una applicazione avessimo più Window/User Control che interagiscono in modi diversi con gli stessi dati o se ritenessimo conveniente avere non una singola tabella ma un gruppo di tabelle che hanno delle interazioni con varie Windows/User Control, potremmo creare un ViewModel unico usato in tutte queste finestre.

Non consiglio anche se questa è una preferenza personale, non un pattern suggerito, di creare un unico ViewModel per tutta una applicazione a meno che non vi sia una necessità specifica per farlo. Per la nostra applicazione UsersDb, creeremo una classe ViewModel esterna alla nostra finestra che gestirà i dati della User Interface e le interazioni con la libreria dei servizi.

Quando decidete di lavorare in questo modo ricordatevi che:

  • Il ViewModel deve essere in Binding ai componenti e controlli della Window/User Control tramite le proprie properties.
  • Il ViewModel non è direttamente agganciato ai controlli.
  • Tutta l’interazione diretta con i controlli dovrebbe essere fatta nel code behind della Window o User Control.
  • Non deve esserci interazione diretta ne devono esservi riferimenti diretti fra i controlli della Window e il ViewModel (il fatto che un modello possa servire più Views lo indica direttamente)
  • Tutte le interazioni per quanto minimali fra Window e componenti XAML devono rimanere nel Code Behind ed essere inviate al ViewModel solo come dati.

Come Tener traccia delle modifiche

Abbiamo esaminato come creare la User Interface, prima di iniziare con il codice, però vediamo come fare a tener traccia delle modifiche.

Quando la libreria servizi ci fornisce i dati per visualizzarli e modificarli, abbiamo utilizzato una List di oggetti User, ma se avete letto con attenzione gli articoli che lavorano su WPF, sapete che le collezioni più adatte a lavorare con la UI sono le ObservableCollection, che sono state sviluppate in modo da fornire alla User Interface una serie di automatismi che mappano in modo interattivo anche le modifiche effettuate da codice, in modo che siano riflesse dalla UI automaticamente.

Quando i dati vengono modificati è indispensabile essere in grado di fare alcune cose specifiche:

  • Tenere traccia di tutte le modifiche ai record esistenti
  • Tenere traccia di tutti i record inseriti
  • Tenere lista di tutti i record che si trovano sul database che venissero cancellati
  • Permettere di effettuare l’undo delle modifiche effettuate se necessario.

Ci sono tecniche diverse che si possono utilizzare per farlo, che probabilmente dovranno essere utilizzate in base alla quantità di dati e al tipo di modifiche che vi verranno effettuate.

La tecnica che ho deciso di utilizzare per questo nostro esperimento è quella di conservare una copia dei record letti originalmente sul database e crearne un clone per le modifiche. Aggiungerò anche due collezioni accessorie, la collezione dei record inseriti e quella dei record cancellati.

Le modifiche alla classe User.cs

Per iniziare il nostro progetto, abbiamo bisogno di aggiungere un po’ di codice alla classe User, infatti per poter tenere traccia delle modifiche abbiamo bisogno di due funzionalità, la prima è poter creare un Clone dello User, poi abbiamo bisogno di poter vedere quali saranno gli user modificati, pertanto ci servirà un metodo CompareTo.

public class User : INotifyPropertyChanged, IComparable

La prima modifica che facciamo è aggiungere alla nostra classe l’interfaccia IComparable, chiedendo poi a Visual Studio di implementarla. Questa interfaccia prevede l’implementazione di un singolo metodo, il metodo CompareTo, il cui scopo è quello di comparare due oggetti e verificare se sono uguali oppure no.

public int CompareTo(object obj)
{
	throw new NotImplementedException();
}

Visual Studio ci crea la bozza di un metodo, che prende in input un object e restituisce un int. Anche se non abbiamo ancora approfondito le interfacce, essendo queste qualcosa di complesso, e collegato al polimorfismo, che è parte di qualcosa di più avanzato di quanto sia l’obiettivo di questi articoli, diciamo pure semplificando che un Interfaccia è un contratto che obbliga lo sviluppatore ad implementare all’interno di una classe uno o più metodi e/o una o più property per saperne di più il link a MSDN. In questo caso, l’interfaccia IComparable, richiede che implementiamo un singolo metodo, che permette di comparare due oggetti. Questo metodo, prende in input un oggetto qualsiasi, e fornisce in output un numero intero che deve valere:

  • Zero, se gli oggetti sono uguali
  • Minore di zero, se l’oggetto è più piccolo di quello con cui viene comparato.
  • Maggiore di zero se l’oggetto è più grande di quello con cui viene comparato.

Ovviamente la comparazione di due oggetti non banali, come possono essere i nostri User, richiede di scrivere un po’ di codice, ma non è difficile ne complicato, si tratta di una cascata di if.

Però, quando si tratta di comparare oggetti, è necessario tener conto di alcune possibilità e decidere quali regole seguire quando queste si verificano. In particolare, quando le property di un oggetto sono nulle, come ci vogliamo comportare? Queste sono decisioni che riguardano chi crea il progetto, perché siamo noi che stabiliamo come vogliamo che il programma si comporti.

Pertanto, prima di creare il contenuto del metodo CompareTo, creeremo un Extension dell’interfaccia IComparable. Dove la inseriamo?

12_UsersDb_08_01_IComparableExtension[6]

L’extension potrà essere utilizzata ovunque nel progetto, pertanto la inseriamo nella libreria Jolly. In realtà, così come le extension per la gestione del DbNull, essendo delle funzioni di estensione generiche, potremmo creare una libreria di base riutilizzabile in tutti i nostri progetti e inserirvele all’interno in modo da usarle dove preferiamo.

Nell’immagine qui sopra, potete vedere come ho usato il metodo Add New Item del progetto UsersEntities ed ho creato una classe che si chiama IComparableExtensions in cui inserirò il codice della mie prima extension se in futuro mi servissero altre extension potrò aggiungerle sempre a questa classe:

public static int XxCompareTo(this IComparable objVal, IComparable newVal)
{
	int ret = 1;
	try
	{
		if (objVal != null)
		{
			if (newVal != null)
			{
				ret = objVal.CompareTo(newVal);
			}
		}
		else
		{
			if (newVal == null)
			{
				ret = 0;
			}
			else
			{
				ret = -1;
			}
		}
	}
	catch (Exception)
	{
		//We ignore exceptions
	}
	return (ret);
}

E’un solo metodo, e come gli altri metodi Exstension facciamo iniziare il suo nome con un Xx, è un semplice trucco che mi piace usare per trovare velocemente i miei metodi extension in mezzo a tutti gli altri ma come sempre è una scelta personale, pertanto potete usare la nomenclatura che preferite.

Come potete notare, il metodo è stato creato in modo tale che usi le seguenti regole per la gestione degli elementi nulli:

  • Un oggetto null è sempre minore di qualsiasi oggetto con cui è comparato.
  • Un oggetto non null è sempre maggiore di un oggetto null.

Fatto questo possiamo iniziare a scrivere il nostro metodo CompareTo nella classe User.

public int CompareTo(object obj)
{
	int ret = -1;
	if (obj is User)
	{
		User usr = obj as User;
		if (usr != null)
		{
			ret = this.ID.CompareTo(usr.ID);
			if (ret == 0)
			{
				ret = this.WinUser.XxCompareTo(usr.WinUser);
				if (ret == 0)
				{
					ret = this.WinDomain.XxCompareTo(usr.WinDomain);
					if (ret == 0)
					{
						ret = this.UserName.XxCompareTo(usr.UserName);
						if (ret == 0)
						{
							ret = this.Computer.XxCompareTo(usr.Computer);
							if (ret == 0)
							{
								ret = this.Password.XxCompareTo(usr.Password);
								if (ret == 0)
								{
									ret = this.LoginType.XxCompareTo(usr.LoginType);
								}
							}
						}
					}
				}
			}
		}
	}
	return ret;
}

Come presumo possiate notare dal codice qui sopra, una funzione di comparazione non è altro che una cascata di IF, l’esecuzione della cascata si ferma se 2 delle property sono diverse (non importa se maggiori o minori) mentre prosegue quando tutte le property sono uguali. Nel nostro caso, abbiamo inserito la comparazione di tutte le property di User, potrebbero esservi classi in cui non è necessario farlo, dipende da quello che è l’uso che si farà della comparazione, pertanto ogni caso dovrete valutarlo indipendentemente.

Ora che abbiamo creato il metodo di comparazione fra oggetti, che ci permetterà di rilevare i record modificati, considerato che abbiamo deciso di creare una copia dei record esistenti passandola alla User Interface, per poi usare gli originali per rilevare le modifiche, abbiamo bisogno di una funzione di Clonatura dell’oggetto User.

Vi invito a osservare il seguente codice:

public static ObservableCollection<User> ShallowCopy(List<User> listToCopy)
{
	ObservableCollection<User> ret = new ObservableCollection<User>();
	foreach (User usr in listToCopy)
	{
		ret.Add(usr);
	}
	return (ret);
}

Sono certa che più di qualcuno, osservando questo codice potrebbe pensare che è questa l’operazione che permette di clonare gli User dalla List origine letta dal database alla Observable collection per la User Interface, però, se avete studiato un po’ sapete che quando fate questa operazione create una copia della collection, ma non una copia degli oggetti, infatti, l’operazione semplicemente aggiunge gli oggetti esistenti alla seconda collection usando un reference, pertanto qualsiasi modifica effettuata agli User dell’Observable Collection si rifletterà sugli User Della List di origine. Questo è il motivo per cui ho chiamato il metodo ShallowCopy (copia superficiale).

Per la nostra gestione dati, abbiamo bisogno invece di duplicare gli oggetti User, pertanto implementeremo nella nostra classe User i metodi necessari alla clonazione degli oggetti.

Parlo di Metodi al plurale perché nella mia esperienza ho verificato che è più comodo, perché spesso serve utilizzare entrambi i metodi, dividere la clonazione in due diversi metodi.

  1. il metodo Copy, che copia fisicamente la classe in un altra classe dello stesso tipo.
  2. Il metodo Clone, che genera il nuovo oggetto e poi chiama il metodo Copy.

Vediamo come ho fatto.

public void Copy(User item)
{
	item.ID = this.ID;
	item.Computer = this.Computer;
	item.LoginType = this.LoginType;
	item.Password = this.Password;
	item.UserName = this.UserName;
	item.WinDomain = this.WinDomain;
	item.WinUser = this.WinUser;
}

Il primo metodo è il metodo Copy, data una classe User come parametro, vi copia property per property l’istanza corrente della classe.

public User Clone()
{
	User item = new User();
	Copy(item);
	return item;
}

Il secondo metodo è il metodo Clone, che crea una copia dell’istanza corrente di una classe User restituendola al chiamante.

Questa è una Deep Copy dell’oggetto, ovvero una copia che crea un nuovo oggetto identico al precedente ma non è lo stesso oggetto.

Adesso che abbiamo modificato la nostra classe User, aggiungendo quanto necessario, potremo iniziare a generare il ViewModel necessario a creare la user interface per la gestione della tabella di UsersDb.

Riepilogo

Cosa abbiamo spiegato in questo articolo:

  • Abbiamo indicato alcuni modi per creare una User Interface per la gestione di una tabella database e deciso come costruiremo quella del nostro progetto.
  • Abbiamo spiegato alcuni concetti relativi al ViewModel del paradigma MVVM.
  • Abbiamo indicato alcune modalità con cui gestire le modifiche effettuate sulla User Interface per rifletterle sul database.
  • Abbiamo modificato la classe User per predisporre il necessario a poter gestire le modifiche nel modo che abbiamo scelto per questa tabella.
  • Abbiamo implementato un metodo Extension dell’Interfaccia IComparable
  • Abbiamo implementato i metodi Copy e Clone di una classe.

Potete scaricare il codice del progetto al seguente link:

Per qualsiasi domanda, curiosità, o per segnalare un errore, potete utilizzare il link al modulo di contatto in cima alla pagina.