Press "Enter" to skip to content

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

In questo ultimo post della serie esploreremo un progetto di test che utilizza il nostro User Control in un caso d’uso reale.

Abbiamo realizzato lo User Control per poter inserire, modificare, salvare una collezione di dati di connessione a SQL Server, quindi creiamo una applicazione in grado di creare dei file di dati di connessione sia in modo visibile che in modo crittografato, che è in grado di creare file nuovi oppure di modificare file esistenti o importare dati aggiungendoli ad un file esistente.

wpf_test_01

L’interfaccia del progetto di test prevede una sola Window, dove oltre allo User Control abbiamo due serie di controlli, in cima quelli per selezionare il file da generare o da caricare, e in fondo i pulsanti per salvare o uscire dall’applicazione.

Esaminiamo lo XAML:

<Window x:Class="Dnw.TestTheConnectionInfoControl.Windows.MainWindow"         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:p="clr-namespace:Dnw.TestTheConnectionInfoControl.Properties"
	xmlns:sctl="clr-namespace:Dnw.UI.SqlServer.Controls;assembly=Dnw.UI.SqlServer.v4.0"         Title="Generate and manage Sql Connections" Height="678.068"
	Width="971.71" ResizeMode="CanResizeWithGrip"   Closing="Window_Closing" Loaded="Window_Loaded">
<Grid>
<Grid.RowDefinitions>
	<RowDefinition Height="Auto"/>
	<RowDefinition Height="Auto"/>
	<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0"	FlowDirection="LeftToRight"   Orientation="Horizontal" Margin="10,10,10,10">
	<TextBlock Text="{x:Static p:Resources.txtMWConnectionsFileName}"  Margin="10,2,10,2" HorizontalAlignment="Right"  VerticalAlignment="Bottom"/>
	<TextBox Name="txtFileName"  Text="{Binding FileName, Mode=TwoWay}"  Width="600" Margin="10,2,10,2" />
	<Button Name="btnGetFile" Margin="10,2,10,2"  Click="btnGetFile_Click">
		<TextBlock Text="..." Margin="10,0,10,0"  HorizontalAlignment="Center" VerticalAlignment="Center"/>
	</Button>
</StackPanel>
<ScrollViewer Grid.Row="1" IsEnabled="{Binding HasData}">
	<sctl:SqlConnectionInfoControl Name="ctlConnections"/>
</ScrollViewer>

<StackPanel Grid.Row="2"  FlowDirection="RightToLeft"   Orientation="Horizontal" Margin="10,10,10,10">
	<Button Name="btnExit" Margin="0,2,5,2"   Click="btnClick" Width="120" Height="36" IsCancel="True">
		<StackPanel FlowDirection="LeftToRight"  Orientation="Horizontal" Margin="0,0,0,0">
			<Image Width="24" Height="24"  Source="/TestTheConnectionInfoControl;component/Images/btn_032_157.png"
			Margin="5,2,5,2"/>
			<Label Content="{x:Static p:Resources.txtMWExit}"  VerticalAlignment="Center" HorizontalAlignment="Right" Margin="5,0,5,0" />
		</StackPanel>
	</Button>
	<Button Name="btnSave" Margin="0,2,5,2"   Click="btnClick" Width="120" Height="36"  	IsEnabled="{Binding Modified}" IsDefault="True">
		<StackPanel FlowDirection="LeftToRight"  Orientation="Horizontal" Margin="0,0,0,0">
			<Image Width="24" Height="24"  Source="/TestTheConnectionInfoControl;component/Images/btn_032_367.png"  Margin="5,2,5,2"/>
			<Label Content="{x:Static p:Resources.txtMWSave}"  VerticalAlignment="Center" HorizontalAlignment="Right" Margin="5,0,5,0" />
		</StackPanel>
	</Button>

</StackPanel>
</Grid>

Possiamo vedere che è molto compatto rispetto a quello dello User Control, vediamone le cose importanti in dettaglio.

xmlns:sctl="clr-namespace:Dnw.UI.SqlServer.Controls;assembly=Dnw.UI.SqlServer.v4.0"

Questa definizione di namespace crea un alias sctl: per i componenti contenuti nella nostra libreria, per ora solo lo user control, ma in seguito potremo creare ulteriori componenti.

Closing="Window_Closing" Loaded="Window_Loaded">

