Press "Enter" to skip to content

Creare un’applicazione WPF per gestire Dati su un database SqlServer – parte 12 La UI per la modifica dati

Finalmente direte voi, finalmente possiamo descrivere come si esegue la modifica di una tabella database da una window WPF. Visto che avevamo preparato tutto, la sola cosa da fare sarà implementare i metodi. Però, mi sono accorta di non aver messo un pulsante per ricaricare i dati, pertanto l’ho aggiunto alla Window.

Come potete vedere ho aggiunto un Button sulla “toolbar” e l’icona relativa sulla cartella Images, e ricordatevi di indicare che è di tipo Resource e di non copiarla sulla cartella di installazione.

Vediamo subito come abbiamo modificato il codice per gestire il pulsante.

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

Per prima cosa, prendiamo la porzione del codice che carica la DataGrid, che si trova all’interno dell’evento ContentRendered ed estraiamola in un metodo. 

Come vedete dall’immagine, selezionando il testo e premendo l’opzione Quick actions and refactorings… nel menu contestuale e l’opzione Extract Method per spostare le istruzioni di caricamento dati in un metodo.

Una volta creato il metodo ci viene richiesto di dargli il nome corretto, lo chiamerò LoadData.

Una volta creato il metodo LoadData, che abbiamo chiamato dove prima c’era il codice di base, andiamo ad inserire una chiamata anche nel metodo dell’evento Click del nuovo button.

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

Il codice è molto semplice, chiama semplicemente il metodo creato e ricarica i dati dal Database. Ovviamente in una applicazione del mondo reale, per ricaricare i dati potrebbe essere necessario fare ulteriori operazioni, passare filtri o fare altri controlli.

Ora prima di procedere a mostrarvi il codice implementato per la generazione di un nuovo Autore, mentre lavoravo ho scoperto che c’erano un paio di piccoli problemi,  il primo che avevo dimenticato quando ho creato i metodi di aggiornamento ed un altro dovuto al fatto che il database ha alcuni vincoli sulla tabella Authors di cui tener conto quando andiamo ad inserire o modificare i dati.

Vediamo quindi come ho implementato l’inserimento del nuovo Autore e poi come ho modificato i metodi della classe DpAuthors di conseguenza.

 

private void New_Click(object senderRoutedEventArgs e)
{
	try
	{
		InputBoxWindow ibw = new InputBoxWindow("Nuovo Autore""Indicare l'ID per il nuovo autore""");
		ibw.Icon = this.Icon;
		ibw.WindowStartupLocation = WindowStartupLocation.CenterOwner;
		ibw.Owner = this;
		boolgetID = ibw.ShowDialog();
		if (getID.HasValue && getID.Value )
		{
			if (ibw.InputText != null && ibw.InputText.Length > 0)
			{
				if (Authors.Any(x => x.AuthorID.ToLower() == ibw.InputText.Trim().ToLower()))
				{
					MessageBox.Show("Esiste già un autore con l'ID indicato scegliere un ID Diverso");
				}
				else
				{
					DpPubs dp = new DpPubs(CnString);
					AuthorItem author = new AuthorItem();
					author.AuthorID = ibw.InputText;
					author.AuthorFirstName = "Da modificare";
					author.AuthorLastName = "Da modificare";
					author.Phone = "555";
					DataResult inserted = dp.Authors_Insert(author);
					if (inserted.HasError)
					{
						MessageBox.Show(inserted.ErrorMessage"Errore"MessageBoxButton.OKMessageBoxImage.Error);
					}
					else
					{
						Authors.Add(author);
						CurrentSelection = author;
						GrAuthors.ScrollIntoView(author);
 
					}
				}
			}
			else
			{
				MessageBox.Show("L'ID del nuovo Autore non può essere vuoto.");
			}
		}
		else
		{
			MessageBox.Show("Annullato dall'utente");
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.ToString(), "ERRORE"MessageBoxButton.OKMessageBoxImage.Error);
	}
 
}

