DirectoryInfo aplauziens

Iedomājamies situāciju – rakstījām savu mūziciņu kompaktdiskā. CD rakstītājs (pieņemu, ka burnatonce) paziņo, ka šī failsistēma neatbalsta tik garus nosaukumus. Failu nosaukumi tiek apgriezti līdz zināmam garumam, piemēram, 52 simboliem. Dažādu sakritību rezultātā tieši 52. simbols direkotrijas nosaukumā ir atstarpe (0x20). Līdz ar to burnatonce veiksmīgi izveido direktoriju "d:\direktorijasnosaukums " (pievērst uzmanību atstarpes simbolam nosaukuma beigās).

Pēc tam darbinām šādu kodu:

Dim oDir As New IO.DirectoryInfo("d:\direktorijasnosaukums ")
oDir.GetDirectories()

… un, protams, nobeidzamies ar kļūdu, jo direktorija neesot atrodama. Ielūkojoties oDir objekta atribūtu sarakstā, redzams, ka FullName atribūtā ir pazudusi atstarpe nosaukuma beigās. Līdz ar to vairs neizdodas veikt nekādas darbības ar šo objektu.

Tā, lūk, ir ar .Net

VB.net klases atribūtu redzamība

VB.net klases atribūtu redzamība tiek definēta visam tipam, ne tipa instancei, kā mēs to varbūt sagaidām.
Tas ir, viena klases instance var mierīgi piekļūt citas klases instances privātajiem laukiem. Piemērs:

Class Test
 Private scValue As String
 Public ReadOnly Property Value() As String
    Get
    Return scValue
    End Get
 End Property

 Public Sub Export(ByRef opTest As Test)
    opTest.scValue = Me.scValue
 End Sub
End Class

Tas ir, mēs definējam klasi Test, kurai ir viens privāts lauks scValue. (Privāts = tikai pati klase drīkst mainīt atribūtu vērtības). Un vēl klasei ir ReadOnly atribūts Value, kas atgriež lauka scValue vērtību.

Bet patiesībā veiksmīgi izpildās funkcija export, kas pēc references saņem citu šīs pašas klases instanci un mierīgā garā uzstāda tās lauka vērtību. Jebkādas citas klases instance, protams, netiktu pie Test klases iekšējiem laukiem.

Tomēr – ar šo uzmanīgi! Var rasties problēmas.

Risinājumi un frameworki

Tikai viens citāts:

Nemēģini izstrādāt par kārtu sarežģītāku framework, kas risina tavu vienkāršo problēmu. Raksti vienkāršu algoritmu, tiek galā ar tavu sāpi. Jo tu nezini, kādas ir pārējās problēmas, kuras šim frameworkam vajadzētu aptvert.

Interesanta diskusija ar Anders Hejlsberg, valodas C# izstrādātāju. Diskusijā piedalās arī Bruce Eckel, kurš ir gŗamatu Thinking in C++ and Thinking in Java autors un arī Bill Venners no Artima.com

Listbox lietojumi

Iepriekšējā rakstā minēju, ka “esam mantojuši ListView klasi, atliek tikai to izmantot”. Te nu jautāums, kā to izdarīt.
MS Visual Studio 2003 gada versija pagaidām nepiedāvā iespeju, vismaz – ne acīmredzami. Ir iespēja uz formas novietot gatavas kontroles, ir iespēja gatavās kontroles “ievilkt iekš toolbox-a”, bet nav iespējas iestāstīt VStudio, ka šī tava klase ir patiesībā mantota no windows formām un ka tā ir vizuāla un ka tai ir design-time interfeiss kā jau kārtīgai ActiveX kontrolei.
Tāpēc pagaidām rīkojos tā: uz formas novietoju to kontroli, no kuras esmu mantojis savu klasi. Piemēram, ja man vajag uz formas uzlikt MyListView:Widnows.Forms.ListView, tad uz formas uzlieku pašu ListView elementu. Pēc tam atveram formu dizainera ģenerēto kodu.
Kaut kur tajā kodā atrodam rindu:

Friend WithEvents ListView1 As System.Windows.Forms.ListView
Šajā vietā aizvietojam “System.Windows.Forms.ListView” ar “MyListView”.
Pēc tam pameklējam nedaudz tālāk un atrodam
Me.ListView1 = New System.Windows.Forms.ListView
, kur veicam analoģisku aizvietojumu.
Tālākais arī nav gluži acīmredzams. Nospiežam Save. Aizveram formu dizaineri. Aizveram pašu formu. Uztaisām projektam “rebuild”. Atveram formu dizaineri. Vizuāli nekas nav mainījies (jo design-time interfeisu taču neesam mainījuši), bet kontroles tips ir no Windows.Forms.ListView pārvērties par MyListView, kas arī bija vajadzīgs.
Jaunajā VStudijas versijā ir paredzēta vienkāršota kontroļu tipu maiņa, kur šādas izmaiņas būs veicamas ar vienu klikšķi. Viens “+” punkts jaunajai studijai.

Kārtošana un IComparer

Mazliet par elementu kārtošanu. Nē, šis nebūs par ātriem kārtošanas algoritmiem, bet par saraksta sakārtošanu pēc paša noteiktas funkcijas.
Reizēm gadās situācija, ka ir dota Windows forma, uz kuras ListView kontrole. Kontrolē iekšā kaut kāds saraksts. Jānis Bērziņš, Pēteris Vītoliņš un visi pārējie. Vai arī “01.05.2004”, “03.02.2003” un citi. Kaut kādu ierakstu saraksts. Parasti gribas šādu sarakstu redzēt sakārtotu. Piemēram, augošā secībā pēc datumiem.
Tā tas gadījās arī man, vajadzēja sakārtot šādus Latvijā tik pierastā veidā noformatētus datumus (dd.MM.yyyy) augošā secībā. Skaidrs ir tas, ka, ja vienkārši ieslēgsim “Sort ascending” ListView kontrolei, datumi tiks sakārtoti apmēram šādā secībā – 01.01.2003; 01.02.2003; 02.01.2003, kas, protams, neatbilst mūsu vēlmei.
Ko darīt? Iejaukties elementu kārtošanas funkcijā.
Ja šādai kontrolei (kā ListView) ir ieslēgts Sort (asc/desc), tad pēc katra elementa pievienošanas tā sevī iekšienē izsauc Sort() funkciju. Sort funkcija pēc kaut kāda (mums nezināma) algoritma sakārto elementus pieprasītajā secībā. Taču skaidrs ir viens, ka neatkarīgi no izvēlētā kārtošanas algoritma (shell sort, quicksort, heapsort), Sort funkcijai būs nepieciešams salīdzināt divus elementus – “kas lielāks, zirgs vai 4nieks?”. Tātad, lai veidotu savu sakārtojumu ListView kontrolē, atliekt tikai piespiest tās kārtotāju izmantot kādu programmētāja definētu salīdzināšanas operāciju.
Šī vajadzība .NET arhitektūrā ir risināta ar IComparer interfeisa palīdzību. Interfeiss pieprasa implementēt vienu funkciju – Compare(x,y), kas atgriež negatīvu vērtību, ja x<y, pozitīvu vērtīby, ja x>y vai 0, ja x=y.
Jāatceras, ka Compare() tiks izsaukts tiem elementiem, ko mēģinām salīdzināt, nevis to labeliem, piemēram. Tas ir, ListView gadījumā mums jāveido funkcija, kas māk salīdzināt objektus ar tipu ListViewItem.
Izveidoju šādu klasi:

Public Class SimpleComparer
  Implements IComparer
  Public Function Compare(ByVal x As Object, ByVal y As Object) _
        As Integer
    Implements System.Collections.IComparer.Compare
    Dim s1 As String = CType(x, ListViewItem).SubItems(0).Text
    Dim s2 As String = CType(y, ListViewItem).SubItems(0).Text
    Dim d1 As Date = DateSerial( _
        CInt(Mid(s1, 7)), CInt(Mid(s1, 4, 2)), CInt(Mid(s1, 1, 2)))
    Dim d2 As Date = DateSerial( _
        CInt(Mid(s2, 7)), CInt(Mid(s2, 4, 2)), CInt(Mid(s2, 1, 2)))
    Return Date.Compare(d1, d2)
  End Function