In questa riga, vediamo la definizione dell’event handler dell’evento Loaded e di quello dell’ evento Closing della finestra che osserveremo poi nel Code Behind.

Il resto della window contiene oggetti simili a quelli che abbiamo giá commentato all’intenro dei post precedenti perció non andremo nel dettaglio.

  • Abbiamo una grid primaria con tre righe e una colonna.
  • Nella prima riga uno stack panel con 3 controlli, la label, la textbox ed il button che permettono di creare un nome di file o selezionare un nome di file esistente per i dati di connessione.
  • Nella seconda riga uno ScrollViewer con all’interno il nostro User control, lo abbiamo inserito in modo che se la finestra fosse troppo piccola per lo user control apparirebbero le scrollbar.
  • Infine la terza riga, che contiene uno stack panel con i due bottoni salva ed esci.

Andiamo invece a vedere cosa abbiamo inserito nel code behind della nostra Window:

public partial class MainWindow : Window, INotifyPropertyChanged
{
	public MainWindow()
	{
		InitializeComponent();
		this.DataContext = this;
		this.Icon = BitmapFrame.Create(new Uri("pack://application:,,,/Images/dnwIco.ico", UriKind.RelativeOrAbsolute));
		mHasData = false;

	}

Partiamo dall’inizio, ed esploriamo un altro modo di implementare il ViewModel in WPF, in questo caso, la finestra è specializzata, ha solo un determinato compito ed è semplice, quindi invece di implementare una classe Model per fornirgli tutti i servizi e i dati funzionali a XAML, facciamo si che la Window sia il model di se stessa. Per farlo implementiamo l’interfaccia INotifyPropertyChanged e corrispondente evento e nel costruttore della classe, dopo l’InitializeComponent assegnamo this al datacontext della Window in questo modo potremo effettuare il Binding ai controlli della finestra sulle property di se stessa, vediamo quali abbiamo implementato.

public string FileName
{
	get
	{
		return ctlConnections.FileName;
	}
	set
	{
		ctlConnections.FileName = value;
		OnPropertyChanged("FileName");
	}
}

public bool HasData
{
	get
	{
		return mHasData;
	}
	set
	{
		mHasData = value;
		OnPropertyChanged(FLD_HasData);
	}
}

Le property dell'”automodel” sono due, FileName che espone la property del controllo in modo che possiamo collegarla al Text della Textbox dedicata ad accoglierlo. HasData è un boolean che abbiamo collegato alla proprietá IsEnabled dello ScrollViewer, in modo da permettere al controllo di essere attivo solo dopo che abbiamo scelto un nome di file per leggere e salvare i dati delle connessioni.

private void Window_Loaded(object sender, RoutedEventArgs e)
{
	this.ctlConnections.GetImportData = GetImportData;
	this.ctlConnections.GetExportData = GetExportData;
}

L’evento Loaded della Window, che inizializza i delegate delle funzioni di acquisizione dei dati per l’import e l’export.

private ImportExportData GetExportData()
{
	ImportExportData ieData = null;
	SaveFileDialog sfd = new SaveFileDialog();
	sfd.Title = Properties.Resources.txtMWExportFileNameTitle;
	sfd.DefaultExt = EXT_Dnwx;
	sfd.Filter = TXT_Filters;
	sfd.FilterIndex = 2;
	sfd.CheckFileExists = false;
	sfd.CheckPathExists = true;
	bool? ret = sfd.ShowDialog();
	if (ret.HasValue && ret.Value)
	{
		bool isEncrypted = Path.GetExtension(sfd.FileName).ToLower() == EXT_Dnwx;
		ieData = new ImportExportData(sfd.FileName, isEncrypted);
	}

	return (ieData);
}

private ImportExportData GetImportData()
{
	ImportExportData ieData = null;
	OpenFileDialog ofd = new OpenFileDialog();
	ofd.Title = Properties.Resources.txtMWImportFileNameTitle;
	ofd.DefaultExt = EXT_Dnwx;
	ofd.Multiselect = false;
	ofd.Filter = TXT_Filters;
	ofd.FilterIndex = 2;
	ofd.CheckFileExists = false;
	ofd.CheckPathExists = true;
	bool? ret = ofd.ShowDialog();
	if (ret.HasValue && ret.Value)
	{
		bool isEncrypted = Path.GetExtension(ofd.FileName).ToLower() == EXT_Dnwx;
		ieData = new ImportExportData(ofd.FileName, isEncrypted);
	}

	return (ieData);
}

I due metodi per la richiesta dei nomi dei file di import e di export, che inoltre settano il flag di crittografia quando viene utilizzata l’estensione apposita, questa è una regola di business arbitraria imposta da noi. Potrebbe essere che in ambito diverso le estensioni cambino o cambino le modalitá per cui richiedere dati in chiaro o dati crittografati, questo il motivo per lasciare fuori dallo User Control questo tipo di funzionalitá.

private void btnGetFile_Click(object sender, RoutedEventArgs e)
{
	try
	{

		OpenFileDialog ofd = new OpenFileDialog();
		ofd.Title = Properties.Resources.txtMWConnectionFileNameTitle;
		ofd.FileName = "Connections.dnwx";
		ofd.DefaultExt = EXT_Dnwx;
		ofd.Multiselect = false;
		ofd.Filter = TXT_Filters;
		ofd.FilterIndex = 2;
		ofd.CheckFileExists = false;
		ofd.CheckPathExists = true;
		bool? ret = ofd.ShowDialog();
		if (ret.HasValue && ret.Value)
		{
			this.FileName = ofd.FileName;
			HasData = true;
			if (File.Exists(ofd.FileName))
			{
				bool isEncrypted = Path.GetExtension(ofd.FileName).ToLower() == EXT_Dnwx;
				try
				{
					ctlConnections.Load(isEncrypted);
				}
				catch (Exception ex)
				{
					EventLogger.SendMsg(ex);
					MessageBox.Show(ex.Message);
					ctlConnections.Clear();
				}
			}
			else
			{
				ctlConnections.Clear();
			}
		}
	}
	catch (Exception ex)
	{
		EventLogger.SendMsg(ex);
		MessageBox.Show(ex.Message);
	}
}

L’event handler del bottone di selezione del nome del file, permette la selezione di un file (che puó anche non esistere quindi ci permetterá di creare file nuovi). In base all’estensione del file “.dnwx” o qualsiasi altra estensione il sistema stabilisce se i dati sono crittografati o meno e carica il controllo con il contenuto del file oppure effettua una Clear se il file non esiste in modo di permettere di crearne nuovi.

private void btnClick(object sender, RoutedEventArgs e)
{
	try
	{

		Button btn = sender as Button;
		if (btn != null)
		{
			switch (btn.Name)
			{
				case "btnExit":
					this.Close();
					break;
				case "btnSave":
					Save();
					break;
			}
		}
	}
	catch (Exception ex)
	{
		EventLogger.SendMsg(ex);
		MessageBox.Show(ex.Message);
	}
}

L’Event handler del click dei due bottoni di comando della Window.

private void Save()
{
	bool isEncrypted = Path.GetExtension(ctlConnections.FileName).ToLower() == EXT_Dnwx;
	ctlConnections.Save(isEncrypted);
}

Il metodo di salvataggio dei dati, che decide se crittografare i dati in base all’estensione del file.

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{

	if (ctlConnections.IsChanged)
	{
		if (MessageBox.Show(Properties.Resources.warMWChangesNotSaved,
			Properties.Resources.warMWTitle, MessageBoxButton.YesNo,
			MessageBoxImage.Warning) != MessageBoxResult.Yes)
		{
			e.Cancel = true;
		}
	}
}

L’event handler dell’evento closing della Window, che controlla se vi sono modifiche ed eventualmente annulla la chiusura della form.

Conclusioni

Questa lunga serie di post ci ha visto costruire per la prima volta uno User Control completto e completamente funzionante per una applicazione nel mondo reale, abbiamo provato a spiegare i meccanismi di base che stanno dietro a WPF alla gestione Data Driven e ai concetti di View e ViewModel.  Se la creazione dello User Control si è rivelata complessa e con molte cose da imparare, la Window in cui lo abbiamo usato invece si scosta poco da una normale Form, se non per il fatto che fa da ViewModel a se stessa. Speriamo di aver chiarito alcuni concetti di base a chi si sta avvicinando a WPF e avervi aperto a nuove idee e nuove cose da provare.

Buona Pesca a tutti.

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.

Aggiungo i riferimenti alle librerie di supporto che saranno utilizzate anche negli articoli successivi.