Per inserire un nuovo autore, nel metodo che risponde al Click del button New, eseguiamo le seguenti operazioni:

  • Creiamo la nostra InputBox, inserendo il titolo e l’istruzione per l’utente, non mettiamo nulla sul codice che deve essere inserito.
  • Aggiorniamo l’icona della InputBox, indichiamo di aprirla nel centro della finestra attuale, e indichiamo che la nostra finestra AuthorsList è l’Owner della InputBox in modo che appaia nel suo centro, ed infine chiamiamo la ShowDialog che mostra la finestra come Modale.
  • Testiamo il valore reso dalla chiamata alla Dialog. Al contrario di quanto era per le Windows Forms dove il valore reso era un’enumerazione, la Dialog WPF restituisce un Nullable Boolean, ovvero True, False o Null come valore. E se ben ricordate il codice dentro alla InputBox, abbiamo posto DialogResult (il valore reso da ShowDialog) a True se l’utente preme OK oppure a False se preme Annulla. Pertanto la verifica è: se la chiamata ha tornato un valore (non è null) e tale valore è true, allora andiamo avanti. Altrimenti daremo messaggio di errore.
  • Il valore dell’ID che l’utente ha digitato lo ricaviamo leggendo la property InputText dall’oggetto InputBoxWindow (ibw), verifichiamo per prima cosa che non sia nullo o vuoto. Se lo fosse diamo messaggio d’errore, altrimenti proseguiamo.
  • Prima di tutto verifichiamo che non esista un altro autore con il codice digitato dal nostro utente, se l’ID esistesse già, lo segnaliamo ed usciamo, se invece non esiste proseguiamo.
  • Creiamo un istanza della classe DpPubs per poter usare il metodo insert.
  • Creiamo un istanza della classe AuthorItem, in cui inseriamo l’ID che abbiamo ricevuto dalla InputBox. Inoltre, inseriamo un valore “placeholder” nel Nome, Cognome e Telefono dell’autore, perché la tabella database, contiene dei vincoli che rendono obbligatorio che tali campi non siano nulli, quindi se non li inizializziamo, riceveremo un errore.
  • Chiamiamo il Metodo Authors_Insert, per inserire il nuovo autore. Verifichiamo se è andato a buon fine o se ci restituisce un errore. In caso di errore diamo un messaggio ed usciamo.
  • Se va tutto bene, aggiungiamo il nuovo autore alla collection Authors collegata alla DataGrid e selezioniamo il nuovo record, infine indichiamo alla DataGrid di effettuare uno scroll in modo che la nuova riga sia visibile.

Ora che abbiamo descritto il codice per la Insert, vediamo cosa ho modificato nel metodo Authors_Insert della classe DpPubs per far funzionare correttamente l’inserimento.

 

public DataResult Authors_Insert(AuthorItem author)
{
	DataResult ret = new DataResult();
	try
	{
 
		using (SqlConnection cn = new SqlConnection(CnString))
		{
			cn.Open();
			SqlTransaction tn = cn.BeginTransaction();
			SqlCommand cmd = new SqlCommand();
			cmd.Connection = cn;
			cmd.Transaction = tn;
			cmd.CommandText = SQL_Authors_Insert;
			cmd.Parameters.Clear();
			cmd.Parameters.AddWithValue("@au_id"author.AuthorID);
			cmd.Parameters.AddWithValue("@au_lname"author.AuthorLastName != null ? author.AuthorLastName : System.DBNull.Value );
			cmd.Parameters.AddWithValue("@au_fname"author.AuthorFirstName != null ? author.AuthorFirstName : System.DBNull.Value);
			cmd.Parameters.AddWithValue("@phone"author.Phone != null ? author.Phone : System.DBNull.Value);
			cmd.Parameters.AddWithValue("@address"author.Address != null ? author.Address : System.DBNull.Value);
			cmd.Parameters.AddWithValue("@city"author.City != null ? author.City : System.DBNull.Value);
			cmd.Parameters.AddWithValue("@state"author.State != null ? author.State : System.DBNull.Value);
			cmd.Parameters.AddWithValue("@zip"author.Zip != null ? author.Zip : System.DBNull.Value);
			cmd.Parameters.AddWithValue("@contract"author.Contract);
 
			try
			{
				cmd.ExecuteNonQuery();
				ret.Ok = true;
				tn.Commit();
			}
			catch (Exception iex)
			{
				ret.ErrorMessage = iex.ToString();
				tn.Rollback();
			}
 
 
 
		}
	}
	catch (Exception ex)
	{
 
		ret.ErrorMessage = ex.ToString();
	}
	return ret;
 
}

Qui sopra la nuova versione del metodo Insert per la tabella Authors, se lo comparate con il precedente, vedrete che la modifica che ho effettuato riguarda le righe in cui genero i parametri con i valori da assegnare ai campi della tabella, la modifica è stata la seguente:

cmd.Parameters.AddWithValue("@au_lname"author.AuthorLastName != null ? author.AuthorLastName : System.DBNull.Value);
// Era
cmd.Parameters.AddWithValue("@au_lname"author.AuthorLastName );

La nuova versione della composizione dei parametri per la INSERT controlla il valore delle stringhe e se queste fossero nulle vengono sostituite con il valore System.DBNull.Value. Poichè se il valore di un parametro viene impostato a null, tale parametro non viene generato da ADO.Net provocando un errore perché il metodo INSERT non trova uno dei parametri che si aspetta.

Di qui, la if compatta che ho usato, molto simile alla iif di Access per chi avesse usato il VBA.

Condizione ? ValoreSeVero : ValoreSeFalso;

Tale necessità è identica anche per l’Update, pertanto se osservate anche il metodo Update è stato modificato.

Non solo, anche sul metodo Select, per il caricamento dei dati ho inserito l’operazione contraria, ovvero se il valore letto dalla tabella fosse System.DBNull.Value, allora lo converto in null.

Se qualcuno di voi si sta chiedendo perché il valore NULL del Database non sia automaticamente convertito in null C# è una domanda che mi pongo dal 2003, quando ho iniziato a usare C# e ancora non ho idea di quale sia la necessità per cui ci sono valori diversi. Bisogna chiederlo al papà di C#. Mads Torgersen.

Ma torniamo ai nostri metodi e vediamo come aggiornare il record che l’utente aggiornasse e come cancellare un autore.

private void GrAuthors_CellEditEnd(object senderDataGridCellEditEndingEventArgs e)
{
	try
	{
		AuthorItem itm = e.Row.Item as AuthorItem;
		DpPubs dp = new DpPubs(CnString);
		DataResult updated = dp.Authors_Update(itm);
		if (updated.HasError)
		{
			MessageBox.Show(updated.ErrorMessage"ERRORE"MessageBoxButton.OKMessageBoxImage.Error);
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.ToString(), "ERRORE"MessageBoxButton.OKMessageBoxImage.Error);
	}
 
}

La modifica del record viene salvata sull’evento CellEditEnd, pertanto solo quando l’utente si sposta su una cella diversa dopo la modifica la sua modifica viene salvata. Fate attenzione che come tutorial ci fermiamo qui, ma nel mondo reale ci possono essere ulteriori controlli da inserire e modifiche da salvare (ad esempio prima di chiudere la window).

Per salvare la modifica, utilizziamo l’oggetto e.Row.Item che la datagrid ci passa come parametro e lo passiamo all’istanza di DpPubs che generiamo per chiamare il metodo Authors_Update. Controlliamo poi che l’aggiornamento sia andato a buon fine, in caso contrario diamo messaggio di errore.

 

private void Delete_Click(object senderRoutedEventArgs e)
{
	try
	{
		if (CurrentSelection == null)
		{
			MessageBox.Show("E' necessario selezionare un autore per cancellarlo.""Avviso"MessageBoxButton.OKMessageBoxImage.Warning);
			return;
		}
		DpPubs dp = new DpPubs(CnString);
		DataResult deleted = dp.Authors_Delete(CurrentSelection.AuthorID);
		if (deleted.HasError)
		{
			MessageBox.Show(deleted.ErrorMessage"Errore"MessageBoxButton.OKMessageBoxImage.Error);
			return;
		}
		
		Authors.Remove(CurrentSelection);
		CurrentSelection = null;
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.ToString(), "ERRORE"MessageBoxButton.OKMessageBoxImage.Error);
	}
 
}

Ultimo ma non meno importante il codice per cancellare un Autore. In cui effettuiamo le seguenti operazioni:

  • Verifichiamo se l’autore può essere cancellato, ovvero se non ha alcun libro collegato. In caso contrario non permettiamo la cancellazione.
  • Usiamo la classe DpPubs per cancellare l’autore.
  • In caso di errore diamo un messaggio all’utente.
  • Se va tutto bene, togliamo il record dalla collection e cancelliamo la selezione.

Con queste operazioni abbiamo concluso la serie con la gestione di tutto quel che serve per interagire con il database senza passare per cose troppo complesse.

Ovviamente, in una applicazione nel mondo reale quello che abbiamo fatto non basta, però può dare a chi inizia a giocare con C# e i database una spinta per iniziare a produrre qualcosa di funzionante, che è lo stimolo migliore per voler proseguire.