Visto che ho implementato una form di Login in Windows Forms, nel post precedente, considerato che WPF è molto più attuale, performante, e propedeutico ad usare UWP e i sistemi di sviluppo per il mobile, direi che è opportuno implementare lo stesso tipo di funzionalità anche in questa tecnologia. Pertanto ho creato un mini progetto per mostrarvi come fare a creare una Window di Login da chiamare prima di creare la finestra principale di programma in una applicazione WPF.
Dove inserisco la form di Login?
E’ una domanda che sembra banale ma in realtà non lo è.
La form di Login va inserita prima dell’apertura della finestra principale di una applicazione, prima di effettuare qualsiasi operazione di inizializzazione, connnessione a database o altro tipo di operazione collegata alla creazione del contesto applicativo. Infatti, nell’articolo dedicato alle Windows Forms, l’ho inserita nella classe Program.cs ed esattamente all’interno del metodo Main, l’entry point applicativo.
In Wpf, sfortunatamente il Main non c’è! Mi direte voi che di certo siete entrati a esplorare App.Xaml e App.xaml.cs nonchè MainWindow.xaml e MainWindow.xaml.cs nel template standard di WPF.
Se cercate bene, dopo aver compilato, vedrete che il Main c’è, ma ci viene nascosto perché Visual Studio automaticamente crea il codice C# a partire dallo xaml quando compila e dentro a tale codice nasconde il metodo Main. Se attivate la visualizzazione completa dei files da visual studio usando il tastino in alto nell’immagine qui sotto.
E poi aprite App.g.cs, troverete il metodo Main.
Ma non potete farvi modifiche, perché il file viene rigenerato dal compilatore ad ogni compilazione, ma ovviamente i programmatori che hanno creato WPF non sono così crudeli da non lasciarvi modo di effettuare operazioni prima di far partire la finestra principale dell’applicazione e vi mettono a disposizione un opportuno Entry Point ove mettere il codice di controllo ed inizializzazione all’interno della classe Application, da cui App deriva.
Vediamo quindi come modificare il comportamento standard in quello che ci serve.
Modifiche ad App.xaml
<Application x:Class="LoginFormWpf.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:LoginFormWpf" StartupUri="Windows\MainWindow.xaml"> <Application.Resources> </Application.Resources> </Application>
Apriamo l’App.xaml standard che ci è stato creato da Visual studio ed eliminiamo la StartupUri
<Application x:Class="LoginFormWpf.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:LoginFormWpf" > <Application.Resources> </Application.Resources> </Application>
Ovviamente adesso il programma non partirà. Perciò apriamo App.xaml.cs.
Modifiche ad App.xaml.cs
public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { this.ShutdownMode = System.Windows.ShutdownMode.OnExplicitShutdown; base.OnStartup(e); LoginWindow login = new LoginWindow(); bool? logged = login.ShowDialog(); if (logged.HasValue && logged.Value) { this.ShutdownMode = ShutdownMode.OnMainWindowClose; MainWindow win = new MainWindow(); this.MainWindow = win; win.UserName = "The User name is: " + login.UserName; win.Show(); } else { Shutdown(); } } }
Inseriamo in App.xaml.cs il metodo che effettua l’override del metodo OnStartup, che guarda caso viene chiamato dall’applicazione quando il Main nascosto chiama app.Run().
Spero che tutti voi siate familiari con che cosa significa effettuare l’override di un metodo, se non lo siete, la Definizione di override su MSDN potrà darvi un idea precisa.
Ad ogni modo, quel che abbiamo fatto nel nostro metodo è la seguente serie di azioni:
- Modificare il metodo in cui l’applicazione si chiude per effettuare i nostri controlli.
- Eseguire lo startup standard della classe Application, da cui App deriva.
- Instanziare una classe LoginWindow ovvero una Window WPF che ci richiede di effettuare il Login.
- Mostrarla in modalità Dialog, o finestra Modale, facendo quindi in modo che fino a che non si chiude l’esecuzione del metodo si fermi.
- Testare il valore di ritorno della finestra che sarà nullo, se fosse stata chiusa senza alcuna scelta oppure true o false in base all’esito dell’operazione di Login.
- In caso di Login andato a buon fine,
- Modifichiamo il metodo di Shutdown in modo che l’applicazione si chiuda quando si chiuderà la MainWindow.
- Generiamo la MainWindow
- Inizializziamo una property della MainWindow con una stringa che preleva il dato UserName dalla form di Login appena chiusa.
- Apriamo la MainWindow.
- In caso di Login fallito, chiudiamo l’applicazione.
Modifichiamo LoginWindow.xaml
<Window x:Class="LoginFormWpf.Windows.LoginWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:LoginFormWpf" mc:Ignorable="d" Title="MainWindow" Height="200" Width="340"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid Grid.Row="0"> <Grid.RowDefinitions> <RowDefinition Height="50*"/> <RowDefinition Height="50*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Text="User Name" Margin="10,20,10,20" HorizontalAlignment="Right" VerticalAlignment="Center"/> <TextBox Grid.Row="0" Grid.Column="1" Margin="10,20,10,20" HorizontalAlignment="Stretch" VerticalAlignment="Center" Text="{Binding UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> <TextBlock Grid.Row="1" Grid.Column="0" Text="Password" Margin="10,20,10,20" HorizontalAlignment="Right" VerticalAlignment="Center"/> <PasswordBox Name="pwdBox" Grid.Row="1" Grid.Column="1" Margin="10,20,10,20" HorizontalAlignment="Stretch" VerticalAlignment="Center" /> </Grid> <StackPanel Grid.Row="1" Margin="4" Orientation="Horizontal" FlowDirection="RightToLeft"> <Button Padding="10,4,10,4" Margin="4,2,4,2" Click="ButtonCancel_Click"> <StackPanel Orientation="Horizontal" FlowDirection="LeftToRight"> <Image Margin="0" Width="24" Height="24" Source="pack://application:,,,/Images/btn_032_108.png"/> <TextBlock Margin="4" Padding="4,2,4,2" Text="Cancel"/> </StackPanel> </Button> <Button Padding="20,4,20,4" Margin="4,2,4,2" IsEnabled="{Binding OkIsEnabled, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" Click="ButtonOk_Click"> <StackPanel Orientation="Horizontal" FlowDirection="LeftToRight"> <Image Margin="0" Width="24" Height="24" Source="pack://application:,,,/Images/btn_032_107.png"/> <TextBlock Margin="4" Padding="4,2,4,2" Text="OK"/> </StackPanel> </Button> </StackPanel> </Grid> </Window>
Lo xaml della window è piuttosto semplice, ma considerato che chi ha fatto domande al riguardo sul Forum Microsoft, è un beginner, ne spiego brevemente la forma:
- Nella Grid più esterna ho inserito la definizione di 2 righe.
- La prima contiene i controlli per inserire i dati di Login
- La seconda contiene i 2 button che comandano il controllo dei dati inseriti.
- La prima riga occupa tutto lo spazio disponibile (Height=”*”)
- La seconda riga è alta quanto il suo contenuto (Height=”Auto”)
- Nella prima Riga, abbiamo inserito una seconda Grid, che contiene 2 righe e 2 colonne dove metteremo i controlli per i dati di Login.
- La definizione delle 2 righe indica che ciascuna riga ha la stessa altezza, quindi ciascuna occupa la metà dello spazio. (Height=”50*”) attenzione che il numero non è una percentuale, è solo una dimensione relativa, quindi avrei potuto mettere 500* e ottenere lo stesso effetto.
- Le due colonne, sono definite in modo che la prima colonna si dimensioni in base al contenuto (Width=”Auto”), mentre la seconda occupa tutto lo spazio (Width=”*”) in questo modo, la colonna con le descrizioni sarà larga quanto il testo più largo, la seconda occuperà tutto lo spazio.
- Utilizzando le Attached Properties Grid.Row e Grid.Column, che vengono inizializzate nei controlli TextBlock, TextBox e PasswordBox, indichiamo in quale cella della Grid ogni controllo deve essere contenuto.
- Due parole relative alla PasswordBox, presumo possiate notare come al contrario della TextBox per lo UserName questo controllo non ha la property Text e non ha un altra property equivalente, quindi non è in Binding con una property. Il motivo per cui è stata disegnata in questo modo è per garantire maggiore sicurezza, per cui il valore della property Password è accessibile solo al Code Behind.
- Nella seconda Riga della Grid principale abbiamo inserito uno StackPanel, per inserire i Button che comandano le azioni per il controllo della password o l’uscita dall’applicazione. Lo Stack Panel a differenza della Grid ha un posizionamento dei controlli sequenziale e può essere orizzontale o verticale, in questo caso è orizzontale con l’allineamento dei controlli da Destra verso Sinistra, in modo da emulare le dialog standard di Windows.
- Per dimostrare il fatto che tutti i controlli WPF funzionano come le Matrioske, quindi possono contenere non solo testo ma altri controlli, abbiamo inserito in ciascuno dei Button un ulteriore Stack Panel, ogni Stack Panel contiene un immagine e un testo.
- Negli StackPanel, il posizionamento dei controlli viene regolato utilizzando Il Margin ed il Padding che sono ovviamente bordo esterno e bordo interno del controllo.
Modifichiamo ora LoginWindow.xaml.cs
public partial class LoginWindow : Window, INotifyPropertyChanged { public LoginWindow() { InitializeComponent(); this.DataContext = this; UserName = null; } public const string FLD_UserName = "UserName"; private string mUserName; public string UserName { get { return mUserName; } set { mUserName = value; OnPropertyChanged(FLD_UserName); OnPropertyChanged(FLD_OkIsEnabled); } } public const string FLD_OkIsEnabled = "OkIsEnabled"; public bool OkIsEnabled { get { return UserName != null && UserName.Length > 0; } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } private void ButtonCancel_Click(object sender, RoutedEventArgs e) { DialogResult = false; Close(); } private void ButtonOk_Click(object sender, RoutedEventArgs e) { if (pwdBox.Password == "12345") { DialogResult = true; } else { DialogResult = false; } Close(); } }
Anche il codice della classe è molto semplice perciò ne descriviamo semplicemente le funzionalità.
- Nel costruttore, assegnamo al DataContext la finestra stessa, in modo da poter effettuare il Binding con le property dei controlli.
- La property UserName è collegata in binding alla TextBox.
- L’evento PropertyChanged permette di comunicare alla Window le variazioni effettuate alle property definite.
- La property OkIsEnabled, è una property Boolean che permette di pilotare l’attivazione del tasto OK solo in caso lo UserName contenga dei dati, serve a dimostrare come in WPF non sono gli Eventi a controllare il comportamento dell’interfaccia, ma i dati. Faccio notare come, per far si che il valore di OkIsEnabled sia aggiornato, nella property UserName viene sollevato anche il suo OnPropertyChanged.
- Button cancel, semplicemente indica che è richiesta l’uscita dall’applicazione.
- Button OK in questo caso fa un controllo banale, se la password ha il valore da me deciso ritorna Vero, in modo che l’applicazione si apra, altrimenti ritorna False. Questo metodo sarebe il luogo corretto ove inserire ad esempio una chiamata ad un LoginManager che si connette a database verificando UserName e Password prima di indicare True o False.
La finestra di Login, ho scritto la password non lo user name quindi OK non è attivo.
Ora ho scritto lo User Name ed OK è attivo.
Il risultato nella Main Window.
Se rimpicciolisco la Window, come potete vedere, il contenuto reagisce di conseguenza.
Modifichiamo MainWindow.xaml
Per mostrare come “passare” i dati da una window ad un altra in WPF, abbiamo creato qualcosa anche nella MainWindow, vediamo cosa.
<Window x:Class="LoginFormWpf.Windows.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:LoginFormWpf.Windows" mc:Ignorable="d" Title="MainWindow" Height="240" Width="960"> <Grid> <TextBlock Text="{Binding UserName, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" FontFamily="Lucida Typewriter" FontSize="60" FontWeight="Bold" Foreground="Purple" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> </Window>
Abbiamo fatto una semplice visualizzazione, collegata alla property UserName, ed abbiamo semplicemente mostrato come cambiare dimensione e tipo di Font oltre al suo colore.
Modifichiamo MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged { public MainWindow() { InitializeComponent(); this.DataContext = this; } public const string FLD_UserName = "UserName"; private string mUserName; public string UserName { get { return mUserName; } set { mUserName = value; OnPropertyChanged(FLD_UserName); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } }
Anche il codice è molto semplice, infatti abbiamo solo collegato la classe al DataContext, abbiamo implementato il Property Changed e in questo modo, quando UserName viene inizializzata, il suo valore è visibile sulla Window.
Si tratta di un progetto davvero molto semplice dedicato a chi sta iniziando, ma spero possa aver stuzzicato la curiosità di chi vuole iniziare ad usare XAML e WPF.
Il codice esempio usato in questo articolo è disponibile al link seguente
Companion code, Creare una form di Login in WPF
Per qualsiasi domanda, potete usare il link alla form di contatto o postare una domanda sul forum Microsoft a cui partecipo spesso e dove vi sono altri in grado di rispondere ai vostri quesiti.