Press "Enter" to skip to content

3 – Uno user control per gestire i dati di connessione a SQL Server

In questo terzo post della serie dedicata agli user control WPF  parleremo di alcune differenze fondamentali fra WPF e Windows Forms e inizieremo a creare le classi per il nostro User control.

La differenza fondamentale fra un applicazione Windows Forms ed una applicazione WPF è che mentre Windows Forms è prettamente una modalità di programmazione Event Driven, ovvero l’interfaccia cambia il proprio stato e reagisce in base agli eventi che si verificano al suo interno, WPF invece funziona in modalit Data Driven, ovvero lo stato dell’interfaccia viene modificato dalla modifica di dati al suo interno.

Qual’è il vantaggio e la potenza di tutto questo? inizialmente, vista la quantità di cose da fare per  vedere qualche risultato, piú che un vantaggio a me è sembrato un modo per procurare mal di testa al povero programmatore, in realtà i vantaggi sono molteplici, ci sono delle cose da fare in piú è vero ma ci sono anche molte cose che poi arrivano automaticamente, è piú facile regolare il comportamento dell’interfaccia ed essere certi che sia quello che vogliamo, è piú facile fare manutenzione o aggiungere funzionalità ed agganciarle a comportamenti già costruiti.

Scommetto che vi ho confuso un poco, quindi come solitamente faccio cercheró di fare degli esempi concreti per spiegare concetti rimandando alla vostra voglia di studiare la ricerca di documenti che trattano la programmazione MVVM in WPF, invece proveremo a realizzare un oggetto funzionante e vedere l’effetto che fa.

La classe SqlConnectionInfoControl

Nel progetto DnwUISqlServer andiamo a creare tre cartelle che utilizzeremo per realizzare lo User Control.

wpf_uc_06

Nella cartella Controls andremo ad inserire la classe dello User Control, Nella cartella Images metteremo le risorse di tipo immagine, nella cartella Models andremo ad inserire il Model (o View Model) del nostro User Control.

Posizionandoci sulla cartella Controls Andiamo ad aggiungere una classe e selezioniamo WPF – User Control, la chiamiamo SqlConnectionInfoControl, e visual studio genererà due files, un file .xaml ed un file .xaml.cs per il Code behind della nostra classe. Il file XAML conterrà semplicemente la definizione dello User Control e un controllo Grid vuoto, il Code Behind sarà completamente vuoto.

wpf_uc_07

Qui sopra l’immagine di come sarà il nostro User Control completo, come facciamo a inserire tutti i controlli che contiene?

Iniziamo dal controllo contenitore, quello dentro a cui sono inseriti tutti gli altri controlli.

<Grid>
		<Grid.RowDefinitions>
			<RowDefinition Height="Auto"/>
			<RowDefinition Height="Auto"/>
			<RowDefinition Height="*"/>
			<RowDefinition Height="Auto"/>
		</Grid.RowDefinitions>
		<Grid.ColumnDefinitions>
			<ColumnDefinition Width="4*"/>
			<ColumnDefinition Width="5*"/>
		</Grid.ColumnDefinitions>
.......
</Grid>

Il controllo primario, una grid in cui abbiamo definito quattro righe e due colonne, i puntini nello XAML qui sopra contengono un bel po’ di cose, visto che dovremo spiegare vari concetti, lo faremo esaminando lo XAML per strati.

Cosa possiamo annotare relativamente alla definizione della grid? Solamente quello che abbiamo inserito negli attributi Height delle righe e Width delle colonne. XAML ha degli strani mezzi per definire la dimensione dei controlli, possiamo mettere direttamente la dimensione in punti Height=”120″ oppure come nel nostro caso possiamo dare delle indicazioni al sistema e dirgli di arrangiarsi. Vediamo come:

  • Auto = usato sulle dimensioni dei controlli indica che si devono adattare alla dimensione del loro contenuto.
  • * = indica che l’oggetto deve occupare tutto lo spazio non occupato dagli altri controlli
  • 4* o 5* = Indica che le due colonne vogliono occupare tutto lo spazio disponibile ma in proporzione diversa, 4* sarà piú piccola di 5* le proporzioni possono essere espresse con qualsiasi numero fate qualche prova per divertirvi se volete.
