Ελληνικό displayname στα properties ενός Class
Με αυτό το post θα προσπαθήσω να καλύψω το System.Attribute Class καθώς επίσης και τον TypeDescriptor και PropertyDescriptor.
Επίσης επισυνάπτω ένα Zip File με τον κώδικα και το testProject.
Γενικά
Kάθε Class που δημιουργούμε περιγράφεται από τον System.ComponentModel. Typedescriptor με τα shared methods του (πχ GetProperties).
Κάθε property του Class περιγράφεται από τoν System.Componentmodel.propertyDescriptor και τα properties του (πχ DisplayName, Description).
Κάθε member (property, method, event κλπ) του Class μπορεί έχει διάφορα Attributes τα οποία διαβάζει ο TypeDescriptor για να περιγράψει το Class.
ΠΧ. Η περιγραφή ενός property στο propertyGrid συμπληρώνεται από το Attribute System.Componentmodel.Description(<Description>).
Αν ο PropertyDescriptor βρεί αυτό το attribute το εμφανίζει στην περιγραφή στο property Grid.
Αλλο attribute είναι το System.Drawing.ToolBoxBitmapAttribute που αναφέρεται στο Control Class και δηλώνει στο toolBoxitem του designer το εικονίδιο που θα εμφανίσει στο toolbox.
Ενα άλλο πολύ σημαντικό Atrribute είναι ο TypeConverter.
Αυτό το χρησιμοποιούμε κυρίως στα Custom Collections για να μπορεί ο Collection Editor να περιγράψει τα Collection Items. (Σε κάποιο άλλο post θα προσπαθήσω να καλύψω αυτό το πολύ σημαντικό κομμάτι)
Στο θέμα
Στην περίπτωση τη δική μας χρειαζόμαστε από τον PropertyDescriptor να διαβάσει το property DisplayName με τα ελληνικά. Ομως δεν υπάρχει τέτοιο attribute (δεν γνωρίζω αν στο .Net Framework 2.0 έχει προστεθεί).
Γι’ αυτό πρέπει να το δημιουργήσουμε.
Για να γίνει αυτό πρέπει να δημιουργήσουμε ένα Class με Inheritance στο System.Attribute.
'To Attribute System.AttributeUsageAttribute δηλώνει ότι
'μπορώ να το χρησιμοποιήσω για Members τύπου property.
'Άλλα AttributeTargets enums είναι to All,το Assembly το Class κλπ.
<System.AttributeUsage(AttributeTargets.Property)> _
Public Class DisplayNameAttribute
Inherits System.Attribute
Private _DisplayName As String = ""
Public Sub New(ByVal DisplayName As String)
_DisplayName = DisplayName
End Sub
Public Property DisplayName() As String
Get
Return _DisplayName
End Get
Set(ByVal Value As String)
_DisplayName = Value
End Set
End Property
End Class
Κάθε Class που δημιουργούμε (αν θέλουμε κάποιο property να έχει DisplayName διαφορετικό από το ίδιο του το όνομα) πρέπει να συμπληρώνει (να κάνει Implementation) το Interface System.Componentmodel.ICustomTypeDescriptor ή (όπως στο παράδειγμά μου) να είναι inherited ενός άλλου Class που κάνει το Implementation.
Μεταφορικά θα έλεγα ότι “του φοράμε τη στολή με τα διακριτικά μας”.
Imports SValsamis
Public Class TestDisplayNamePropertyObject
Inherits CustomDisplayPropertyName
'To Class CustomDisplayPropertyName κάνει Implementation του Interface
'ΙCustomTypeDescriptor
Private _Property1 As String
Private _Property2 As String
<SValsamis.DisplayName("Ιδιότητα πρώτη"), _
System.ComponentModel.Description("Η πρώτη ιδιότητα του TestDisplayNamePropertyObject")> _
Public Property property1() As String
Get
Return _Property1
End Get
Set(ByVal Value As String)
_Property1 = Value
End Set
End Property
<SValsamis.DisplayName("Ιδιότητα δεύτερη"), _
System.ComponentModel.Description("Η δεύτερη ιδιότητα του TestDisplayNamePropertyObject")> _
Public Property property2() As String
Get
Return _Property2
End Get
Set(ByVal Value As String)
_Property2 = Value
End Set
End Property
End Class
Το Class που κάνει Implementation στο ICustomTypeDescriptor “η στολή με τα διακριτικά μας”
Ο κύριος σκοπός αυτού του Class είναι να δημιουργήσει ένα Instance του δικού μας
propertyDescriptor τύπου "CustomDisplayNamePropertyDescriptor"
Imports System
Imports System.ComponentModel
Public Class CustomDisplayPropertyName
Implements ICustomTypeDescriptor
Private Props As PropertyDescriptorCollection
'Αυτά τα Implementations δεν αλλάζουν τίποτα
'από το αρχικό Class και δεν χρειάζεται κάποιο άλλο σχόλιο
'γιατί απλά μεταφέρουν τα αντίστοιχα members από το TypeDescriptor
#Region "ΙCustomTypeDescriptor Implementations"
Public Function GetAttributes() As AttributeCollection Implements ΙCustomTypeDescriptor.GetAttributes
Return TypeDescriptor.GetAttributes(Me, True)
End Function
Public Function GetClassName() As String Implements ICustomTypeDescriptor.GetClassName
Return TypeDescriptor.GetClassName(Me, True)
End Function
Public Function GetComponentName() As String _
Implements ICustomTypeDescriptor.GetComponentName
Return TypeDescriptor.GetComponentName(Me, True)
End Function
Public Function GetConverter() As TypeConverter _
Implements ICustomTypeDescriptor.GetConverter
Return TypeDescriptor.GetConverter(Me, True)
End Function
Public Function GetDefaultEvent() As EventDescriptor _
Implements ICustomTypeDescriptor.GetDefaultEvent
Return TypeDescriptor.GetDefaultEvent(Me, True)
End Function
Public Function GetDefaultProperty() As PropertyDescriptor _
Implements ICustomTypeDescriptor.GetDefaultProperty
Return TypeDescriptor.GetDefaultProperty(Me, True)
End Function
Public Function GetEditor(ByVal editorBaseType As System.Type) As Object _
Implements ICustomTypeDescriptor.GetEditor
Return TypeDescriptor.GetEditor(Me, editorBaseType, True)
End Function
Public Overloads Function GetEvents() As EventDescriptorCollection _
Implements ICustomTypeDescriptor.GetEvents
Return TypeDescriptor.GetEvents(Me, True)
End Function
Public Overloads Function GetEvents(ByVal attributes() As Attribute) As _
EventDescriptorCollection _
Implements System.ComponentModel.ICustomTypeDescriptor.GetEvents
Return TypeDescriptor.GetEvents(Me, attributes, True)
End Function
Public Function GetPropertyOwner(ByVal pd As PropertyDescriptor) As Object _
Implements System.ComponentModel.ICustomTypeDescriptor.GetPropertyOwner
Return Me
End Function
#End Region
'Εδώ σημβαίνει αυτό που θέλουμε να κάνουμε
#Region "Get Properties"
Public Overloads Function GetProperties() As PropertyDescriptorCollection _
Implements ICustomTypeDescriptor.GetProperties
If Props Is Nothing Then
'Παίρνουμε τα properties του πρωτότυπου Class
Dim baseproperties As PropertyDescriptorCollection = _
TypeDescriptor.GetProperties(Me, True)
'Δημιουργούμε ένα κενό propertiesCollection
Props = New PropertyDescriptorCollection(Nothing)
'και από κάθε property του πρωτότυπου δημιουργούμε
'ένα δικό μας propertyDescriptor
'ο οποίος θα διαβάσει το DisplayName property
'του DisplayNameAttribute και του περνάμε στον constructor το αυθεντικό property
For Each prp As PropertyDescriptor In baseproperties
Props.Add(New SValsamis.CustomDisplayNamePropertyDescriptor(prp))
Next
End If
'επιστρέφουμε το νέο Collection
Return Props
End Function
'To ίδιο σημβαίνει και εδώ με τη μόνη διαφορά
'ότι γίνεται φιλτράρισμα με τα attributes του propertyDescriptor
Public Overloads Function GetProperties(ByVal attributes() As Attribute) As _
PropertyDescriptorCollection _
Implements System.ComponentModel.ICustomTypeDescriptor.GetProperties
If Props Is Nothing Then
Dim baseproperties As PropertyDescriptorCollection = _
TypeDescriptor.GetProperties(Me, attributes, True)
Props = New PropertyDescriptorCollection(Nothing)
For Each prp As PropertyDescriptor In baseproperties
Props.Add(New CustomDisplayNamePropertyDescriptor(prp))
Next
End If
Return Props
End Function
#End Region
End Class
Τέλος δημιουργούμε ένα Class τύπου propertyDescriptor και κάνουμε override το property displayname με την τιμή που θα βρεί από το δηλωμένο attribute τύπου SValsamis.DisplayNameAttribute
Imports System
Imports System.ComponentModel
'Αυτό το Class κάνει override το DisplayName του propertyDescriptor
'με το DisplayName property του DisplayNameAttribute μας
Public Class CustomDisplayNamePropertyDescriptor
Inherits PropertyDescriptor
'Instance του PropertyDescriptor που έρχεται στον Constructor
'από το ICustomTypeDescriptor και περιέχει το πρωτότυπο property
Private _BasePropertyDescriptor As PropertyDescriptor
Public Sub New(ByVal BasePropertyDescriptor As PropertyDescriptor)
MyBase.New(BasePropertyDescriptor)
_BasePropertyDescriptor = BasePropertyDescriptor
End Sub
#Region "MustOverrides απο το Base Class"
'Ολα τα παρακάτω Members λειτουργούν ακριβώς όπως το πρωτότυπο και είναι MustOverride
Public Overrides Function CanResetValue(ByVal component As Object) As Boolean
Return _BasePropertyDescriptor.CanResetValue(component)
End Function
Public Overrides ReadOnly Property ComponentType() As System.Type
Get
Return _BasePropertyDescriptor.ComponentType
End Get
End Property
Public Overrides Function GetValue(ByVal component As Object) As Object
Return _BasePropertyDescriptor.GetValue(component)
End Function
Public Overrides ReadOnly Property IsReadOnly() As Boolean
Get
Return _BasePropertyDescriptor.IsReadOnly
End Get
End Property
Public Overrides ReadOnly Property PropertyType() As System.Type
Get
Return _BasePropertyDescriptor.PropertyType
End Get
End Property
Public Overrides Sub ResetValue(ByVal component As Object)
_BasePropertyDescriptor.ResetValue(component)
End Sub
Public Overrides Sub SetValue(ByVal component As Object, ByVal value As Object)
_BasePropertyDescriptor.SetValue(component, value)
End Sub
Public Overrides Function ShouldSerializeValue(ByVal component As Object) As Boolean
Return _BasePropertyDescriptor.ShouldSerializeValue(component)
End Function
#End Region
'Εδώ διαβάζουμε το property DisplayName του
'Attribute "DisplayNameAttribute" και κάνουμε Override το
'DisplayName του πρωτότυπου propertyDescriptor
Public Overrides ReadOnly Property DisplayName() As String
Get
'Διαβάζουμε το Attribute SValsamis.DisplayNameAttribute
Dim attr As Attribute = _BasePropertyDescriptor.Attributes(GetType(SValsamis.DisplayNameAttribute))
'Αν δεν υπάρχει επιστρέφουμε το displayName του πρωτότυπου
'το οποίο είναι το όνομα του property
If attr Is Nothing Then
Return _BasePropertyDescriptor.DisplayName
Else
'Αν υπάρχει τότε επιστρέφουμε το DisplayName property του Attribute
Return CType(attr, SValsamis.DisplayNameAttribute).DisplayName
End If
End Get
End Property
Public Overrides ReadOnly Property Description() As String
Get
Return _BasePropertyDescriptor.Description
End Get
End Property
End Class
Επίλογος
Ελπίζω το άρθρο αυτό να βοήθησε λίγο στο να κατανοήσει κάποιος τα Attributes, Typedescriptor και propertyDescriptor.
Εγώ χρησιμοποιώ αυτό το σενάριο για να μπορώ να δίνω στους χρήστες των εφαρμογών μου δυνατότητες παραμετροποίησης. Δημιουργώ κάποια Classes πχ Report properties και σε ένα propertyGrid Control αφήνω τον χρήστη να συμπληρώσει τα properties, τα οποία όμως είναι φιλικά σε αυτόν και κυρίως ΕΛΛΗΝΙΚΑ.
Σε κάποιο άλλο post θα μετατρέψουμε τo attribute να μιλάει σε διάφορες γλώσσες ανάλογα με το cultrure της εφαρμογής και θα αναφερθούμε αναλυτικότερα στον TypeConverter.
Σταύρος Βαλσάμης
Προγραμματιστής