Creare un Notepad con WPF in Visual Basic 2008
a cura di Alessandro Del Sole (requisiti: conoscenze di base su Windows Presentation Foundation)Introduzione
Windows Presentation Foundation non è solo una tecnologia in grado di offrire supporto per elevati contenuti multimediali e interfacce utente strabilianti. Essa fornisce anche molti strumenti per la semplificazione di operazioni ricorrenti, come, ad esempio, le azioni che vengono compiute su blocchi di testo.
In altre parole, WPF implementa funzionalità standard (come i classici comandi Taglia/Copia/Incolla) che possono essere richiamate dallo sviluppatore senza scrivere alcuna riga di codice e che sono adattabili a tutta una serie di controlli utente.In questo articolo ci proponiamo di realizzare un semplice Notepad con Visual Basic 2008, basando il nostro lavoro su quanto offerto da WPF.
Sebbene l'applicazione finale risulterà apparentemente semplice per la maggior parte dei lettori, in realtà raggiungeremo diversi obiettivi.
In primo luogo approfondiremo i concetti relativi al cosiddetto arrangement (la disposizione a video) di diversi controlli e contenitori in una finestra.
In secondo luogo, impareremo ad utilizzare i comandi predefiniti di WPF all'interno dei nostri menu.
Quindi, utilizzeremo alcuni code snippet per verificare, mediante l'ausilio del Code Access Security di .NET Framework, che l'utente corrente abbia i privilegi per accedere ai file.
Non di meno, impareremo ad includere risorse nelle applicazioni e ad associarle al nostro codice XAML.Da ultimo, il Notepad realizzato ci servirà in futuri articoli come base per introdurre Microsoft Expression Blend, uno strumento che Microsoft ha realizzato per lo sviluppo di interfacce grafiche in WPF.
Prima di iniziare, ecco uno screen-shot del risultato dell'applicazione finale, di modo che, se avete dubbi durante la lettura dell'articolo e la scrittura del codice, possiate avere un riferimento visivo di cosa andremo a realizzare:
![]()
E' possibile scaricare dall'area download il progetto completo in Visual Basic 2008, che è bene avere a disposizione prima di proseguire nella lettura. Per realizzare l'applicazione e tutte le varie procedure è sufficiente utilizzare anche Visual Basic 2008 Express.
Avviate quindi Visual Basic 2008, create un nuovo progetto di tipo WPF Application e ad assegnate al progetto il nome WPFNotePad. Cominciamo dalla realizzazione dell'interfaccia grafica.
Impostazioni dell'oggetto Window
Come ormai sapete, le finestre in WPF sono costituite dagli oggetti Window e ciascun progetto ne prevede una di default. Nell'editor di codice XAML, modificate il tag di apertura dell'elemento Window così come segue, in modo da impostare le dimensioni, il titolo e il colore di sfondo della finestra:<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Window1" x:Name="Window" Title="WPF Notepad" Width="640" Height="480" Background="Black">Solitamente, quando si implementano i menu, viene utilizzato un contenitore DockPanel. Tuttavia, questa non è l'unica scelta disponibile. Facendo un buon utilizzo delle proprietà di allineamento dei vari oggetti e dei relativi margini, si possono ottenere risultati ottimi anche utilizzando altri contenitori (come Grid e StackPanel). A tal proposito, il designer WPF di Visual Basic 2008 è un valido supporto. In questo esempio, pertanto, la Grid sarà il contenitore principale a cui verranno aggiunti i rimanenti controlli. Se invece volete vedere come utilizzare il DockPanel, potete consultare questo post sul mio blog o leggere il mio libro per una trattazione più approfondita.
Vogliamo ora inserire la dicitura "VB T&T Notepad" e rifletterla in basso, in modo da creare un effetto interessante. Il testo risiederà in un controllo TextBlock, mentre l'effetto riflesso sarà implementato, tramite un pennello VisualBrush, all'interno di un controllo Border. Ciò si può realizzare inserendo i due controlli all'interno di un contenitore StackPanel che, come ricorderete, permette di affiancare controlli orizzontalmente o verticalmente. Partiamo con la dichiarazione dello StackPanel e del TextBlock (il codice va inserito all'interno dell'elemento Grid):
<StackPanel VerticalAlignment="Top" Height="60"> <TextBlock Width="196" Height="33" Text="VB T&T Notepad" TextWrapping="Wrap" HorizontalAlignment="Left" FontWeight="Bold" FontSize="24" x:Name="myVisual"> <TextBlock.Foreground> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="#FF353535" Offset="0"/> <GradientStop Color="#FFFFFFFF" Offset="1"/> </LinearGradientBrush> </TextBlock.Foreground> </TextBlock>Probabilmente vi starete chiedendo come mai i valori di Width ed Height non siano arrotondati (es. 30 o 35, invece che 33 pixel). Il motivo è semplice: per predisporre l'interfaccia, ho voluto utilizzare il designer di Visual Basic piuttosto che scrivere manualmente il codice XAML, trascinando i controlli dalla toolbox alla Window. Questo per dimostrare i notevoli progressi fatti nel designer di WPF in Visual Basic 2008 rispetto all'edizione 2005 e che la scrittura manuale dello XAML non è l'unica alternativa disponibile, sebbene alcuni ritocchi al codice siano stati fatti proprio manualmente, per renderlo più preciso. Notate come la & (ampersand, e commerciale) venga inserita mediante la notazione tipica di Html. Lo sfondo del testo è costituito da un gradiente lineare, applicato dal pennello LinearGradientBrush e dalle sue proprietà GradientStop. Al TextBlock è stato inoltre assegnato un identificatore, myVisual, che ci servirà per creare l'effetto di oggetto riflesso.
L'effetto riflesso si può ottenere utilizzando, all'interno del Border, un pennello chiamato VisualBrush. Quest'ultimo, a differenza di altri Brush che riempiono oggetti con colori o gradienti, è in grado di riempire un oggetto con il contenuto di un altro elemento dell'interfaccia grafica. Ho parlato del VisualBrush in questo post nel mio blog, al quale vi rimando per approfondimenti. Il codice seguente, dunque, disegna il Border e lo riempie con un VisualBrush:
<Border Width="196" Height="36" HorizontalAlignment="Left"> <Border.Background> <VisualBrush Visual="{Binding ElementName=myVisual}"> <VisualBrush.Transform> <ScaleTransform ScaleX="1" ScaleY="-1" CenterX="98" CenterY="16" /> </VisualBrush.Transform> </VisualBrush> </Border.Background> <Border.OpacityMask> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <LinearGradientBrush.GradientStops> <GradientStop Offset="0" Color="Black"/> <GradientStop Offset=".7" Color="Transparent"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Border.OpacityMask> </Border> </StackPanel>L'utilizzo del VisualBrush è simile a quello degli altri Brush. L'elemento da usare come riempimento è referenziato, tramite data-binding, dalla proprietà Visual del pennello. La trasformazione ScaleTransform è quella che effettivamente dà l'idea dell'oggetto riflesso, in quanto consente di ridimensionare l'elemento a cui è applicata specificando le varie coordinate sullo schermo. Affinché poi si abbia l'idea che il testo riflesso vada a scomparire gradatamente, si applica una OpacityMask per il tramite di un gradiente. Quando applicato a una OpacityMask, il gradiente può supportare solo due colori, Black e Transparent. Dal punto in cui parte il colore Transparent, l'oggetto riflesso inizia a "scomparire".
Ponete sempre particolare attenzione alle proprietà VerticalAlignment e HorizontalAlignment di ogni controllo, perché diventano fondamentali per avere un layout funzionale e adattabile al ridimensionamento della finestra.
Utilizzo di risorse
Prima di procedere all'implementazione dei menu, è bene ricordare che anche in WPF è possibile assegnare in modo molto semplice icone alle voci di menu. Per brevità, e per capire meglio il concetto alla base, utilizzeremo due sole icone, che assegneremo ai comandi di menu per la creazione e l'apertura di un file di testo. Aprite la finestra My Project, relativa alle proprietà del progetto, quindi selezionate la scheda Resources. Aggiungete ora, tramite il pulsante Add Resource, le due immagini desiderate, in modo che tutto appaia come nella figura seguente:![]()
Potete trovare due immagini nel progetto sorgente a corredo. Dovete poi ricordare di attivare la finestra delle proprietà (F4) per ciascuna immagine ed impostare la sua proprietà Build Action su Resource.
Implementazione del menu File
Il menu File della nostra applicazione implementerà funzionalità classiche per l'apertura, la creazione e il salvataggio di un file di testo. Tornate all'editor di codice XAML e, dopo il tag di chiusura dell'elemento StackPanel precedente, aggiungete il seguente codice:<Menu Margin="0,60,0,0" VerticalAlignment="Top" Height="20"> <MenuItem Header="File"> <MenuItem Header="Nuovo" Name="newMenuItem" Click="onNewMenuItem"> <MenuItem.Icon> <Image Height="16" Width="16" Source="Resources/NewDocumentHS.Png"/> </MenuItem.Icon> </MenuItem> <MenuItem Header="Apri" Name="openMenuItem" Click="onOpenMenuItem"> <MenuItem.Icon> <Image Height="16" Width="16" Source="Resources/OpenHS.Png"/> </MenuItem.Icon> </MenuItem> <MenuItem Header="Salva" Name="saveMenuItem" Click="onSaveMenuItem"/> <Separator/> <MenuItem Header="Esci" Name="quitMenuItem" Click="onQuitMenuItem"/> </MenuItem>Per ogni voce di menu, la proprietà Header contiene il testo da visualizzare, la proprietà Click specifica il gestore da richiamare ogni qualvolta l'utente faccia clic sulla voce di menu e la proprietà Icon consente di specificare l'icona per la voce. Notate come questo avvenga mediante l'utilizzo di un controllo Image, inserito gerarchicamente nell'elemento MenuItem.Icon. Specificando le proprietà Width e Height per l'oggetto Image, assicurandoci che entrambe siano corrispondenti all'altezza e alla larghezza dell'immagine, si eviteranno effetti di "sgranatura". Il percorso di ciascuna immagine è costituito dalla cartella Resources e viene referenziato, in XAML, sotto forma di Uri. Le risorse non verranno copiate nella cartella di pubblicazione dell'assembly ma incorporate nei metadati dello stesso. I separatori tra le voci vengono aggiunti tramite semplici elementi Separator. Fatto questo, iniziamo con la parte interessante: il menu Modifica e i comandi predefiniti di WPF.
Implementazione del menu Modifica: i comandi predefiniti di Windows Presentation Foundation
Come certamente sapete, tutte le applicazioni in circolazione includono comandi di menu comuni, indipendentemente dal tipo di applicazione. Un ambiente di sviluppo, un'applicazione della suite Microsoft Office, browser web e molti altri tipi di applicazioni, ad esempio, hanno un menu File e un menu Modifica al cui interno vengono resi disponibili comandi che svolgono lo stesso tipo di funzione per tutte le applicazioni, indipendentemente dal tipo. Per rendere più omogeneo lo sviluppo, indipendentemente dall'applicazione che si sta realizzando, WPF mette a disposizione un set di comandi standard, che lo sviluppatore può riutilizzare senza dover riscrivere codice che gestisca la logica dei vari comandi.In WPF, per esempio, i controlli TextBox e RichTextBox utilizzano la stessa logica per gestire le azioni di Taglia/Copia/Incolla del testo. Al fine di evitare allo sviluppatore di riscrivere codice per queste azioni a seconda del controllo utilizzato, WPF prevede comandi standard che consentono la stessa azione indipendentemente dal controllo. Ciò significa che deve essere per primo il controllo a prevedere la gestione di talune azioni. Poiché nel nostro Notepad utilizzeremo una TextBox, possiamo tranquillamente utilizzare i comandi standard di WPF per implementare interamente il menu Modifica preoccupandoci esclusivamente della parte grafica, senza curarci del codice operativo. Il codice XAML che segue è la dimostrazione di quanto appena esposto:
<MenuItem Header="Modifica"> <MenuItem Command="ApplicationCommands.Undo" Header="Annulla"/> <MenuItem Header="Ripristina" Command="ApplicationCommands.Redo"/> <Separator/> <MenuItem Header="Taglia" Command="ApplicationCommands.Cut"/> <MenuItem Header="Copia" Command="ApplicationCommands.Copy"/> <MenuItem Header="Incolla" Command="ApplicationCommands.Paste"/> <Separator/> <MenuItem Header="Trova" Command="NavigationCommands.Search"/> </MenuItem> </Menu>La proprietà Command di ciascun MenuItem permette di specificare l'azione predefinita da associare alla voce. Le azioni sono contenute nella classe ApplicationCommands. Per esempio, ApplicationsCommand.Undo stabilisce che quando l'utente fa clic su quella voce deve essere annullata l'ultima operazione compiuta nella TextBox. WPF è in grado di determinare automaticamente a quale controllo l'azione vada associata. Gli altri comandi inseriti si spiegano da soli. Addirittura è possibile implementare la funzionalità di ricerca di testo. In questo modo, non avremo la necessità di scrivere codice Visual Basic per ciascun comando, penserà a tutto il .NET Framework. Per avere un elenco completo dei comandi disponibili, potete visitare questa pagina della documentazione MSDN relativa alla classe ApplicationCommands.
Implementazione della TextBox
L'ultimo passaggio relativo all'interfaccia grafica è l'aggiunta di una TextBox, alla quale vengono assegnate alcune proprietà necessarie per migliorare l'input da parte dell'utente:<TextBox Name="myTextBox" Margin="0,80,0,0" Text="" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" AcceptsReturn="True" AcceptsTab="True" />AcceptsReturn e AcceptsTab impostate su True permettono di accettare la pressione dei tasti Invio e Tab all'interno del controllo. Fatto questo, passiamo alla parte operativa. Aprite il file di code-behind, nel quale ci accingiamo ad inserire il codice che svolgerà le operazioni relative al solo menu File.
Il codice Visual Basic
E' il momento di scrivere codice Visual Basic. Dopo aver aggiunto una direttiva Imports Microsoft.Win32 (per l'utilizzo delle finestre di dialogo), iniziamo con lo scrivere quanto segue. Per comodità ho aggiunto commenti nel codice stesso:'campi utilizzati per memorizzare lo stato del documento Private isSaved As Boolean Private isTextChanged As Boolean 'quando il testo viene modificato, aggiorna lo stato del documento Private Sub myTextBox_TextChanged(ByVal sender As System.Object, _ ByVal e As System.Windows.Controls.TextChangedEventArgs) _ Handles myTextBox.TextChanged isTextChanged = True isSaved = False End Sub 'chiude l'applicazione Private Sub onQuitMenuItem(ByVal sender As Object, ByVal e As RoutedEventArgs) Application.Current.Shutdown() End SubIl codice di cui sopra è piuttosto semplice e non ha bisogno di ulteriori commenti. Il seguente gestore, consente di aprire un file di testo:
'Apre un file Private Sub onOpenMenuItem(ByVal sender As Object, ByVal e As RoutedEventArgs) 'OpenFileDialog non è la stessa classe di Windows Forms, ma appartiene al 'namespace Microsoft.Win32 ed è un wrapper della omonima finestra di Windows Dim ofd As New OpenFileDialog With ofd .Filter = "File di testo|*.txt|Tutti i file|*.*" .Title = "Seleziona un documento" 'il risultato della finestra può essere vero, falso o nullo 'per questo motivo restituisce un tipo Nullable Dim result As Nullable(Of Boolean) = .ShowDialog Select Case result 'Se l'utente clicca OK... Case Is = True '..e se la stringa del nome del file \u232 ? valida.. If String.IsNullOrEmpty(.FileName) = False Then '..verifica che l'utente abbia i permessi necessari per accedere al file 'sfruttando tecniche sul Code Access Security di .NET Try Dim perm As New FileIOPermission(FileIOPermissionAccess.Read, .FileName) perm.Demand() Catch ex As System.Security.SecurityException MessageBox.Show( _ "Non si dispone dei privilegi necessari per accedere al file specificato", _ "WPF Notepad", MessageBoxButton.OK, MessageBoxImage.Warning) Exit Sub End Try 'Quindi prova ad aprire il file Try 'Legge il file myTextBox.Text = My.Computer.FileSystem.ReadAllText(.FileName) 'e imposta lo stato del documento isTextChanged = False isSaved = True 'eccezioni specifiche. Il messaggio di ciascuna ne spiega lo scopo Catch ex As PathTooLongException MessageBox.Show("Il nome del percorso \u232 ? troppo lungo.", "WPF Notepad", _ MessageBoxButton.OK, MessageBoxImage.Error) Exit Sub Catch ex As FileNotFoundException MessageBox.Show("Il file non \u232 ? stato trovato.", "WPF Notepad", _ MessageBoxButton.OK, MessageBoxImage.Error) Catch ex As IOException MessageBox.Show("Errore di I/O.", "WPF Notepad", MessageBoxButton.OK, _ MessageBoxImage.Error) Exit Sub Catch ex As FileFormatException MessageBox.Show("Il formato del file non corrisponde a quello atteso", "WPF Notepad", _ MessageBoxButton.OK, MessageBoxImage.Error) Catch ex As Exception MessageBox.Show(ex.ToString, "WPF Notepad", MessageBoxButton.OK, MessageBoxImage.Error) End Try Else Exit Sub End If Case Else Exit Sub End Select End With End SubIl codice è abbastanza commentato, tuttavia ritengo opportuno focalizzare l'attenzione su alcuni aspetti. Innanzitutto, WPF non mette a disposizione alcuna finestra di dialogo, al contrario di Windows Forms. Due, quindi, sono le alternative possibili: ricorrere all'interoperabilità con Windows Forms, oppure utilizzare le finestre di dialogo di Windows, per le quali il namespace Microsoft.Win32 fornisce delle classi corrispondenti che fungono da wrapper di questi Common Controls. Per questo motivo abbiamo utilizzato la classe OpenFileDialog del namespace in questione. In secondo luogo, queste classi restituiscono un tipo generico Nullable (nella fattispecie Nullable di valori booleani). Ciò significa che il risultato restituito può essere True, False o Nothing.
Un altro aspetto molto importante è quello legato ai permessi. Spesso accediamo al sistema come amministratori ed avviamo l'IDE con elevati privilegi, dimenticando che i nostri potenziali utenti con molta probabilità accederanno al sistema come utenti standard e non avranno accesso a tutte le risorse di sistema. A seconda della provenienza dell'assembly (e del set di permessi ad esso assegnato, c.d. Zone) questo può avere accesso ristretto solo a limitate risorse di sistema, a volte l'accesso non è neanche consentito per leggere dei file. Con un'istanza della classe FileIOPermission, appartenente al namespace System.Security.Permissions, è possibile verificare che un file sia accessibile in lettura. Il metodo Demand della classe solleva una SecurityException qualora non sussistano i privilegi per l'accesso.
Notate poi come sia possibile leggere il file di testo con una sola riga di codice (grazie al namespace My di Visual Basic) e l'intercettazione di eccezioni specifiche.
I due seguenti gestori sono relativi alla creazione di un nuovo documento e al salvataggio del documento corrente, tenuto conto che bisognerà implementare un metodo SaveFile (che faremo a breve):
Private Sub onNewMenuItem(ByVal sender As Object, ByVal e As RoutedEventArgs) 'Se il documento è già stato salvato, ne crea uno nuovo If isSaved = True Then myTextBox.Text = String.Empty Else 'se il testo è stato modificato, chiede conferma di salvataggio If isTextChanged Then Dim result As MessageBoxResult = _ MessageBox.Show("Salvare le modifiche?", "WPF Notepad", _ MessageBoxButton.YesNoCancel, MessageBoxImage.Question) Select Case result Case Is = MessageBoxResult.Cancel Exit Sub Case Is = MessageBoxResult.No myTextBox.Text = String.Empty Case Is = MessageBoxResult.Yes If saveFile() = True Then myTextBox.Text = String.Empty Else Exit Sub End If End Select Else 'altrimenti ne crea uno nuovo myTextBox.Text = String.Empty End If End If End Sub Private Sub onSaveMenuItem(ByVal sender As Object, ByVal e As RoutedEventArgs) If saveFile() = True Then isSaved = True End SubAnche in questo caso i commenti vi saranno di aiuto, ma mi preme evidenziare un altro aspetto tipico di WPF. Il primo metodo, prima di creare un nuovo documento, chiede conferma all'utente mediante la classe MessageBox. Si tratta di una classe omonima a quella di Windows Forms, ma non è affatto la stessa. Infatti, come potete osservare, gli argomenti che essa riceve e il tipo che restituisce sono implementati da classi ed enumerazioni differenti, esclusive del namespace System.Windows. Non è quindi superfluo precisare che, nel caso vi troviate in situazioni di interoperabilità tra le due tecnologie, è bene specificare il nome completo della classe, comprensivo del namespace (es. System.Windows.MessageBox) per evitare ambiguità.
L'ultimo metodo da scrivere si chiama SaveFile e consente di salvare il documento di testo corrente:
Private Function saveFile() As Boolean 'anche in questo caso la SaveFileDialog \u232 ? un wrapper della finestra di Win32 Dim sfd As New SaveFileDialog With sfd .Title = "Specifica un documento" .Filter = "Documenti di testo|*.txt|Tutti i file|*.*" .OverwritePrompt = True Dim result As Nullable(Of Boolean) = .ShowDialog Select Case result Case Is = True 'Tecnica sul Code Access Security per verificare che l'utente abbia i 'permessi necessari per accedere al file Try Dim perm As New FileIOPermission(FileIOPermissionAccess.Write, .FileName) perm.Demand() Catch ex As System.Security.SecurityException MessageBox.Show( _ "Non si dispone dei privilegi necessari per scrivere il file specificato", _ "WPF Notepad", MessageBoxButton.OK, MessageBoxImage.Warning) Exit Function End Try Try My.Computer.FileSystem.WriteAllText(.FileName, myTextBox.Text, False) Return True Catch ex As PathTooLongException MessageBox.Show("Il nome del percorso \u232 ? troppo lungo.", _ "WPF Notepad", MessageBoxButton.OK, MessageBoxImage.Error) Return False Catch ex As IOException MessageBox.Show("Errore di I/O.", "WPF Notepad", MessageBoxButton.OK, _ MessageBoxImage.Error) Return False Catch ex As Exception MessageBox.Show(ex.ToString, "WPF Notepad", MessageBoxButton.OK, _ MessageBoxImage.Error) Return False End Try Case Else Return False End Select End With End FunctionL'opportunità di scrivere un metodo che restituisce un valore booleano risiede nel fatto che, in questo modo, i precedenti metodi che richiamano questo potranno conoscere l'esito del salvataggio. Anche in questo frangente abbiamo utilizzato la medesima tecnica di controllo dei permessi, questa volta in scrittura, utilizzando la classe FileIOPermission.
La creazione del nostro Notepad di base è terminata. Potete avviarlo e divertirvi ad utilizzare i comandi predefiniti di WPF per rendervi conto con mano della straordinaria potenza di questa funzionalità (per alcuni di essi sarà necessario evidenziare il testo desiderato). Ovviamente, il Notepad può essere migliorato, aggiungendo funzionalità di stampa o suddividendo il salvataggio nei due comandi canonici Salva e Salva con nome. Come accennato all'inizio, il progetto ci servirà in articoli futuri per osservare l'utilizzo di Microsoft Expression Blend, un potente strumento per la realizzazione di interfacce grafiche avanzate in WPF.
Conclusioni
In questo articolo avete imparato alcuni nuovi concetti su WPF, come la riflessione di oggetti, gli ApplicationsCommands, l'utilizzo di risorse in applicazioni WPF e l'arrangement dei controlli anche senza l'utilizzo del DockPanel. Per ulteriori informazioni potete contattarmi al mio indirizzo visitare il mio blog.