<Menu Grid.Row="0" Grid.ColumnSpan="2" Height="38" Margin="10,10,10,0"
  VerticalAlignment="Top" BorderThickness="2"   Foreground="Black" FontSize="16" FontWeight="Normal">
........
</Menu>

Se osservate lo Xaml completo del nostro controllo, troverete che la porzione qui sopra riportata, ovvero il controllo Menu, è posizionato all’interno del TAG Grid che abbiamo osservato per primo, questo controllo è pertanto contenuto all’interno della grid principale, dove verrà posizionato, se non indicassimo nulla, verrebbe inserito a Riga 0 e Colonna 0 della grid, nel nostro caso, abbiamo indicato specificamente che si troverà Sulla prima riga della Grid ed occuperà due colonne. Abbiamo anche indicato una serie di Attributi che indicano al sistema come vogliamo che il menu sia formattato, Altezza, margine rispetto al controllo contenitore, allineamento verticale, bordo, dimensione e colore della font. Tutti gli attributi di formattazione, cosí come in HTML possono essere inseriti in uno stile CSS, in XAML possono essere definiti in un TAG Stile assegnato poi al controllo. Vedremo come si fa a farlo proseguendo in questo esempio.

<Grid Grid.Row="1" Grid.ColumnSpan="2" >
	<Grid.RowDefinitions>
	<RowDefinition Height="Auto" />
	</Grid.RowDefinitions>
	<Grid.ColumnDefinitions>
		<ColumnDefinition Width="*"/>
		<ColumnDefinition Width="120" />
	</Grid.ColumnDefinitions>
........
</Grid>

Una ulteriore Grid, anche questa è contenuta nella grid primaria ed è posizionata sulla seconda riga della stessa, occupa anche essa entrambe le colonne della Grid Primaria e a sua volta contiene due colonne ed una riga, non abbiamo aggiunto alcuna nuova nozione nello XAML precedente, ma abbiamo usato gli attributi di riga e le opzioni per le misure per righe e colonne.

<ListBox Grid.Row="2" Grid.Column="0" ItemsSource="{Binding SqlConnections}" SelectedItem="{Binding CurrentConnection}">
......... 
</ListBox>

Il terzo controllo inserito nella grid principale, in questo caso, occupiamo la prima colonna della terza riga della grid primaria , il controllo è una listbox e per descriverlo abbiamo utilizzato un attributo già descritto, quello che lo posiziona sulla riga giusta ed abbiamo aggiunto l’attributo che stabilisce in quale colonna deve essere posizionato, poi abbiamo indicato due ulteriori attributi, ItemSource e SelectedItem, questi due attributi non hanno al loro interno un valore, ma un codice con una notazione nuova, si tratta del primo esempio di Binding di Attributi (proprietà) di un controllo al contenuto del Model che agganceremo a questo User Control.

{Binding NomeProperty}

Questa notazione dice all’attributo: Il tuo valore è rappresentato dal contenuto della Property del model che ti è stata indicata.

Introduciamo la classe SqlConnectionInfoModel

Prima di proseguire con l’analisi dello XAML del controllo, introduciamo la classe ViewModel che agganceremo al code behind del controllo.

public class SqlConnectionInfoModel : INotifyPropertyChanged
{
.....
	public event PropertyChangedEventHandler PropertyChanged;

	protected virtual void OnPropertyChanged(string pPropertyName)
	{
		if (PropertyChanged != null)
			PropertyChanged(this, new PropertyChangedEventArgs(pPropertyName));
	}
}