End Class
Šeit SubItems(0) tiek lietots, lai norādītu kolonnu, pēc kuras jāsalīdzina. Kā zināms, ListViewItem var saturēt vairākas datu kolonnas – piemēram, to bieži var redzēt Windows Explorer logā, kad ieslēgts “Detail view”.
Tātad tagad mums ir savs salīdzinātājs. Diemžēl neviens to neizmanto : ( Ko darīt – rakstīt savu ListView klasi (mantotu no Windows.Forms.ListView), kas izmanto tieši šo salīdzinātāju. To darīju šādi:
Public Class MyListView
  Inherits Windows.Forms.ListView
  Private ocComparer As SimpleComparer
   Public Sub New()
    ocComparer = New SimpleComparer
    Me.ListViewItemSorter = ocComparer
    Me.Sorting = SortOrder.Ascending
   End Sub
End Class

Tagad ir izveidota ListView klase, kas elementu salīdzināšanai izmanto savu salīdzināšanas funkciju. Atliek vien to ielikt kādā savā formā un lietot uz nebēdu.

CLR un COM3

Zināt, kāpēc produkts saucas CLR (The Common Language Runtime, kas izpilda jūsu .net aplikācijas), nevis COM3, kā tam bija patiesībā jāsaucas? Tikai tāpēc, ka ir vairākas Windows versijas, kurās nav iespējams uztaisīt direktoriju ar nosaukumu COM3 (traucē porta nosaukums, kurš ar to sakrīt).

Threadi

Mazliet par pavedieniem (threads) un VB.NET. Kopš .net CLR un CIL parādīšanās pavedieni vairs nav tikai "izvirtība priekš C++ programmētājiem" — tā ir arī ērti pieejama izvirtība VB programmētājiem.
Pirmkārt — kāpēc gan niekoties ar pavedieniem, ja viss tāpat jau darbojas? Viens no vienkāršākajiem pielietojumiem — ir vajadzīga aplikācija ar lietotāja interfeisu, kas paredzēta nepacietīgam cilvēkam, kuram neder, ka ineterfeiss "bremzē", bet ir nepieciešams veikt darbības, kuras aizņem daudz laika, kuras turklāt nav pārtraucamas. Piemēram, varam iedomāties aplikāciju, kas pār HTTP savienojumu ielādē ierakstus kādā listbox vai tamlīdzīgā kontrolē (kaut vai e-pasta vēstules, kas pa vienai parādās sarakstā). Kamēr dati lādējas, lietotājam varētu ļaut darīt arī kaut ko citu (piemēram, pakustināt formu).
Iedomājamies, ka ir nepieciešams ielādēt datus no HTTP, bet tajā laikā GUI jābūt brīvi pieejamam.
Datus varam dabūt ar šādu metodi

Public Function GetIt()
  Dim o As System.Net.HttpWebRequest
  o = System.Net.WebRequest.Create("http://www.asdf.lv/")
  Dim sText As String = New System.IO.StreamReader( _
    o.GetResponse.GetResponseStream()).ReadToEnd
  return sText
End Function
Skaidrs ir tas, ka šī metode izpildīsies "uzreiz", tātad, ja savienojums būs lēns, UI tajā laikā neatjaunosies.
Ko darīt. Izveidojam jaunu "darbinieka" klasi, kas veiks vajadzīgo darbu:
Public Class loader
  Public Event DataLoaded(ByVal result As String)
  Public Sub GetIt()
    Dim o As System.Net.HttpWebRequest
    o = System.Net.WebRequest.Create("http://www.asdf.lv/")
    Dim sText As String = New System.IO.StreamReader( _
    o.GetResponse.GetResponseStream()).ReadToEnd
    RaiseEvent DataLoaded(sText)
  End Sub
End Class
Šī klase definē arī savu notikumu (event) — DataLoaded. Notikums iestāsies tad, kad dati būs ielādēti. Notikums arī palīdzēs nogādāt ielādētos datus no izpildītāja pie "īpašnieka".
Ko darīt īpašniekam? Pieņemam, ka īpašnieks ir Windows forma, kurā nepieciešams ielādēt datus. Formai pirmkārt nepieciešama privāta izpildītāja klases instance.
Public WithEvents loaders As loader
Šo instanci nevaram deklarēt tās metodes ķermenī, kura izsauks strādātājpavedienu, jo mēs taču ceram, ka metode izsauks ielādes procedūru un turpinās darbu. Tātad, iespējams, beigsies pirms strādātājpavediena metodes.
Vēl nepieciešams deklarēt pašu pavedienu. To var izdarīt ar sekojošu rindu:
Public myThread As System.Threading.Thread
. Nu, kas tad vēl atliek… pavediena inicializācija un datu ķeršana. Šeit arī kopējais formas kods (izņemot kontroļu ģenerācijas kodu)
Public Class Forma
  Public WithEvents loaders As loader
  Public myThread As System.Threading.Thread
  Private Sub Button1Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
    Handles Button1.Click
    loaders = New loader()
    myThread = New System.Threading.Thread(AddressOf loaders.GetIt)
    myThread.Start() `sākas patiesā ielāde
  End Sub

  Private Sub loaders
a(ByVal result As String) _
     Handles loaders.DataLoaded
    Me.TextBox1.Text = result
  End Sub
End Class

Šeit arī redzams, ka, nospiežot pogu button1 tiek izveidota jauna pavediena instance, kurai tiek nodota izpildāmās metodes adrese. Šajā gadījumā tiek izmantota klases instances metode, bet tikpat labi metode varēja būt arī statiska.
Pēc tam pavediens tiek palaists darbā (un forma aizmirst par to, ka tas darbojas).
Vēlāk tiek apstrādāts notikums, ko radījusi ielādes klase – dati ir ielādēti.

Tālāk nepārmantosim vis

Rakstot VB.Net (vai arī jebkurā citā citā OO programmēšanas valodā, izvēloties attiecīgus programmēšanas līdzekļus), ir vērts klasēm, par kuru “pareizību” neesi pilnībā pārliecināts, pievienot NotInheritable atslēgas vārdu. Tas nodrošinās to, ka klase nebūs mantojama. Savukārt citiem tavas klašu bibliotēkas lietotājiem būs vieglāk saprast, kuras klases ir izmantojamas par bāzes klasēm viņu aplikācijās.

VB.net koda izskaistināšana

Mazliet par VB.Net un glītāku kodu.
Ja jāveido klase, kurai ir daudz atribūtu (property) un tiem visiem nepieciešamas set un get metodes, tad izvedojas milzīgi garš kods, kurš sastāv gandrīz tikai no

Public Property Name() As String
   Get
    Name = scName
   End Get
   Set(ByVal Value As String)
    scName = Value
   End Set
End Property
Un tā daudzas reizes. Šis kods gandrīz nekad nav jāmaina un nav arī nepieciešams to bieži apskatīt. VB.Net development IDE piedāvā iespēju “collapsēt” katra atsevišķa property kodu. Bet diemžēl IDE šo informāciju nesaglabā un, nākamo reizi atverot šīs pašas klases koda failu, atkal ieraugām gāaaaru palagu ar atribūtiem. Toties, ja izvedo atsevišķu reģionu ar atribūtiem, tad tā stāvoklis saglabājas arī pie faila aizvēršanas. Attiecīgi kodā rakstām
#Region "Klases properties"
Public Property Name() As String
   Get
    Name = scName
   End Get
   Set(ByVal Value As String)
    scName = Value
   End Set
End Property
#End Region

vb.? un xml

Lai nerastos iespaids, ka VB draudzējas ar XML standartu tikai sākot ar VB7(VB.net), pastāstīšu, ka arī VB6 ir ļoti labi māk saprasties ar XML lietām. Jā, arī Excel VBA, Access VBA un viss pārējais.
Ja pieliekam projekta referencēs, piemēram, bibliotēku “Microsoft XML, version 2.0”, tad pieejams gandrīz tas pats iespēju klāsts, kas VB.net. Piemēram, vienkārša XML string ielasīšana.

Dim oXMLDoc As New DOMDocument
Dim oXMLNodeList As IXMLDOMNodeList
Dim oXMLNode As IXMLDOMNode
Dim sTestString As String
sTestString = "<DzivniekuSaraksts>" & _
   "<dzivnieks tips = ‘Kaķis’ vecums = ‘1’ />" & _
   "<dzivnieks tips = ‘Suns’ vecums = ‘2’ />" & _
   "</DzivniekuSaraksts>"
oXMLDoc.loadXML (sTestString)
Set oXMLNodeList = oXMLDoc.getElementsByTagName("dzivnieks")
For Each oXMLNode In oXMLNodeList
 MsgBox (CStr(oXMLNode.Attributes.getNamedItem("tips").Text) & _
  vbCrLf & _
  CStr(oXMLNode.Attributes.getNamedItem("vecums").Text & vbCrLf))
Next
Tieši tāpat, kā VB.net, arī šeit pastāv iespēja dokumentu ielādēt, izmantojot URI shēmu. To nodrošina XMLHTTPRequest klase.