Pro demonstraci si vytvoříme komponentu s názvem Person. V praxi bychom pro ni těžko hledali využití, ale v teoretické rovině je to ideální ukázka. A jak bude vlastně vypadat?
' Jmenný prostor obsahující atributy pro práci s komponentami
Imports System.ComponentModel
Public Class Person
Inherits Component
End Class
Tento kód umístíme do nového souboru v našem projektu. Pojmenujme ho výstižně Person.vb. Po kompilaci projektu nám v ToolBoxu přibyde nová kategorie a v ní nová komponenta.
Pokud Vám vrtá hlavou, proč nepoužijeme ovládací prvek (Control), odpověď je snadná. Control má kromě funkcionality také velkou vizuální úlohu a používá se k interakci s uživatelem. Component je určena ke snadnějšímu sdílení objektů v aplikaci a v podstatě pouze reprezentuje třídu.
Struktura
Třídě Person nyní přidáme některé vlastnosti, na kterých budeme později prezentovat používání atributů z Systém.ComponentModel. Určitě bude třeba vědět, jak se naše osoba jmenuje, kdy se narodila a jak jí můžeme kontaktovat.
Public Class Person
Inherits Component
Private _name As String
Private _birth As Date
Private _mail As String
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
If Not String.IsNullOrEmpty(value) Then
_name = value
Else
Throw New ArgumentNullException("value")
End If
End Set
End Property
Public Property Birth() As Date
Get
Return _birth
End Get
Set(ByVal value As Date)
_birth = value
End Set
End Property
Public Property Mail() As String
Get
Return _mail
End Get
Set(ByVal value As String)
_mail = value
End Set
End Property
Public ReadOnly Property Age() As Integer
Get
If Me.Birth <> Nothing Then Return Date.Now.Year - Me.Birth.Year Else Return 0
End Get
End Property
End Class
Atributy
Jmenný prostor Systém.ComponentModel obsahuje (jak je již z názvu patrné) spoustu užitečných tříd pro práci s komponentami, potažmo vizuální editaci jejich vlastností. Než se pustíme do pokročilejší editace vlastností naší osoby, dáme nějakou kulturu jejím vlastnostem. Jistě jste si všimli, že v okně Properties, konkrétně v jeho části s nápovědou, je poněkud vymeteno, oproti komponentám, které nám nabízí Visual Studio. Také vlastnosti, které jsme definovali se všechny zařadili do skupiny Misc, přesto, že je o to nikdo nežádal. S tím nám pomohou atributy:
Browsable
Atribut Browsable určuje, zda se má vlastnost zobrazit v okně Properties. Zejména u vlastností, které jsou pouze pro čtení (ReadOnly) je to celkem zbytečné. Skryjme tedy vlastnost Age pro práci v Designeru.
<Browsable(False)> _
Public ReadOnly Property Age() As Integer
Get
If Me.Birth <> Nothing Then Return Date.Now.Year - Me.Birth.Year Else Return 0
End Get
End Property
Tento zápis zajistí, že vývojář tuto vlastnost v okně Properties neuvidí. Přístup z kódu zůstane samozřejmě nedotčený.
Aributy se zapisují do ostrých závorek. Mezera a podtržítko za atributem jsou proto, že se atributy mají psát před vlastnost:
<Browsable(False)> Public ReadOnly Property Age() As Integer
To by ovšem bylo značně nepřehledné, a proto můžeme za atributem tímto způsobem rozdělit zápis stejně, jako kdybychom například zadávali parametry metodě, která jich požaduje hodně, a kód by přesahoval hranice obrazovky. Pak je možno parametry psát například každý na jeden řádek, a patřičně je oddělovat právě tímto separátorem.
Category
Nyní máme všechny vlastnosti v kategorii Misc. Tam se umisťují všechny vlastnosti, které nemají kategorii určenou. My si vlasnosti naší osoby rozdělíme do kategorií Personálie a Kontakt. Jakým způsobem myslím není třeba vysvělovat. Jediný problém by mohl nastat u Age, kde již jeden atribut máme. Není sice třeba tuto vlastnost nikam řadit, protože je skrytá, ale pro pořádek jí zařadíme do kategorie personálií. A jak tedy zapíšeme více atributů za sebou? Jednoduše.
<Browsable(False), Category("Personal")> _
DefaultEvent, DefaultProperty, DefaultValue
Než si vysvětlíme tyto tři atributy, trochu upravíme naší osobu. Přidáme jí konstruktor, dvě události a jednu novou vlastnost. Bude se hodit vědět, jestli osoba žije, nebo je již po smrti. Přidáme vlastnost Alive. Bude jen pro čtení a zařadíme ji do kategorie personálií. Také ji skryjeme atributem Browsable. Poté vytvoříme dvě nové události – Born a Died. Ty se obejdou bez parametrů.
Public Event Born()
Public Event Dead()
Nyní má komponenta způsob, jak nám dát vědět, když se osoba narodí a když zemře. Za narození budeme považovat její vytvoření. Vyvoláme tedy událost Born v konstruktoru.
Public Sub New()
_alive = True
RaiseEvent Born()
End Sub
Událost Dead bude vyvolána zabití osoby metodou Kill:
Public Sub Kill()
_alive = False
RaiseEvent Died()
End Sub
Teď už ke slíbeným atributům DefaultXxx. DefaultEvent a DefaultProperty mají podobné použití. Jak jste si jistě všimli, okno Properties neumožňuje editovat pouze vlastnosti, ale též události. Pokud máme na formuláři dvě tlačítka a u jednoho upravíme nějakou vlastnost, když klikneme na další tlačítko, v Properties se přesune focus na stejnou vlastnost. Totéž platí o událostech. Když ale máme dva různé objekty, a u druhého neexistuje stejná vlastnost jako u prvního, focus padne právě na DefaultEvent, resp. DefaultProperty.
DefaultEvent
DefaultEvent udává, která událost je výchozí pro komponentu. Tato vlastnost dostane focus, jakmile otevřeme okno Properties a vybereme naši komponentu. Handler pro tuto vlastnost se také vytvoří po dvojkliku na komponentu na formuláři.
DefaultProperty funguje stejně jako DefaultEvent, ovšem s tím rozdílem, že platí pro záložku Properties a ne Events, tudíž na vlastnosti komponenty.
DefaultEvent a DefaultProperty jsou atributy na úrovni třídy. Upravíme tedy:
<DefaultEvent("Born"), DefaultProperty("Name")> _
Public Class Person
...
DefaultValue je atribut, který udává výchozí hodnotu vlastnosti. Jak si můžete všimnout, pokud umístíte na formulář nějaký prvek, v panelu Properties jsou téměř všechny jeho vlastnosti vykresleny obyčejným fontem. Pokud ale nějakou změníte, ztuční. Vlastnosti, které nemají výchozí hodnotu se vykreslují tučným fontem. U naší komponenty budou ale silně všechny vlastnosti. Nedefinovali jsme jim totiž výchozí hodnotu atributem DefaultValue. My si nastavíme výchozí hodnotu u vlastnosti Name a Mail, a sice na "(none)". U ostatních vlastností to nemá moc smysl.
<DefaultValue(GetType(String), "(none)"> _
V prvním parametru říkáme, že výchozí hodnota je typu String. Funkce GetType nám zajistí, že funkce dostane jako parametr datový typ Type. Samotný String předat nemůžeme, protože se jedná o datový typ a nemůže být použit jako výraz.
Description
Další užitečný atribut je Description. Mnohdy je obtížné najít vhodný název pro vlastnost či událost. Buď nebývá dost krátký nebo dost jednoznačný. Pro vývojáře je pak zdržující, když musí testovat, k čemu dotyčná vlastnost je (například pokud používá third-party komponentu, která má nedostatečnou dokumentaci). Atribut Description v takových případěch značně ulehčuje práci. Definuje totiž text nápovědy v okně Properties. Naše komponenta má vlastnosti pojmenované jednoznačně, ovšem krátká nápověda neuškodí ani u těch nejjednoznačnějších vlastností či událostí. Doplňme si tedy krátké texty ke každé vlastnosti. Inspirovat se můžeme popisy u komponent z Visual Studia. Valná většina z nich začíná na "Gets or sets", což dodržím i já.
DesignOnly
Atribut DesignOnly určuje, zda je možno nastavovat hodnotu vlastnosti pouze v režimu návrhu, nebo i za běhu programu. Jak funguje poznáte, když si na formulář umístíte PropertyGrid, nastavíte mu SelectedObject na Person1 a k Person1 přidáte jakoukoliv vlastnost s DesignOnly(True). V okně Properties bude normálně možné nastavovat tuto vlastnost, ovšem při spuštění nebude možno hodnotu této vlastnosti změnit. Ani kódem, ani přes PropertyGrid na Formu.
Nenapadlo mě žádné praktické využití a cpát to do ukázky jen proto, aby to tam bylo, nebudu. Pokud Vás napadá nějaké konstruktivní využití tohoto atributu, určitě se o něj s námi podělte v diskusi.
DesignTimeVisible
Tento atribut se nepoužívá na třídy, které mají grafické znázornění. Jeho najčastější použití je tam, kde komponenta má nějaká subkomponenty, které si vykreslje sama. Třída pro subkomponentu bude pak DesignTimeVisible(False), aby nebyla v listě s komponentami v Designeru.
DisplayName
DisplayName je velice užitečný atribut. Jako parametr si bere text, který určí, jak se má jmenovat vlastnost v PropertyGridu (panel Properties je pouhý PropertyGrid). Pokud tedy nemáte chuť vytvářet komplexní uživatelské rozhraní například pro nastavení aplikace, je možné vytvořit si třídu s odpovídajícími vlastnostmi, nastavit jim DisplayName a rázem dostanete dokonalý prvek k editaci nastavení, místo například desítek CheckBoxů, RadioButtonů a dalších komponent. My si pro demonstraci nastavíme české texty do DisplayName pro naší osobu. Pro skryté vlastnosti to samozřejmě není třeba.
Já sice nejsem velkým fanouškem lokalizace a počešťování, ale obrovskou výhodu vidím v tom, že například můžeme nastavit více user-friendly verze jmen vlastností. Není Anually Bolded Dates pro uživatele lepší, než AnuallyBoldedDates?
ToolBoxItem, ToolBoxBitmap
ToolBoxItem udává, zda se má třída připojit k ToolBoxu. To logicky chceme. Defaultní hodnota atributu je True, takže je netřeba ho definovat. ToolBoxBitmap už je zajímavější. Udává resource, který se použije jako ikona pro třídu, na kterou je atribut aplikován.
< ... , ToolboxBitmap(GetType(Person), "PersonBitmap.png")> _
Do ToolBoxBitmap zadáme jméno obrazového souboru o velikosti 16x16 pixelů.
Závěrem
V tomto článku jsme si probrali některé často používané atributy jmenného prostoru System.ComponentModel. V příštím díle bych rád probral TypeConvertery, které se používají k tomu, aby bylo možné vlastnost rozkliknout a editovat po složkách. Jako například Location – možno rozkliknout na X a Y.
Zdrojové kódy