La classe ViewModel, che creiamo nella cartella Models del nostro progetto, ha solo una caratteristica indispensabile perché funzioni correttamente nel ruolo a lei assegnato. Deve implementare l’interfaccia INotifyPropertyChanged e tutte le property che sono agganciate in Binding al controllo di cui lei è il ViewModel devono scatenare l’evento PropertyChanged alla loro variazione in modo che la UI possa reagire all’evento ed aggiornarsi. Visto che la listbox utilizza due property di questa classe, SqlConnections e CurrentConnection, vediamo come sono implementate per capire di piú sul funzionamento del modello e della UI ad essa collegata.

public const string FLD_SqlConnections = "SqlConnections";

private SqlConnectionInfosCollection mSqlConnections;

public SqlConnectionInfosCollection SqlConnections
{
	get
	{
		return mSqlConnections;
	}
	set
	{
		mSqlConnections = value;
		OnPropertyChanged(FLD_SqlConnections);
	}
}
public const string FLD_CurrentConnection = "CurrentConnection";

private SqlConnectionInfo mCurrentConnection;

public SqlConnectionInfo CurrentConnection
{
	get
	{
		return mCurrentConnection;
	}
	set
	{
		if (mCurrentConnection != null)
		{
			mCurrentConnection.PropertyChanged += CurrentConnection_PropertyChanged;
		}
		mCurrentConnection = value;
		if (mCurrentConnection != null)
		{
			mCurrentConnection.PropertyChanged += CurrentConnection_PropertyChanged;
		}
		OnPropertyChanged(FLD_CurrentConnection);
		OnPropertyChanged(FLD_SqlConnectionIsInEditMode);
		OnPropertyChanged(FLD_IsInEditMode);

	}
}

void CurrentConnection_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
	if (this.IsInEditMode)
	{
		this.IsChanged = true;
	}
	switch (e.PropertyName)
	{
		case SqlConnectionInfo.FLD_Trusted:
			OnPropertyChanged(FLD_SqlConnectionIsInEditMode);
			break;
	}
}

Come possiamo vedere, le due property sono definite in modo standard per C#, abbiamo la variabile privata, e il Getter ed il setter, in piú solleviamo l’evento PropertyChanged quando il valore della property viene Settato. La costante con il nome della property è una delle regole che utilizzo nel codice, non è indispensabile creare la costante, vedremo peró che creare la costante puó tornarci utile se dobbiamo sollevare l’evento per una property in piú punti, ma non allontaniamoci da dove siamo ora.

SqlConnections è un istanza della collection che abbiamo creato per la gestione delle informazioni di connessione, è un observable collection e contiene oggetti di tipo SqlConnectionInfo, queste caratteristiche fanno si che la collection ed il suo contenuto forniscano automaticamente gli eventi PropertyChanged del loro contenuto ai controlli WPF a cui sono collegati (Binding).

CurrentConnection è un istanza singola della classe SqlConnectionInfo anche essa fornisce gli eventi PropertyChanged ma ha alcune cose in piú nell’implementazione del Setter che ci servono a fornire alcuni servizi al nostro User Control.

  • Quando il valore di CurrentConnection viene modificato, verifichiamo se esiste e sganciamo dal valore corrente della property un event handler che gestisce l’evento PropertyChanged del contenuto.
  • Quando cambia il valore della property, oltre a scatenare l’evento PropertyChanged di se stessa, scatena l’evento PropertyChanged di altre due Property.

Perché tutto questo? Guardando l’event handler vediamo che questo event handler fa due cose, Setta la property IsChanged a true, quindi in qualche modo notifica all’intero Model che i dati che gestisce sono stati modificati.
Inoltre, se la property che è stata modificata è la property “Trusted” dell’oggetto, notifica una modifica alla property SqlConnectionIsInEditMode.

