Guida al Visual Basic .NET
Capitolo 70° - PropertyGrid
Questo controllo è davvero molto complesso: rappresenta una griglia delle proprietà, esattamente la stessa che lo sviluppatore usa per modificare le caratteristiche dei vari controlli nel form designer. La sua enorme potenza sta nel fatto che, attraverso la reflection, riesce a gestire qualsiasi oggetto con facilità. Le si può associare un controllo del form, su cui l'utente può agire a proprio piacimento, ma anche una classe, ad esempio le opzioni del programma, con cui sarà quindi possibile interagire molto semplicemente da un'unica interfaccia. Le proprietà e i metodi importanti sono:
In questo capitolo mi concentrerò sul caso in cui si debba interfacciare PropertyGrid con un oggetto nuovo creato da codice. Binding di classi create dal programmatorePer far sì che PropertyGrid visualizzi correttamente una classe creata dal programmatore, basta assegnare un oggetto di quel tipo alla proprietà SelectedObject, poichè tutto il processo viene svolto tramite reflection. Tuttavia ci sono alcune situazioni in cui questo processo ha bisogno di un aiuto esterno per funzionare: quando le proprietà sono di tipo reference (stringhe escluse), non vengono visulizzati tutti i loro membri, poichè il controllo non è in grado di convertire un valore adatto in stringa. Ad esempio, se si deve leggere un oggetto di tipo Person, il nome e la data di nascita verranno analizzati correttamente, ma il campo Fretello As Person come verrà interpretato? Non è possibile far stare una classe su una sola riga, poichè non si conosce il modo di convertirla in un valore rappresentabile (in questo caso, in una stringa). Lo strumento che Vb.Net fornisce per arginare questo problema è un attributo, di nome TypeConverter, definito nel namespace System.ComponentModel (dove, tra l'altro, sono situati tutti gli altri attributi usati in questo capitolo). Questo accetta come costruttore un parametro di tipo Type, che espone il tipo di una classe con la funzione di convertitore. Ad esempio: 'Questa classe ha la funzione di convertire Person in stringa Public Class PersonConverter '(Per convenzione, i convertitori di questo tipo, devono 'terminare con la parola "Converter" '... End Class Public Class Person Private _Name As String Private _Birthday As Date Private _Brother As Person '... 'Per la proprietà Brother (fratello), si applica l'attributo 'TypeConverter, specificando quale sia la classe convertitore. 'Si utilizza solo il tipo perchè la classe, come vedremo 'in seguito, espone solo metodi d'istanza, ma che possono 'essere utilizzati da soli semplicemente fornendo i parametri 'adeguati. Perciò sarà il programma stesso a creare, 'a runtime, un oggetto di questo tipo e ad usarne la funzioni <TypeConverter(GetType(PersonConverter))> _ Public Property Brother() As Person '... End Class
Ecco un esempio di come si presenterà il controllo dopo aver fornito queste direttive:
'Questo namespace contiene gli attributi necessari a 'impostare le proprietà in modo che si interfaccino 'correttamente con PropertyGrid Imports System.ComponentModel 'Quando si usa uno statementes Imports, la prima voce 'si riferisce al nome del file *.dll in s?. Dato che si 'vuole BooksManager sia consierato come una namespace, non 'bisogna aggiungere un altro namespace BooksManager in questo file 'L'autore del libro, con eventuale biografia Public Class Author 'Il nome completo Private _Name As String 'Data di nascita e morte Private _Birth, _Death As Date 'Indica se l'autore è ancora vivo Private _IsStillAlive As Boolean 'Una piccola biografia Private _Biography As String <DisplayName("Nome"), _ Description("Il nome dell'autore."), _ Browsable(True), _ Category("Generalita'")> _ Public Property Name() As String Get Return _Name End Get Set(ByVal Value As String) _Name = Value End Set End Property <DisplayName("Piccola biografia"), _ Description("Un riassunto delle parti più significative della " & _ "vita dell'autore."), _ Browsable(True), _ Category("Dettagli")> _ Public Property Biography() As String Get Return _Biography End Get Set(ByVal Value As String) _Biography = Value End Set End Property <DisplayName("Data di nascita"), _ Description("La data di nascita dell'autore."), _ Browsable(True), _ Category("Generalita'")> _ Public Property Birth() As Date Get Return _Birth End Get Set(ByVal Value As Date) 'Nessun controllo: la data di nascita può essere 'spostata a causa di uno sbaglio, che altrimenti 'potrebbe produrre un'eccezione _Birth = Value End Set End Property <DisplayName("Data di morte"), _ Description("Data di morte dell'autore."), _ Browsable(True), _ Category("Generalita'")> _ Public Property Death() As Date Get Return _Death End Get Set(ByVal Value As Date) 'Bisogna assicurarsi che la data di morte sia 'posteriore a quella di nascita If Value.CompareTo(Me.Birth) < 1 Then 'Genera un'eccezione Throw New ArgumentException("La data di morte deve " & _ "essere posteriore a quella di nascita!") Else 'Prosegue l'assegnazione _Death = Value 'Impostando la data di morte si suppone che l'autore 'non sia più in vita... Me.IsStillAlive = False End If End Set End Property <DisplayName("Vive"), _ Description("Determina se l'autore è ancora in vita."), _ Browsable(True), _ Category("Generalita'")> _ Public Property IsStillAlive() As Boolean Get Return _IsStillAlive End Get Set(ByVal Value As Boolean) _IsStillAlive = Value End Set End Property 'Un nome e una data di nascita sono obbligatori Sub New(ByVal Name As String, ByVal Birth As Date) Me.Name = Name Me.Birth = Birth Me.IsStillAlive = True End Sub 'Tuttavia, il controllo PropertyGrid richiede un costruttore 'senza parametri Sub New() Me.Birth = Date.Now Me.IsStillAlive = True End Sub End Class Public Class IsbnConverter 'Facendo derivare questa classe da ExpandableObjectConverter 'si comunica al compilatore che questa classe è usata per 'convertire in stringa un valore rappresentabile in una 'PropertyGrid. Così facendo, sarà possibile modificare 'il codice agendo sulla stringa complessiva e non 'obbligatoriamente sulle varie parti Inherits ExpandableObjectConverter 'Determina se sia possibile convertire nel tipo dato Public Overrides Function CanConvertTo(ByVal Context As ITypeDescriptorContext, _ ByVal DestinationType As Type) As Boolean 'Si può convertire in Isbn, dato che questa classe è 'scritta apposta per questo If (DestinationType Is GetType(Isbn)) Then Return True End If Return MyBase.CanConvertFrom(Context, DestinationType) End Function 'Determina se sia possibile convertire dal tipo dato Public Overrides Function CanConvertFrom(ByVal Context As ITypeDescriptorContext, _ ByVal SourceType As Type) As Boolean 'Si può convertire da String, dato che questa classe è 'scritta apposta per questo If (SourceType Is GetType(String)) Then Return True End If Return MyBase.CanConvertFrom(Context, SourceType) End Function 'Converte da stringa a Isbn Public Overrides Function ConvertFrom(ByVal Context As ITypeDescriptorContext, _ ByVal Culture As Globalization.CultureInfo, _ ByVal Value As Object) As Object If TypeOf Value Is String Then Dim Str As String = DirectCast(Value, String) 'Cerca di creare un nuovo oggetto isbn Try Dim Obj As Isbn = Isbn.CreateNew(Str) Return Obj Catch ex As Exception MessageBox.Show(ex.Message, "Books Manager", _ MessageBoxButtons.OK, MessageBoxIcon.Exclamation) Return New Isbn End Try End If Return MyBase.ConvertFrom(Context, Culture, Value) End Function 'Converte da Isbn a stringa Public Overrides Function ConvertTo(ByVal Context As ITypeDescriptorContext, _ ByVal Culture As Globalization.CultureInfo, _ ByVal Value As Object, ByVal DestinationType As Type) As Object If DestinationType Is GetType(String) And _ TypeOf Value Is Isbn Then Dim Temp As Isbn = DirectCast(Value, Isbn) Return Temp.ToString End If Return MyBase.ConvertTo(Context, Culture, Value, DestinationType) End Function End Class 'Il codice ISBN, dal primo gennaio 2007, deve obbligatoriamente 'essere a tredici cifre. Per questo motivo metterò solo 'questo tipo nel sorgente 'P.S.: per convenzione, gli acronimi con più di due lettere devono 'essere scritti in Pascal Case Public Class Isbn 'Un codice è formato da: 'Un prefisso (3 cifre) - solitamente 978 indica un libro in generale Private _Prefix As Int16 = 978 'Un identificativo linguistico (da 1 a 5 cifre): indica 'il paese di provenienza dell'autore - in Italia è 88 Private _LanguageID As Int16 = 88 'Un prefisso editoriale (da 2 a 6 cifre): indica l'editore Private _PublisherID As Int64 = 89637 'Un identificatore del titolo Private _TitleID As Int32 = 15 'Un codice di controllo che può variare da 0 a 10. 10 viene 'indicato con X, perciò lo imposto come Char Private _ControlChar As Char = "9" <DisplayName("Prefisso"), _ Description("Prefisso del codice, costituito da tre cifre."), _ Browsable(True), _ Category("Isbn")> _ Public Property Prefix() As Int16 Get Return _Prefix End Get Set(ByVal Value As Int16) If Value = 978 Or Value = 979 Then _Prefix = Value Else Throw New ArgumentException("Prefisso non valido!") End If End Set End Property <DisplayName("ID Lingua"), _ Description("Identifica l'area da cui previene l'autore."), _ Browsable(True), _ Category("Isbn")> _ Public Property LanguageID() As Int16 Get Return _LanguageID End Get Set(ByVal Value As Int16) _LanguageID = Value End Set End Property <DisplayName("ID Editore"), _ Description("Identifica il marchio dell'editore."), _ Browsable(True), _ Category("Isbn")> _ Public Property PublisherID() As Int32 Get Return _PublisherID End Get Set(ByVal Value As Int32) _PublisherID = Value End Set End Property <DisplayName("ID Titolo"), _ Description("Identifica il titolo del libro."), _ Browsable(True), _ Category("Isbn")> _ Public Property TitleID() As Int32 Get Return _TitleID End Get Set(ByVal Value As Int32) _TitleID = Value End Set End Property <DisplayName("Carattere di controllo"), _ Description("Verifica la correttezza degli altri valori."), _ Browsable(True), _ Category("Isbn")> _ Public Property ControlChar() As Char Get Return _ControlChar End Get Set(ByVal Value As Char) _ControlChar = Value End Set End Property Public Sub New() End Sub 'Restituisce in forma di stringa il codice Public Overrides Function ToString() As String Return String.Format("{0}-{1}-{2}-{3}-{4}", _ Me.Prefix, Me.LanguageID, Me.PublisherID, _ Me.TitleID, Me.ControlChar) End Function 'Metodo statico factory per costruire un nuovo codice ISBN. Se 'si mettesse questo codice nel costruttore, l'oggetto verrebbe 'comunque creato anche se il codice inserito fosse errato. 'In questo modo, la creazione viene fermata e restituito 'Nothing in caso di errori Shared Function CreateNew(ByVal StringCode As String) As Isbn 'Con le espressioni regolari, ottiene le varie parti Dim Split As New System.Text.RegularExpressions.Regex( _ "(?<Prefix>d{3})-(?<Language>d{1,5})" & _ "-(?<Publisher>d{2,6})-(?<Title>d+)-(?<Control>w)") Dim M As System.Text.RegularExpressions.Match = _ Split.Match(StringCode) 'Se la lunghezza del codice, senza trattini, è di '13 caratteri e il controllo tramite espressioni regolari 'ha avuto successo, procede If StringCode.Length = 17 And M.Success Then Dim Result As New Isbn With Result .Prefix = M.Groups("Prefix").Value .LanguageID = M.Groups("Language").Value .PublisherID = M.Groups("Publisher").Value .TitleID = M.Groups("Title").Value .ControlChar = M.Groups("Control").Value End With Return Result Else Throw New ArgumentException("Il codice inserito è errato!") End If End Function End Class 'Una classe che rappresenta un libro Public Class Book Private _Title As String 'Si suppone che un libro abbia meno di 32767 pagine XD Private _Pages As Int16 = 100 'Si possono anche avere più autori: in questo caso si ha 'una lista a tipizzazione forte. Private _Authors As New List(Of Author) 'L'eventuale serie a cui il libro appartiene Private _Series As String 'Casa editrice Private _Publisher As String 'Data di pubblicazione Private _PublicationDate As Date 'Argomento Private _Subject As String 'Costo in euro Private _Cost As Single = 1.0 'Ristampa Private _Reprint As Byte = 1 'Codice ISBN13 Private _Isbn As New Isbn <DisplayName("Titolo"), _ Description("Il titolo del libro."), _ Browsable(True), _ Category("Editoria")> _ Public Property Title() As String Get Return _Title End Get Set(ByVal Value As String) _Title = Value End Set End Property <DisplayName("Collana"), _ Description("La collana o la serie a cui il libro appartiene."), _ Browsable(True), _ Category("Editoria")> _ Public Property Series() As String Get Return _Series End Get Set(ByVal Value As String) _Series = Value End Set End Property <DisplayName("Editore"), _ Description("La casa editrice."), _ Browsable(True), _ Category("Editoria")> _ Public Property Publisher() As String Get Return _Publisher End Get Set(ByVal Value As String) _Publisher = Value End Set End Property <DisplayName("Pagine"), _ Description("Il numero di pagine da cui il libro è composto."), _ DefaultValue("100"), _ Browsable(True), _ Category("Dettagli")> _ Public Property Pages() As Int16 Get Return _Pages End Get Set(ByVal Value As Int16) If Value > 0 Then _Pages = Value Else Throw New ArgumentException("Numero di pagine insufficiente!") End If End Set End Property <DisplayName("Autore/i"), _ Description("L'autore o gli autori."), _ Browsable(True), _ Category("Editoria")> _ Public ReadOnly Property Authors() As List(Of Author) Get Return _Authors End Get End Property <DisplayName("Pubblicazione"), _ Description("La data di pubblicazione della prima edizione."), _ Browsable(True), _ Category("Dettagli")> _ Public Property PublicationDate() As Date Get Return _PublicationDate End Get Set(ByVal Value As Date) _PublicationDate = Value End Set End Property <DisplayName("Codice ISBN"), _ Description("Il codice ISBN conformato alla normativa di 13 cifre."), _ Browsable(True), _ Category("Editoria"), _ TypeConverter(GetType(IsbnConverter))> _ Public Property Isbn() As Isbn Get Return _Isbn End Get Set(ByVal Value As Isbn) _Isbn = Value End Set End Property <DisplayName("Ristampa"), _ Description("Il numero della ristampa."), _ DefaultValue(1), _ Browsable(True), _ Category("Dettagli")> _ Public Property Reprint() As Byte Get Return _Reprint End Get Set(ByVal Value As Byte) If Value > 0 Then _Reprint = Value Else Throw New ArgumentException("Ristampa: valore errato!") End If End Set End Property <DisplayName("Costo"), _ Description("Il costo del libro, in euro."), _ Browsable(True), _ Category("Editoria")> _ Public Property Cost() As Single Get Return _Cost End Get Set(ByVal Value As Single) If Value > 0 Then _Cost = Value Else Throw New ArgumentException("Inserire prezzo positivo!") End If End Set End Property End Class E il codice del form: Class Form1 Private Sub strAddBook_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles strAddBook.Click Dim Title As String = _ InputBox("Inserire il titolo del libro:", "Books Manager") 'Controlla che la stringa non sia vuota o nulla If Not String.IsNullOrEmpty(Title) Then Dim Item As New ListViewItem(Title) Dim Book As New Book() Book.Title = Title Item.ImageIndex = 0 Item.Tag = Book lstBooks.Items.Add(Item) End If End Sub Private Sub lstBooks_SelectedIndexChanged(ByVal sender As Object, _ ByVal e As EventArgs) Handles lstBooks.SelectedIndexChanged 'Esce dalla procedura se non ci sono elementi selezionati If lstBooks.SelectedIndices.Count = 0 Then Exit Sub End If 'Altrimenti procede pgBook.SelectedObject = lstBooks.SelectedItems(0).Tag End Sub
C#, TypeScript, java, php, EcmaScript (JavaScript), Spring, Hibernate, React, SASS/LESS, jade, python, scikit, node.js, redux, postgres, keras, kubernetes, docker, hexo, etc...
|