Jako reakci na některé dotazy k výbornému článku pana Linharta jsem se snažil jeho kód aplikovat do trošičku praktické košilky, aby bylo zřejmé, jak se dá s technologií zásuvných modulů pracovat. Neberte to, prosím, jako odborný výklad, spíše jako vyjádření, jak jsem lekci pana Linharta pochopil já (s plug-iny jsem pracoval poprvé). Omlouvám se také panu Hercegovi a Jechovi, nevím jaká mají v diskusních příspěvcích omezení takže doufám, že ta přemíra textu neshodí místní server (ale bohužel jsem nevěděl, kam jinam bych to mohl hodit) Takže "malé" praktické cvičení: Pokusíme se sestavit skutečně funkční kalkulačku: 1) Spustíme VB -> Nový projekt 2) Zvolíme Class Library (jako první si vytvoříme potřebná rozhranní), do Name dáme třeba PlugInterf (název naší knihovny s rozhraními), Do solution Name cokoliv, třeba Kalkulator. 3) V SolutionExploreru přejmenujte Class1.vb třeba na MathInterface.vb 4) v kódu smažte Public Class - end Class a nahraďte kódem
Public Interface IMathDual
ReadOnly Property name() As String
ReadOnly Property znacka() As String
Function Operace(ByVal x As Single, ByVal y As Single, ByRef vysledek As Double) As String
End Interface
Se stejnou filosofií si pod tento interface (do stejného souboru) doplníme ještě rozhranní druhé, tentokrát pro matematické operace, které pro svůj chod potřebují jako vstup pouze jeden operand:
Public Interface IMathPrim
ReadOnly Property name() As String
ReadOnly Property znacka() As String
Function Operace(ByVal x As Single, ByRef vysledek As Double) As String
End Interface
(kód by bylo vhodné okomentovat, zvláště budete-li jej distribuovat dále, pro šetření místem a časem to nechám bez komentáře, funkčnost bude jasná z plug-inů (alespoň je vidět, jak je to jednoduché!:-) 5) Sestavte knihovnu (menu->Build->Build PlugInterf) Tím se nám vytvořila knihovna (v podadresáři Pluginterf\bin\debug) se jménem PlugInterf.dll, která obsahuje definici našich rozhranní. 6) Nyní si vytvoříme jeden zásuvný modul: 7) Vytvořte si nový projekt: v solutionExploreru klepněte pravým tlačítkem myši a vyberte Add-> New project 8) typ projektu zvolte opět Class Library a do názvu dejte třeba Matika01 9) opět si přejmenujte v solutionExploreru Class1.vb na Matika1.vb 10) V projektu Matika01 klepněte (v solution Exploreru) 2x na My Project, čímž se Vám otevře karta nastavení projektu, kde si na záložce References přidejte referenci na právě vytvořenou knihovnu (add->Browse->na disku vyhledejte PlugInterf.dll) 11) Přepněte se do okna s kódem Matika1.vb. Přejmenujte Class Matika1 na
Public Class Scitani
12) abychom se mohli odvolávat přímo na jednotlivá definovaná rozhranním, napište nad deklaraci Public Class Scitani ještě příkaz
Imports PlugInterf
13) Jako první řádek do těla naší třídy Scitani zapište, že tato třída je vlastně konkrétní implementací námi definovaného rozhranní:
Implements IMathDual
14) Okamžitě jsme si všimli, že VS za nás udělalo podstatnou část práce - dle toho, co jsme definovali v rozhranní nám Visual Studio sestavilo kompletní kostru naší třídy (jak jsme již dříve zmínili, v rozhranní jsme definovali, co naše třída musí obsahovat, a tady je první aplikování této informace). Takže kostru již máme, nyní jenom doplníme, jak přesně jednotlivé vlastnosti a metody budou v naší třídou implementovány: 15) doplníme kód naší třídy třeba takto:
Imports PlugInterf
Public Class Scitani
Implements IMathDual
Public ReadOnly Property name() As String Implements PlugInterf.IMathDual.name
Get
Return "Sčítání"
End Get
End Property
Public Function Operace(ByVal x As Single, ByVal y As Single, ByRef vysledek As Double) As String Implements
PlugInterf.IMathDual.Operace
vysledek = x + y
Return ""
End Function
Public ReadOnly Property znacka() As String Implements PlugInterf.IMathDual.znacka
Get
Return "+"
End Get
End Property
End Class
jinak řečeno - Vlastnost name nám bude vracet slovní popis matematické metody, vlastnost znacka nám vrátí jako string znaménko metody a funkce operace spolkne dva sčítance (x a y) a do referenční proměnné vysledek uloží jejich součet. Návratovou hodnotou je buď prázdný řetězec značící, že výpočet proběhl vpořádku, nebo text chybové zprávy. Stejně tak si můžeme (pod výše uvedený kód) doplnit ještě stejným způsobem třeba třídu pro odčítání:
Public Class Odcitani
Implements IMathDual
Public ReadOnly Property name() As String Implements PlugInterf.IMathDual.name
Get
Return "Odčítání"
End Get
End Property
Public Function Operace(ByVal x As Single, ByVal y As Single, ByRef vysledek As Double) As String Implements
PlugInterf.IMathDual.Operace
vysledek = x - y
Return ""
End Function
Public ReadOnly Property znacka() As String Implements PlugInterf.IMathDual.znacka
Get
Return "-"
End Get
End Property
End Class
16) Opět si můžeme celý projekt sestavit, aby se nám vytvořila naše první "zásuvná" knihovna 17) A nyní nastal čas tvorby vlastního kalkulátoru: 18) V solution exploreru opět zadáme add-> new Project, vybereme Windows Application a nazveme ji třeba Kalkulator 19) Formulář si upravíme na rozměr 500 x 300, umístíme na něj: - Tebtbox, nastavíme u něj multiline na true, rozměr 108 x 242 a umístění na pozici 12;12 (hodnoty jsou, samozřejmě přibližné - takto se mi to samo chytlo okrajů. Rozměry udávám pouze proto, že nemám možnost ukázat, jak by to mělo vypadat a ať to máme alespoň přibližně stejné). Název nastavte třeba na TbxVypis. Anchor nastavte na Top,Left,Bottom - druhý textbox s názvem TbxDisplay, u něj nastavte trochu výraznější font (já nastavil Size na 20 a vlastnost Bold na True, vložte počáteční text 0 (nula) a polohu někde vpravo nahoře (já mám Location 126;12 a rozměr 354x38) Anchor nastavte Top,Left,Right (aby nám to nautíkalo). - ke spodní hraně formuláře si do pravého rohu přidejte dvě tlačítka standardní velikosti (tak, jak Vám to samy budou chytat zarovnávátka k okrajům), jedno nazvěte třeba BtnClear a napište na něj CLS, druhé nazvěte BtnRovno a napište na něj "=". Pro obě tlačítka si nastavte Anchor na Bottom, Right - jako poslední si na formulář přidejte Panel, kterým vyplňte (opět jak Vám to zarovnávátka zachytí) zbylý prostor mezi dvěma textboxy nahoře a vlevo a tlačítky dole. Anchor nastavte na všechny 4 směry a název panelu nechte klidně standardní. Ten panel je pouze pomocný - sem budeme umisťovat tlačítka jednotlivých funkcí). No a můžeme přejít k tvorbě kódu: 20) Protože budeme opět potřebovat pracovat s naším rozhranním, stejně jako v minulém projektu, přidáme si i do tohoto projektu referenci na náš PlugInterf, včetně Imports PlugInterf na začátku kódu. 21) Začátek kódu by mohl vypadat následovně:
Imports System.IO
Imports System.Reflection
Imports PlugInterf
Public Class Form1
'Seznam všech dostupných zásuvných modulů
Private operacePrim As New List(Of IMathPrim)
Private operaceDual As New List(Of IMathDual)
Private registr As Single = 0
Private operaceId As Integer = 0
Jak již bylo zmíněno, nejprve si naimportujeme potřebné jmenné prostory a poté si nadeklarujeme globální proměnné. Dále budeme potřebovat nějaké kontejnery, ve kterých budeme uchovávat odkazy na nalezené matematické operace. Na rozdíl od příkladu pana Linharta jsem ty proměnné (jako silně typované kolekce) nadefinoval dvě - pro operace zpracovávající dva vstupní operandy a pro operace pracující pouze s jedním vstupem (každá skupina má své vlastní rozhranní, protože obsahuje jinak volanou funkci). Dále jsem si nadeklaroval dvě proměnné jako zásobníky pro zapamatování zvolené operace a hodnoty prvního operandu Nyní ošetříme událost Load formuláře. V této události se nejprve pokusíme najít zásuvné moduly, které jsou k dispozici a poté doplníme uživatelské rozhranní (pro každou operaci přidáme na formulář odpovídající tlačítko):
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
nactiPluginy()
doplnFormular()
End Sub
A tady jsou jednotlivé metody:
Private Sub nactiPluginy()
'Prohledá všechny dll soubory ve složce aplikace
For Each file As FileInfo In New DirectoryInfo(My.Application.Info.DirectoryPath).GetFiles("*.dll",
SearchOption.TopDirectoryOnly)
Dim pluginAssembly As Assembly = Nothing
Try
'Pokusí se načíst knihovnu
pluginAssembly = Assembly.LoadFrom(file.FullName)
Catch ex As Exception
'Při načítání knihovny došlo k chybě (např. nesprávný formát knihovny)
End Try
If pluginAssembly IsNot Nothing Then
'Prohledá všechny typy v knihovně
For Each t As Type In pluginAssembly.GetTypes()
'Pokud typ implementuje rozhraní zásuvného modulu (definovaného v projektu PluginInterf)
'vytvoří instanci tohoto typu a přidá jej do seznamu zásuvných modulů
'nejprve pro IMathPrim
If t.GetInterface(GetType(IMathPrim).FullName) IsNot Nothing Then
Dim plugin As IMathPrim = DirectCast(pluginAssembly.CreateInstance(t.FullName), IMathPrim)
operacePrim.Add(plugin)
' Poté pro IMathDual
ElseIf t.GetInterface(GetType(IMathDual).FullName) IsNot Nothing Then
Dim plugin As IMathDual = DirectCast(pluginAssembly.CreateInstance(t.FullName), IMathDual)
operaceDual.Add(plugin)
End If
Next
End If
Next
End Sub
Metodu pro načtení Plug-inů jsem si, s laskavým svolením (nebo vlastně bez něj) půjčil od pana Linharta (stále jenom rozpracovávám jeho příklad, aby bylo patrné, jak by to bylo možné (celkem smysluplně) využít prakticky), a udělal jsem v něm pár úprav: - vyhodil jsem kontrolní výpisy (ty mě teď nezajímají) a za druhé jsem nalezené typy zkoumal na implementaci obou mých rozhranní. Po doběhnutí této metody již mám všechny nalezené plug-in třídy uložené v mých dvou kolekcích. Metoda pro doplnění formuláře:
Private Sub doplnFormular()
' duální operace
Dim rozmerX = Panel1.Width / 5
Dim pocatekY = 0
For i As Integer = 0 To operaceDual.Count - 1
Dim tlacitko As New Button
With tlacitko
.Left = (i Mod 5) * rozmerX
.Width = rozmerX - 5
.Top = Math.Floor(i / 5) * 30
.Height = 23
.Text = operaceDual(i).znacka
.Tag = i
End With
Panel1.Controls.Add(tlacitko)
AddHandler tlacitko.Click, AddressOf osetriDual
Next
' primární operace
pocatekY = Panel1.Height / 2
For i As Integer = 0 To operacePrim.Count - 1
Dim tlacitko As New Button
With tlacitko
.Left = (i Mod 5) * rozmerX
.Width = rozmerX - 5
.Top = pocatekY + Math.Floor(i / 5) * 30
.Height = 23
.Text = operacePrim(i).znacka
.Tag = i
End With
Panel1.Controls.Add(tlacitko)
AddHandler tlacitko.Click, AddressOf osetriPrim
Next
End Sub
Pro každou nalezenou třídu (matematickou funkci) vytvořím tlačítko, nastavím jeho vlastnosti a polohu (duální funkce řadím vedle sebe od levého horního rohu mého pomocného Panelu1, tlačítka pro jednooperátorové funkce řadím do dolní poloviny téhož panelu). Do vlastnosti .tag každého tlačítka si uložím index konkrétní funkce v kolekci (pro pozdější jednodušší volání). Přidám handler na ošetření stisknutí tlačítka a tlačítko přidám do kolekce Controlls daného panelu. Toto celé provedu jak pro všechny nalezené funkce v kolekci operaceDual, tak i pro všechny v operacePrim. No a Toť takřka celé, zbývá již jen ošetřit stisknutí funkčních tlačítek:
Private Sub osetriDual(ByVal sender As Object, ByVal e As System.EventArgs)
operaceId = CType(sender, Button).Tag
registr = CSng(TbxDisplay.Text)
'zapíšeme do výpisu
TbxVypis.Text &= registr & vbCrLf
TbxVypis.Text &= operaceDual(operaceId).znacka & vbCrLf
End Sub
Stisknutí tlačítka duální operace nedělá nic moc, protože vlastní výpočet je prováděn až po zadání druhého operandu a stisknutí tlačítka rovná se. Pouze tedy provedeme nějaké kontrolní zápisy a "zapamatujeme" si první operand a Id pluginu odpovídajícímu stisknutému tlačítku. Vlastní výpočet proběhne až po stisknutí tlačítka BtnRovno (jedno ze dvou, které jsme na form přidali sami):
Private Sub BtnRovno_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnRovno.Click
Dim vysledek As Double
Dim resultat As String = operaceDual(operaceId).Operace(registr, CSng(TbxDisplay.Text), vysledek)
TbxVypis.Text &= TbxDisplay.Text & vbCrLf & "=" & vbCrLf
If resultat = "" Then
TbxVypis.Text &= vysledek & vbCrLf
TbxDisplay.Text = vysledek
Else
TbxVypis.Text &= resultat
TbxDisplay.Text = resultat
End If
TbxVypis.Text &= "============" & vbCrLf & vbCrLf
End Sub
Funkčnost je snad zřejmá. Ošetření tlačítka pro jednooperátorové funkce udělá vše najednou:
Private Sub osetriprim(ByVal sender As Object, ByVal e As System.EventArgs)
operaceId = CType(sender, Button).Tag
Dim vysledek As Double
Dim resultat As String = operacePrim(operaceId).Operace(CSng(TbxDisplay.Text), vysledek)
'zapíšeme do výpisu
TbxVypis.Text &= TbxDisplay.Text & vbCrLf
TbxVypis.Text &= operacePrim(operaceId).znacka & vbCrLf
TbxVypis.Text &= "=" & vbCrLf
If resultat = "" Then
TbxVypis.Text &= vysledek & vbCrLf
TbxDisplay.Text = vysledek
Else
TbxVypis.Text &= resultat
TbxDisplay.Text = resultat
End If
TbxVypis.Text &= "============" & vbCrLf & vbCrLf
End Sub
A aby byla kalkulačka hotová, zbývá nám toliko ošetřit stisknutí druhého tlačítka, které jsme si na form dali sami a které tento form vyčistí:
Private Sub BtnClear_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnClear.Click
registr = 0
operaceId = 0
TbxVypis.Text = ""
TbxDisplay.Text = "0"
End Sub
Toť celé. Pokud jsem tam nenasázel moc chyb, tak můžete projekt zkompilovat a spustit, a... ...a vidíte, že máte prázdnou kalkulačku, bez jakýchkoliv funkcí. Ale to je zcela vpořádku, protože jsme programu nedali k dispozici žádný plug-in. Proto vypněte kalkulačku, a do adresáře s jejím spustitelným souborem nakopírujte knihovnu s našimi dvěma pluginy - Matika01.dll. A spusťte kalkulačku znovu - a pokud nedošlo k nějaké chybě, už byste na ní měli mít první 2 funkční tlačítka. Chvíli si to otestujte a pak to celé zavřete (včetně visual Basicu). No a aby byl smysl Zásuvných modulů ještě zřejmější, pohrajeme si teď na "externího vývojáře". Spusťte si VB (nebo VS), a vytvořte si zcela nový projekt typu Class Library. Jako jméno dejte třeba Matika02 a do názvu Solution si dejte třeba Pluginy. A dále už to znáte: V Solution Exploreru si přejmenujte Class1.vb na Matika2.vb, do referencí si přidejte naši známou PlugInterf.dll, a do kódu úplně nahoru naimportujte jmenný prostor našich plugin referencí:
Imports PlugInterf
A zkusíme si tentokrát udělat něco pro jeden operátor, třeba druhou odmocninu (abychom si ukázali i chybová hlášení). Změňte si název třídy a naimplementujte si potřebné rozhranní:
Imports PlugInterf
Public Class DruhaOdmocnina
Implements IMathPrim
Zase nám to vytvoří celou kostru třídy a my ji jen smysluplně doplníme. Třeba takto:
Imports PlugInterf
Public Class DruhaOdmocnina
Implements IMathPrim
Public ReadOnly Property name() As String Implements PlugInterf.IMathPrim.name
Get
Return "Druhá odmocnina"
End Get
End Property
Public Function Operace(ByVal x As Single, ByRef vysledek As Double) As String Implements
PlugInterf.IMathPrim.Operace
vysledek = 0
If x < 0 Then Return "ODMOCNINA ZÁP. ČÍSLA!"
vysledek = Math.Sqrt(x)
Return ""
End Function
Public ReadOnly Property znacka() As String Implements PlugInterf.IMathPrim.znacka
Get
Return "x^½"
End Get
End Property
End Class
Vysvětlení snad netřeba. Samozřejmě si můžete do stejného souboru nasázet i další třídy pro další funkce (dokonce můžete v jednom souboru klidně kombinovat funkce pro dva operandy s funkcemi pro jeden - jenom musíte vždy použít správné rozhranní). Celý projekt si sestavte (menu->Build->Build matrika02). (nelekněte se, pokud Vám to vyhodí hlášku ohledně kódování - v označení funkce jsem použil znak 1/2, což je unicode, tak to klidně nechte systém překódovat). Opět můžete Visual basic ukončit a projekt uzavřít a... ... a to hlavní právě nastává. Najděte si (průzkumníkem) na disku právě vytvořenou knihovnu Matika02.dll (pokud jste používal názvy dle mé rady, tak ji naleznete v projektech v podadresáři Pluginy\Matika02\bin\Debug a překopírujte ji do adresáře, ve kterém máte zkompilovánu kalkulačku z minulého projektu (opět, pokud jste použil doporučené názvy, pak je to v kalkulator\Kalkulator\bin\Debug). No a v tomtéž adresáři byste měl nalézt i spouštěcí soubor celé kalkulačky (Kalkulator.exe), tak jej pokepáním spusťte. A aniž byste jakkoliv do vlastního programu kalkulátoru zasahoval, měl byste ho mít rozšířený o další funkci. A to je krása, smysl a oblast vhodného nasazení Plug-inů! Za domácí úkol si můžete vytvořit pár dalších zásuvných modulů a můžete je dát do diskuse k dispozici ostatním, stejně tak mohou pár (snad jiných :-) funkcí přidat i ostatní kolegové, kteří se v tom budou chtít pocvičit, no a tak uzříte další výhodu Plug-inů - mohou být vyvíjeny nezávisle na sobě v komunitě, čímž silně akcelerujete vývoj takovéhoto projektu!
|