Riepiloghiamo:

  • SqlConnections è il contenitore dei dati gestiti dallo user control, ItemsSource=”{Binding SqlConnections}” ci dice che la collection è stata assegnata come datasource alla listbox.
  • CurrentConnection conterrà la connection correntemente selezionata quella di cui vedremo i dettagli nello user control, SelectedItem=”{Binding CurrentConnection}” ci dice che il suo valore viene aggiornato o aggiorna il selected item della listbox.

Proseguiamo l’esplorazione dello XAML, aggiungeremo codice alla classe Model mentre lavoriamo ed esploreremo ulteriori concetti di WPF.

<ScrollViewer HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="1" Grid.Row="2"  Margin="3,3,3,3"  Padding="0,0,0,0" MaxHeight="600" VerticalScrollBarVisibility="Auto"  HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
	<Grid  Style="{StaticResource PanelEditMode }" Margin="0,0,0,0">
		<Grid.RowDefinitions>
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
		</Grid.RowDefinitions>
		<Grid.ColumnDefinitions>
			<ColumnDefinition Width="4*"/>
			<ColumnDefinition Width="3*"/>
			<ColumnDefinition Width="3*"/>
			<ColumnDefinition Width="2*"/>
		</Grid.ColumnDefinitions>
	</Grid>
</ScrollViewer>

Il contenitore per i controlli del dettaglio della connessione corrente, occupa la seconda colonna della terza riga della grid principale, contiene un controllo ScrollViewer, ovvero un pannello con scrollbar automatica, che permette al proprio contenuto, in questo caso la grid di dettaglio di scorrere nel caso quel che contiene fosse troppo grande per la dimensione totale del controllo. Al suo inteno una ulteriore Grid, che ci permetterà di inserire i controlli del dettaglio.
Che cosa possiamo trovare in questo controllo che non abbiamo ancora esplorato? Fatto salvo alcune property che determinano la forma e l’aspetto dei due controlli, in questa porzione di codice appare un nuovo elemento:

Style="{StaticResource PanelEditMode }"

Che cos’è questo attributo e che cos’è il suo valore. Come potrebbe suggerire il suo nome, Style è una property che contiene informazioni relative all’aspetto di un controllo.

Dove è definito questo stile e che cosa contiene? Il valore assegnato all’attributo ci dovrebbe dare degli indizi, non si tratta di un Binding, quindi non è riferito al Model, StaticResource ci indica che si tratta di una risorsa, le risorse hanno un posto ben preciso nello UserControl dove vengono definite o dichiarate.

<UserControl.Resources>
	<ResourceDictionary>
......
		<Style x:Key="PanelEditMode" TargetType="{x:Type Grid}">
			<Setter Property="Background" Value="#ffefefef"  />
			<Style.Triggers>
				<DataTrigger Binding="{Binding IsInEditMode}" Value="True">
					<Setter Property="Background" Value="#ffcccccc" />
				</DataTrigger>
			</Style.Triggers>
		</Style>
	</ResourceDictionary>
</UserControl.Resources>

All’inizio del nostro file XAML, c’è un Tag (.Resources) che è proprio dello UserControl e ci permette di definire tutte le risorse locali oppure le risorse importate da altri assembly.
Come possiamo vedere, lo stile in questione non definisce semplicemente alcuni attributi statici per il controllo ma fa qualcosa di diverso e piuttosto importante. In questo caso infatti, lo stile definisce che il colore di Background del pannello a cui è applicato cambia se il valore della variabile  IsInEditMode contenuta nel Model di questo controllo cambia il proprio valore da False a True.

  • x:Key = Nome dello stile
  • TargetType = Tipo di controllo a cui lo stile si applica in questo caso il controllo Grid
  • Setter = Quale property viene modificata e quale valore assume per default in questo caso il Background.
  • Style.Triggers = Quando lo stile viene modificato?
  • DataTrigger = Quale dato scatena la modifica dello stile
    • Setter = Il nuovo valore della property che viene modificata

Spiegare come è fatto questo stile ci porta ad esaminare ulteriori porzioni di codice del Model del nostro User control e continua ad illustrare il concetto di Data Driven del funzionamento di WPF.

public bool IsInEditMode
{
	get
	{
		return mIsInEditMode;
	}
	set
	{
		mIsInEditMode = value;
		OnPropertyChanged(FLD_IsInEditMode);
		OnPropertyChanged(FLD_IsNotInEditMode);
		OnPropertyChanged(FLD_SqlConnectionIsInEditMode);
		OnPropertyChanged(FLD_EditImage);
	}
}

La property che abbiamo utilizzato per la definizione dello stile, come possiamo vedere non scatena un solo evento, ma scatena l’evento di modifica per altre 3 proprietà del nostro model. Qual’è il motivo per cui l’evento è stato inserito su questa property e non sulle altre? Perché tutte e tre le property sono collegate al valore di questa o il loro valore è determinato da questa property. Vediamo come.

public bool IsNotInEditMode
{
	get
	{
		return (!IsInEditMode);
	}
}

La prima property, è il complemento della precedente, perché non possiamo usare l’altra? Perché il binding WPF puó essere fatto ad una variabile, non alla sua negazione, quindi in modo molto semplice useremo queste due variabili per attivare o disattivare le opzioni di menu sul controllo.

public bool SqlConnectionIsInEditMode
{
	get
	{
		bool isTheCurrentConnectionTrusted = false;
		if (CurrentConnection != null)
		{
			isTheCurrentConnectionTrusted = CurrentConnection.Trusted;
		}
		return this.IsInEditMode && !isTheCurrentConnectionTrusted;
	}
}

La seconda Property, in questo caso la dipendenza di questa property è molteplice, infatti dipende sia dal fatto che siamo in modalità di modifica che dal fatto che la connessione corrente sia in modalità Trusted. Questa property viene utilizzata per attivare o disattivare la modifica delle textbox Username e Password nel dettaglio dei dati.

public BitmapImage EditImage
{
	get
	{
		if (IsInEditMode)
		{
			return (mEditImage);
		}
		else
		{
			return (mNotEditImage);
		}
	}
}

L’ultima property collegata restituisce l’immagine di stato che è accanto al titolo della finestra, che è diversa se siamo in visualizzazione o modifica dei dati. Vediamo quindi cosa gestiscono lo stile e le variabili che abbiamo appena visto:

wpf_uc_08

Stato di visualizzazione, l’immagine ci dice che non siamo in modifica, i controlli non sono attivi, lo sfondo è grigio chiaro.

wpf_uc_09

Stato di modifica, l’immagine mostra che siamo in modifica, i controlli sono attivi, lo sfondo è piú scuro per far risaltare meglio i controlli attivi.

Prima di addentrarci in oggetti ancor piú interessanti di quelli esaminati e continuare a esaminare i concetti di Data Driven, Binding e Model, terminiamo l’esame dello XAML di struttura con l’ultimo controllo della Grid Primaria:

<StackPanel Grid.Row="3" Grid.ColumnSpan="2" FlowDirection="LeftToRight"  Orientation="Horizontal" Margin="10,10,10,10">
</StackPanel>

L’ultima riga della grid primaria, un controllo StackPanel che contiene il necessario a visualizzare il nome del file dati che è correntemente agganciato al controllo e non aggiunge nulla a quanto già discusso. Abbiamo il suo posizionamento nella grid, e tre property che indicano come i controlli verranno inseriti al suo interno e in che modalità saranno inseriti (orizzontalmente).

Abbiamo fatto il primo step nell’implementazione di uno User Control WPF, nella quarta parte dell’articolo scenderemo in maggiore dettaglio.

Il progetto di esempio che contiene il codice come sarà alla fine della serie è disponibile nella nuova versione delle librerie di uso comune a questo indirizzo:

Per qualsiasi domanda, curiosità approfondimento, potete usare il link al modulo di contatto in cima alla pagina.