~~ CLASS (SINIF) MODULLER ~~

Class moduller hakkında herşeyi bu sayfada bulabilirsiniz...


VBA Projesi nedir? Excel dosyası ile entegre Visual Basic componentleridir. Bunlar UserForm, standart modül, class modüllerdir.
Üye Nedir? Componetler içinde bulunan her bir Function, Sub, Variable (değişken) ve Property üyedir.
Konumuz class yani sınıf modüller. Bu sınıflara standart modüllerden farklı olarak property ler yazılır. Bundan başka modifier olarak “Friend” bulunur.

MODİFİERLER:
Private : Geçerliliği sınıf düzeyindedir. Diğer sınıflardan görülemez. Kullanım üyeleri değişken, fonksiyon, prosedür ve özellik. Private üyeler Object Browser’ da görünmezler.
Public : Geçerliliği proje düzeyindedir. Public üyeler, standart modulde dış dünya (iki farklı VBA projesi) ve sınıflar arasında kolaylıkla haberleşebilir. Kullanım üyeleri değişken, fonksiyon, prosedür ve özellik. Public üyeler Object Browser’ da görülebilirler.
Friend : Geçerliliği proje düzeyindedir. Friend tanımlı üyeler yalnız sınıflar arasında haberleşebilir. Kullanım üyeleri fonksiyon, prosedür ve özellik. Friend üyeler değişkenlerde kullanılmaz ve Object Browser’ da görülemezler. (VBA parolası verilmiş ise) Friend tanımlı üye, Public tanımlı üyeden 10 kat daha hızlı çalıştığı (yapılan testlerde) söylenmektedir.

ÜYELER:
Variable (değişken), Function ve Sub (yordam) ı zaten biliyorsunuz. Sadece Property (özellik) lere değinelim.
Bir sınıfın özelliklerine veri alış verişi “=” ile yapılır. Birden fazla argüman konulabilse de, bunun yerine prosedür veya fonksiyon kullanmanızı tavsiye ederim. Alttaki resim birden fazla argümanın nasıl kullanıldığını anlatıyor.
http://msdn.microsoft.com/en-us/libr...ice.12%29.aspx





Standart Property kullanımı aşağıdaki gibidir :
clsTestClass.Name = “Veri”
a = clsTestClass.Name


Özelliklerin 3 metodu vardır: Get/Let/Set
Get : Standart veri tipi veya nesne almak için,
Let : Yalnız standart veri tipi atamak için,
Set : Yalnız nesne atamak için kullanılırlar.


PROPERTY TÜRLERİ :

Read-Write Property : Hem yazılabilir, hem de okunabilir özellik tipidir. Aynı Property adı olmak şartıyla Get/Let veya Get/Set ile birlikte kullanılır.

Private m_Name As String

Public Property Get Name() As String
    Name = m_Name
End Property

Public Property Let Name(ByVal deg As String)
    m_Name = deg
End Property


Private m_ObjProp As Object

Public Property Get ObjProp() As Object
    Set ObjProp = m_ObjProp
End Property

Public Property Set Name(ByVal deg As Object)
    Set m_ObjProp = deg
End Property


Read-Only Property : Yalnız okunabilir özellik tipidir. Yalnız Get kullanılır. Haberleşme için Friend Property/Sub/Function kullanılır.

Private m_Name As String

Public Property Get ObjProp() As String
    Name = m_Name
End Property

Friend Property Let SetToVariable(ByVal deg As String)
    m_Name = deg
End Property


Private m_ObjProp As Object

Public Property Get ObjProp() As Object
    Set ObjProp = m_ObjProp
End Property

Friend Property Set SetToVariable(ByVal deg As Object)
    Set m_ObjProp = deg
End Property



Write-Only Property : Yalnız yazılabilir özellik tipidir. Yalnız Let/Set kullanılır. Haberleşme için Friend Property/Function kullanılır.

Private m_Name As Object

Public Property Let Name(ByVal deg As String) As String
    m_Name = deg
End Property

Friend Property Get ReadFromVariable() As String
    ReadFromVariable = m_Name
End Property


Private m_ObjProp As Object

Public Property Set Name(ByVal deg As Object) As Object
    Set m_ObjProp = deg
End Property

Friend Property Get ReadFromVariable() As Object
    Set ReadFromVariable = m_ObjProp
End Property



IMPLEMENTASYON :
Bir sınıfı, diğer sınıfa dahil ederek çalıştırma şeklinde tanımlayabiliriz. Bir Class modül projesinde birden fazla sınıf bulunur. Zaten tek bir sınıf olsa, fonksiyon ya da prosedür deposundan başka bir kullanım şekli olmazdı.
“Sınıf döndüren sınıf”, “WithEvents”, “Implements” şeklinde üç türlü kullanım vadır.

Sınıf Döndüren Sınıf :
Bunu bir örnekle açıklamak daha anlaşılır olacak. Örneğin, “TextBox1.Font.Name = “Arial” kodunu açalım.
“TextBox1” bir sınıftır. “Font”, “TextBox1” sınıfının bir özelliğidir (Property) ve başka bir sınıfı döndürür. Ve bu sınıfın özelliklerinden (Property) biri de “Name” dir.
Aynı kullanım mantığını biz de Class modül ile yapalım…


“clsTextBox1” Class modül :

Private m_Font As clsFont

Public Property Get Font() As clsFont
    Set Font = m_Font
End Property

Public Property Set Font(deg As clsFont)
    Set m_Font = deg
End Property

Private Sub Class_Initialize()
    Set m_Font = New clsFont
End Sub


“clsFont” Class modül :

Private m_Name As String

Public Property Get Name() As String
    Name = m_Name
End Property

Public Property Let Name(deg As String)
    m_Name = deg
End Property



“Module1” Standart modül :

Sub Test()
    Dim txt As New clsTextBox1
    
    txt.Font.Name = "Arial"
    
    MsgBox txt.Font.Name
End Sub



WithEvents :
“WithEvents”, aynı zamanda Class modüllerde “olay (Click, DblClick gibi)” oluşturma konusuna girdiği için bu konuyu da bu başlıkta anlatmakta sakınca görmedim.
Bir sınıf içindeki metot işlem yaparken, yapılan işlemle ilgili bilgileri event ile diğer sınıfa eş zamanlı gönderebilir. Event lar, sınıf modüllerinde General Declaretion kısmında Public olarak tanımlanırlar. Arguman kullanma zorunlu değildir.
Object Browser’ da bir olay (üye), şimşek ikonu ile kendini belli eder.
Event lar, “WithEvents” ile bir sınıf içinden çağırılabilir. Biliyoruz ki, UserForm da bir sınıftır.

Public Event deneme1()
Public Event deneme2(ByVal arg As String)
Public Event deneme3(ByVal arg1 As Long, ByVal arg2 As String, … )


Basit bir örnek :

“clsFiles” Class modulu:

' Olaylar...
Public Event ListFiles(ByVal FileName As String)
Public Event ErrorMessage(ByVal ErrNumber As Long, ByVal ErrMsg As String)
Public Event BeforeAction(ByVal Msg As String)
Public Event AfterAction(ByVal Msg As String)

Public Sub GetFiles(FolderPath As String, Optional FilePattern As String = "*.*")
    Dim d As String, s As String
    
    s = IIf(Right(FolderPath, 1) = "\", FolderPath, FolderPath & "\")
    
    d = Dir(s & FilePattern, vbArchive + vbHidden + vbNormal + vbReadOnly + vbSystem)
    
    RaiseEvent BeforeAction(FolderPath & " dizininde dosya araması başladı.")
    
    Do While d <> ""
        If d = "explorer.exe" Then ' Herhangi bir denetimle hata döndür :
            GoTo ErrHandler
        Else
            RaiseEvent ListFiles(d) ' Dosya ismini "olay" ımıza gönder
        End If
        d = Dir
    Loop
    
    RaiseEvent AfterAction(FolderPath & " dizininde dosya araması tamamlandı.")
    
    Exit Sub
    
ErrHandler:
    ' Form modulundeki olaya gönderilecekse :
    ' 1-)
    RaiseEvent ErrorMessage(123456, d & " dosyası içeriyor !") ' veya
    ' 2-)
    ' Gerçek bir hata durumunda
    'RaiseEvent ErrorMessage(Err.Number, Err.Description)
    
    '***************************************************************
    
    ' Form modulundeki "olay" ımıza göndermeden normal bir error penceresi çıkması için :
    ' 1-)
    'Err.Raise 123456, , "Özel Açıklama"
    ' 2-)
    'Err.Raise Err.Number, , Err.Description
End Sub



“UserForm1” modulu: (Bir adet ListBox, bir adet Buton ekleyin)

Private WithEvents fls As clsFiles

Private Sub CommandButton1_Click()
    Set fls = New clsFiles
    fls.GetFiles "c:\windows"
End Sub

Private Sub fls_AfterAction(ByVal Msg As String)
    ' Sınıf içinde Dir işlemi bittikten sonra buraya mesaj göndermiştik.
    Debug.Print Msg
End Sub

Private Sub fls_BeforeAction(ByVal Msg As String)
    ' Sınıf içinde Dir başlamadan önce buraya mesaj göndermiştik.
    Debug.Print Msg
End Sub

Private Sub fls_ErrorMessage(ByVal ErrNumber As Long, ByVal ErrMsg As String)
    ' Sınıf içindeki üyenin çalışması sırasında hata olursa veya
    ' başka bir denetimle burada yakalanacak.
    Debug.Print "Hata No="; ErrNumber; "; Hata İletisi="; ErrMsg
End Sub

Private Sub fls_ListFiles(ByVal FileName As String)
    ' Sınıf içindeki üye düzgün çalışırken verileri burada yakalanacak.
    ListBox1.AddItem FileName
End Sub


“Private WithEvents fls As clsFiles” yazıp/yapıştırıp “Enter” tuşundan sonra aşağıdaki resimdeki Combolar “fls” üyelerini getirecektir. Unutmayın; “clsFiles” sınıfında bir olay tanımlandıysa “fls” ismi sol comboda görünecektir.




Implements :
“Implements” e geçmeden önce “Implements “ in kaynağı olan “Interface” e göz atalım.
Interface (arayüz), DLL dosyaların dış dünyanın görmesi için kendi özelliklerini barındıran üye listesidir. Interface sınıfları, baş harfi “I” olarak adlandırılırlar. Bir DLL Projesinde sınıfın Implementasyona kapalı olduğu durumda Interface sınıfı ayriyetten eklenir. VBA projelerinde Interface sınıflarına ihtiyaç yoktur. Aşağıdaki örnek de bilgi amaçlıdır.


Basit bir örnek :

“IDeneme” Class modulu (Interface sınıfı) :

' Interface Sınıfı

Public Sub DenemeSub()
    ' Boş kalacak.
End Sub

Public Function DenemeFunction()
    ' Boş kalacak.
End Function

Public Property Get DenemeProperty() As Variant
    ' Boş kalacak.
End Property

Public Property Let DenemeProperty(ByVal vNewValue As Variant)
    ' Boş kalacak.
End Property


“Class1” Class modulu :

' Esas sınıfımız.
Implements IDeneme

Private Function IDeneme_DenemeFunction() As Variant
    ' Buraya kod yazılacak.
End Function

Private Property Let IDeneme_DenemeProperty(ByVal RHS As Variant)
    ' Buraya kod yazılacak.
End Property

Private Property Get IDeneme_DenemeProperty() As Variant
    ' Buraya kod yazılacak.
End Property

Private Sub IDeneme_DenemeSub()
    ' Buraya kod yazılacak.
End Sub


“Module1” standart modül :

' Buradan da esas sınıfımızı çağıracağız.
    
    Dim cls As IDeneme
    
    Set cls = New Class1 ' Yanlışlık yok; kullanım bu şekilde.
    
    ' Aşağıdaki gibi çağıracağız.
    cls.DenemeFunction
    cls.DenemeProperty = 10
    cls.DenemeSub
End Sub


Implements, Interface’ i kendi sınıfımıza dahil etme işlemidir. Referans olarak eklenen bir kütüphanenin “New” sözcüğüyle Instance olmuş versiyonudur. Aşağıda “Scripting.Dictionary” sınıfının Implementini görüyorsunuz.

“CustomDictionary” Class modulu :

Implements Scripting.Dictionary

Private m_objDic As Scripting.Dictionary

Private Sub Class_Initialize()
    Set m_objDic = New Scripting.Dictionary
End Sub

Private Sub Class_Terminate()
    Set m_objDic = Nothing
End Sub

Private Sub Dictionary_Add(Key As Variant, Item As Variant)
    m_objDic.Add Key, Item
End Sub

Private Property Let Dictionary_CompareMode(ByVal RHS As Scripting.CompareMethod)
    m_objDic.CompareMode = RHS
End Property

Private Property Get Dictionary_CompareMode() As Scripting.CompareMethod
    Dictionary_CompareMode = m_objDic.CompareMode
End Property

Private Property Get Dictionary_Count() As Long
    Dictionary_Count = m_objDic.Count
End Property

Private Function Dictionary_Exists(Key As Variant) As Boolean
    Dictionary_Exists = m_objDic.Exists(Key)
End Function

Private Property Get Dictionary_HashVal(Key As Variant) As Variant
    ' ???
End Property

Private Property Let Dictionary_Item(Key As Variant, RHS As Variant)
    m_objDic.Item(Key) = RHS
End Property

Private Property Get Dictionary_Item(Key As Variant) As Variant
    Dictionary_Item = m_objDic.Item(Key)
End Property

Private Property Set Dictionary_Item(Key As Variant, RHS As Variant)
    Set Dictionary_Item(Key) = RHS
End Property

Private Function Dictionary_Items() As Variant
    Set Dictionary_Items = m_objDic.Items
End Function

Private Property Let Dictionary_Key(Key As Variant, RHS As Variant)
    Dictionary_Key(Key) = RHS
End Property

Private Function Dictionary_Keys() As Variant
    Set Dictionary_Keys = m_objDic.Keys
End Function

Private Sub Dictionary_Remove(Key As Variant)
    m_objDic.Remove Key
End Sub

Private Sub Dictionary_RemoveAll()
    m_objDic.RemoveAll
End Sub


“Implements Scripting.Dictionary” yazıp/yapıştırıp “Enter” tuşladığınızda aşağıdaki resimdeki comboları görürsünüz. Eğer referansını eklediğiniz kitaplık Implementasyona açıksa sol comboda bu ismi görürsünüz. Eğer yoksa, bu kitaplığın baş harfi “I” olan Interface lerin neler olduğunu Object Browser dan görebilirsiniz. Ancak bunlar gizli olduğundan Object Browser üzerinde sağ klik ile “Show Hidden Members” işaretlemeniz gerekir.
Sağ taraftaki comboda bulunan tüm üyeleri teker teker seçip sınıfınıza dahil etmeniz gerekir (içine kod yazmayacaksanız bile). Zaten bir tanesini bile eklemeden çalıştırmak istediğinizde VBA editörü sizi uyaracaktır.



Fark ettiyseniz “Private” tanımlı bu üyeler nasıl çağırılıp çalıştırılacak? İşte “Implements” kelimesi bize bu imkanı veriyor.

“Module1” standart modül :

Sub Test()
    ' Kısaca buradaki Implements :
    ' cls isimli Scripting.Dictionary nin,
    ' New Instance yapılmış hali CustomDictionary dir.
    ' Diğer bir deyişle, CustomDictionary ile
    ' New Scripting.Dictionary yapıyoruz.
    
    Dim cls As Scripting.Dictionary
    
    Set cls = New CustomDictionary ' "cls", bir "New Scripting.Dictionary" dir.
    
    cls.Add "ad", "Zeki"
    cls.Add "soyad", "Gürsoy"
    
    Debug.Print "Eleman Sayısı="; cls.Count; "; Ad="; cls("ad"); "; SoyAd="; cls("soyad")
End Sub




KOLEKSİYONLAR (Collection) :
Bazen Implemetasyonu yapılan diğer sınıfı, farklı özellik değerler dizisi olarak görmek isteriz. Böyle durumlarda koleksiyonları kullanırız. Koleksiyonun her bir elemanı, diğer sınıfı farklı özellikleriyle barındırır. Bu tip sınıfları bir kütüphaneyi referans olarak eklediğinizde Object Browser’ da İngilizcedeki çoğul ekiyle adlandırıldığını görürsünüz. Örneğin: “File” ve “Files” sınıfları gibi.

Çoğul eki ile adlandırılan sınıf, çoğul eki almamış olan sınıf koleksiyonu içerir. Bu tür sınıfların şu üyeleri standarttır. Bunlar “Count” ve “Item” olurlar. Bazen “Items” adında “collection” döndüren üye (property) de olabilir. Bu üye, For-Each döngüsüne imkan vermesi için konulur. “Items” kullanıldığı durumlarda “Count” üyesine gerek kalmaz çünkü, bir “Collection” döndüren “Items” üyesi “Items.Count” şeklinde kaç adet olduğunu verebilir. Ancak For-Each döngüsü için “Items” üye bulundurmak zorunlu değildir. Bunun diğer yolu da “NewEnum” fonksiyonudur. Bu fonksiyonu ileride açıklayacağım.
Çoğul eki almamış diğer sınıf ise, standart veri tipi içeren özellikler içerir. Ayrıca başka bir sınıf koleksiyonu döndüren özellikler de bulunabilir.
Birbiriyle bağlantılı FileSystemObject, Folders, Folder, Files ve File sınıflarını örnek alalım.

FileSystemObject Sınıfı : “GetFolder” fonksiyonu bir “Folder” sınıfı döndürür.
Folder Sınıfı : “Name”, “Files”, “SubFolders” özellikleri içerir. “Name” String iken, “Files” özelliği bir “File” koleksiyonunu ve “SubFolders” ise bir “Folder” koleksiyonu döndürür.

Basit bir örnek:

”clsSheet” Class modulu :

Public Function GetRange(rng As Range) As clsCells
    Dim vCells As New clsCells, r As Range
    
    For Each r In rng
        vCells.AddToCol r.Value, r.Address(0, 0)
    Next
    
    Set GetRange = vCells
End Function


”clsCells” Class modulu :

Private col As Collection

Public Property Get Count() As Long
    Count = col.Count
End Property

Public Property Get Item(Key As Variant) As clsCell
    Set Item = col.Item(Key)
End Property

Public Property Get Items() As Collection
    ' For-Each döngüsü için kullanılır.
    Set Items = col
End Property

Friend Sub AddToCol(vValue As Variant, vAddress As String)
    Dim c As New clsCell
    
    c.SetToAddress = vAddress
    c.Value = vValue
    col.Add c, CStr(vAddress)
End Sub

Private Sub Class_Initialize()
    Set col = New Collection
End Sub

Private Sub Class_Terminate()
    Set col = Nothing
End Sub


”clsCell” Class modulu :

Private m_Value As Variant
Private m_Adress As String

Public Property Get Value() As Variant
    Value = m_Value
End Property

Public Property Let Value(ByVal vNewValue As Variant)
    m_Value = vNewValue
End Property

Public Property Get Address() As String
    ' Adres özelliği Read-Only dir.
    Address = m_Adress
End Property

Friend Property Let SetToAddress(ByVal vNewValue As String)
    ' Read-Only olan Address özelliğinde dış dünyanın veri alabilmesi
    ' için Friend olan SetToAddress ile sınıflar arasında haberleşme sağlıyoruz.
    m_Adress = vNewValue
End Property


Test için module1 kodu :

Sub Test()
    Dim csh As New clsSheet, cs As New clsCells
    
    Set cs = csh.GetRange(Range("a1:a10"))
    
    For i = 1 To cs.Count
        Debug.Print "Adres="; cs.Item(i).Address; "; Value="; cs.Item(i).Value
    Next
    
    ' veya
    
    Dim r As clsCell
    
    For Each r In cs.Items
        Debug.Print "Adres="; r.Address; "; Value="; r.Value
    Next
    
    ' veya
    
    Debug.Print cs.Item("a5").Value
End Sub


İkinci örnek de Form nesnelerinin olaylarını ortak kullanma üzerine olsun. Çözüme giden tekniklerin birden fazla olabileceğini görebilmeniz açısından aşağıda aynı işi gören dört farklı kullanım mevcut.

1. Algoritma : (Kendi geliştirdiğim ve önerdiğim. CallBack stili.)

“clsCheckBoxes” Class modulu :

Private WithEvents chkBox As MSForms.CheckBox
Public Event Click(obj As MSForms.CheckBox)
Private col As Collection

Private Sub chkBox_Click()
    RaiseEvent Click(chkBox)
End Sub

Friend Sub Add(frm As Object, objChkBox As MSForms.CheckBox)
    Set chkBox = objChkBox
    col.Add frm ' UserForm1
End Sub

Private Sub Class_Initialize()
    Set col = New Collection
End Sub

Private Sub Class_Terminate()
    Set col = Nothing
End Sub


“UserForm1” modulu : (Test için 3 adet CheckBox ekleyin ve clik’leyin)

Public WithEvents cls As clsCheckBoxes

Private Sub cls_Click(obj As MSForms.CheckBox)
    MsgBox "Benim Adım : " & obj.Name
End Sub

Private Sub UserForm_Activate()
    Dim c As MSForms.Control, NewForm As UserForm1
    
    For Each c In Me.Controls
        If TypeOf c Is MSForms.CheckBox Then
            ' cls değişkenine atamak üzere yeni UF ata. Bu nedenle form ekrana yüklendiği
            ' andaki olaya (Activate) kod yazıyoruz.
            ' New UserForm1 ile atama yaparken Initialize olayı altındaki kod çalışır; DİKKAT!!!
            Set NewForm = New UserForm1
            
            ' Yeni UF nin cls değişkenine yeni Class instance ata.
            Set NewForm.cls = New clsCheckBoxes
            
            NewForm.cls.Add NewForm, c ' Yeni formu ve kotrolü koleksiyona ata.
        End If
    Next
End Sub


2. Algoritma : (Koleksiyon Class modül içinde)

“clsCheckBoxes” Class modulu :

Public WithEvents chkBox As MSForms.CheckBox
Private col As Collection

Public Sub Add(CheckBoxObject As MSForms.CheckBox)
    Dim chkClass As New clsCheckBoxes
    
    Set chkClass.chkBox = CheckBoxObject
    col.Add chkClass
End Sub

Private Sub chkBox_Click()
    MsgBox "Benim Adım : " & chkBox.Name
End Sub

Private Sub Class_Initialize()
    Set col = New Collection
End Sub

Private Sub Class_Terminate()
    Set col = Nothing
End Sub


“UserForm1” modulu : (Test için 3 adet CheckBox ekleyin ve clik’leyin)

Private cls As clsCheckBoxes

Private Sub UserForm_Initialize()
    Dim c As MSForms.Control
    
    Set cls = New clsCheckBoxes
    
    For Each c In Me.Controls
        If TypeOf c Is MSForms.CheckBox Then
            cls.Add c
        End If
    Next
End Sub

Private Sub UserForm_Terminate()
    Set cls = Nothing
End Sub


3. Algoritma : (Koleksiyon Form modülü içinde)

“clsCheckBoxes” Class modulu :

Public WithEvents chkBox As MSForms.CheckBox

Private Sub chkBox_Click()
    MsgBox "Benim Adım : " & chkBox.Name
End Sub


“UserForm1” modulu : (Test için 3 adet CheckBox ekleyin ve clik’leyin)

Private col As Collection

Private Sub UserForm_Initialize()
    Dim ctrl As MSForms.Control
    Dim cls As clsCheckBoxes
    
    Set col = New Collection
    
    For Each ctrl In Me.Controls
        If TypeOf ctrl Is MSForms.CheckBox Then
            Set cls = New clsCheckBoxes
            Set cls.chkBox = ctrl
            col.Add cls
        End If
    Next
End Sub

Private Sub UserForm_Terminate()
    Set col = Nothing
End Sub


4. Algoritma : (Koleksiyon yerine bir nesne dizisi; yani bir Array)

“clsCheckBoxes” Class modulu :

Public WithEvents chkBox As MSForms.CheckBox

Private Sub chkBox_Click()
    MsgBox "Benim Adım : " & chkBox.Name
End Sub


“UserForm1” modulu : (Test için 3 adet CheckBox ekleyin ve clik’leyin)

Private m() As clsCheckBoxes

Private Sub UserForm_Initialize()
    Dim ctrl As MSForms.Control
    Dim cls As clsCheckBoxes, i As Long
    
    For Each ctrl In Me.Controls
        If TypeOf ctrl Is MSForms.CheckBox Then
            Set cls = New clsCheckBoxes
            Set cls.chkBox = ctrl
            i = i + 1
            ReDim Preserve m(i)
            Set m(i) = cls
        End If
    Next
End Sub



SINIF VE ÜYELERİNİN NİTELİKLERİNİ AYARLAMA :
VBA sınıflarına atanabilecek nitelikler aşağıdaki tablodadır. Bu nitelikler VBA editöründe değiştirilemez. Bunun için sınıflar önce bir klasöre Export yapılmalı, bu dosyalar notepad ile açılarak gerekli ilaveler aşağıdaki tabloya göre yapıldıktan sonra kaydedip tekrar VBA projesine “Import” yapılmalıdır. Bir prosedür veya property iki nitelik alabilir. Örneğin : Hem default member hem de description niteliği alabilir. Açıklama verirken dikkat edin; “\n” ifadesinin karşılığı yeni satır (NewLine) dır.



Default Member Atama :
Range(“A1”) bir nesnedir. Ancak varsayılan üye niteliği varsa aşağıdaki kullanımlarda default member’ e yazılır veya okunur. Bir default member, Object Browser penceresinde, ilgili üyenin ikonunun sol üst köşesinde küçük mavi nokta ile kendini belli eder.

a = Range(“A1”) veya a = Range(“A1”).Value
Range(“A1”) = a veya Range(“A1”).Value = a

Hazırladığımız sınıfı notepad ile açtğımızda default member olmasını istediğimiz üyenin içine yazarız. Kırmızı font ile yazılmış satırlar ilavelerdir.

Public Property Get Item(Key As Variant) As clsCell
    Attribute Item.VB_UserMemId = 0
    Attribute Item.VB_Description = "Bir clsCell nesnesi döndürür"
    Set Item = col.Item(Key)
End Property


Enumarator Atama :
Yukarıda, koleksiyon konusunda For-Each döngüsü için “Items” adlı üye kullanılabildiğinden bahsetmiştim. Aynı işi görecek ikinci yol da “Enumarator” kullanmaktır.

Şimdi “col” isminde collection değişkeni olduğunu varsayarak, notepad içinde olması gereken Enumarator fonksiyonu ile atanması gereken nitelik aşağıdaki gibi olmalıdır. Function yerine Read-Only Property olarak da yazılabilir.

Public Property Get NewEnum() As IUnknown
    Attribute NewEnum.VB_Description = "Bu özellik For-Each döngüsü imkanı verir."
    Attribute NewEnum.VB_UserMemId = -4
    Set NewEnum = col.[_NewEnum]
End Function


Sınıf Açıklaması Atama :
Bu, Object Browser’ da sınıfa ait üyelerin değil de, sol taraftaki sınıf isimlerine tıklandığında alt tarafta görünecek açıklamadır. "FileSystemObject" isminde bir sınıfı export yaptıktan sonra notepad ile göreceğiniz mevcut nitelikler ile, atanacak niteliğin kırmızı olduğu satır aşağıdadır.

VERSION 1.0 CLASS
BEGIN
    MultiUse = -1 'True
END
Attribute VB_Name = "FileSystemObject"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Attribute VB_Description = "Scripting Kütüphanesi\nFileSystemObject Sınıfı"



BİR EXCEL EKLENTİSİNİ DLL GİBİ KULLANMA :
Bir .xls, .xlsm, .xla, .xlam dosyasını referans kitaplığı olarak ekleyebilirsiniz. Bunun için references penceresinde “Browse” dediğinizde seçilecek dosya tipini aşağıdaki resimde gördüğünüz gibi değiştirmeniz gerekir.




Dosyayı seçtiğinizde, Excel menüsünde bir eklenti işaretlenip “Tamam” denildiğinde dosya nasıl fiziksel olarak açılıyorsa, VBA referansı olarak eklendiğinde de dosya fiziksel olarak açılacağı için dosyayı eklenti olarak kaydedip kullanmak uygun olur.

Ancak eklentiyi bir DLL gibi kullanabilmeniz için uymanız gereken kurallar vardır:

1-) Class modül özelliği “PublicNotCreatable” olmalıdır. “Private” olursa üyeleri kullanamaz/çağıramazsınız. Aynı zamanda “PublicNotCreatable” sınıfındaki üye, “Private” türündeki bir başka sınıftaki üyeye başvuruyorsa eklentiniz yine çalışmayacaktır.




2-) Eklentideki sınıfımız dış dünyadan çağırılırken VB sınıfları gibi “Creatable=True” olmadığından “New” sözcüğü ile kullanılamaz. Dış dünyadan aşağıdaki kullanımlar kabul edilmez.

Dim cls As VBAProject.Class1
Set cls = New VBAProject.Class1 ' Kabul edilmez
' veya
Dim cls As New VBAProject.Class1 ' Kabul edilmez
' veya
Set cls = CreateObject("VBAProject.Class1") ' Kabul edilmez


Dış dünyada “New” kullanmadan sınıfı kullanamıyoruz. Peki bu engeli nasıl aşacağız?

Hatırlayın; standart bir modüldeki “Public” üyeler dış dünya ile haberleşebiliyordu. Eklenti projemizdeki sınıf, yalnız ait olduğu projede “New” olarak tanımlanabildiğine göre, eklenti projesinde New Instance olarak başlatıp standart modüldeki Public üye yardımıyla dış dünyaya göndereceğiz.
Bunun için eklenti projemizde bir de standart modül ekliyoruz. Modulun adı da belirgin bir ad olsun. Örneğin: “Class_New_Instance” . İçine de “New” sözcüğüyle kullanma gereği duyduğumuz sınıfın New Instance’ ını döndüren fonksiyon (üye) yazıyoruz. Fonksiyonun adı da anımsatıcı bir ad olsun.

Örneğin:

Public Function New_Class1() As Class1
     Set New_Class1 = New Class1
End Function


Eğer eklenti projemiz çok daha kalabalık olsaydı örneğin 5 adet class modül, “New Instance” gereken sınıflar için ayrı ayrı fonksiyon yazmamız gerekirdi. Örneğin “Class1” ile birlikte “Class2” yi de çağıracaksak “Class2” için de fonksiyon yazmamış gerekir.

Public Function New_Class2() As Class2
     Set New_Class2 = New Class2
End Function


“New Intance” gerekmeyen sınıflar için fonksiyon yazılması gerekmez. Hatırlayın; “Scripting” kitaplığı oldukça kalabalık sınıfa sahipken, “New” ile Instance olacak sınıf sayısı yalnızca 3’ tür. Bunlar: Dictionary, Encoder ve FileSystemObject’ tir.

Artık dış dünya ile haberleşme sağlayacak fonksiyonumuz da hazır olduğuna göre, dış dünyadan eklentideki sınıfımızı çağırma işlemi aşağıdaki gibi olacaktır.

Dim cls As VBAProject.Class1

Set cls = New_Class1

cls.ProsedurAdi


3-) Eklenti projenizin adını mutlaka değiştirmeniz gerekir. Bu, yeni bir Excel uygulaması başlatıldığında varsayılan verdiği “VBAProject” ismi ile çakışmaması için gereklidir. Vereceğiniz isim aynı zamanda “References” penceresinde görünecek olan isimdir.

Project Name : References penceresinde ve Object Browser’ da kitaplık adı olarak görünecek isimdir.
Project Description : Object Browser’ da, en üstte bulunan açılır listeden kitaplığı seçer seçmez en altta görünecek açıklamadır.
Help File Name : Bir yardım dosyası (chm veya hlp) hazırladıysanız, tam yoluyla burada belirtiyorsunuz.




4-) Eklenti olarak kaydedeceğiniz dosyaya VBA parolası verin. Bu isteğinize bağlıdır ancak, gerek Object Browser’ da, gerekse dış dünyada sınıfı kullanırken (kod yazarken) “Friend” üyeler de görünür durumda olduğundan kalabalıktan dolayı bir yanlışlığa sebep olmaması açısından faydalıdır.

5-) İsteğe bağlı diğer düzenleme olarak, eklentiniz Object Browser’ da görüntülenirken “BuÇalışmaKitabı” (ThisWorkBook) ve mevcut çalışma sayfalarının (Sayfa1, Sayfa2…) birer sınıf olmasının doğal sonucu olarak bu nesneler de çıkacaktır. Yeni bir Excel başlattığınızda aşağıdaki gibi görünür.




“BuÇalışmaKitabı” nesnesini tamamen kaldırmak mümkün değildir ama çalışma sayfalarını tamamen kaldırabiliriz. Yalnız, az sonra anlatacağım koruma yöntemi için engeldir. Yine de kullanmak isterseniz aşağıdaki resimli adımları izleyin.

- Tek sayfa dışında tüm sayfaları sildikten sonra sayfa adı üzerinde sağ klik ile “Ekle” seçin.



- Gelen pencerede “MS Excel 4.0 Makrosu” nu seçerek “Tamam” deyin.



- Çalışma sayfaları sekmeleriniz aşağıdaki gibi görünecektir. “Sayfa1” isimli sayfayı silin.



- Artık Object Browser’ da yalnız “BuÇalışmaKitabı” (ThisWorkBook) kalacaktır. Sonradan ilave edeceğiniz Class modüllerin isim sıralamasında aralarda kalmaması, en üstte olması için örneğin şunun gibi isim verebilirsiniz: “a_BuÇalışmaKitabı”



6-) Dosyanızı eklenti olarak kaydedin.


Kod Gizliliği?
Milyon dolarlık sermayeli yazılım şirketlerinin programları bile günümüzde kırılabiliyorken bir Excel eklentisinin gizliliği için konulabilecek engellerden çok fazla bir şey bekleyemeyiz. Ancak caydırıcı engeller konulabilir. Tahminimce aşağıda belirttiğim program ile eklentilerinizin gizlilik gücü %80 dir. Bu nedenle projenizi dağıtırken bunu dikkate almanız gerekir. Kullanım kısıtlamasını esas dosyanız yerine eklenti içinde kullanın.

Neden böyle bir eklenti kullanalım?

XLAM SİHİRBAZI:
Bu, kendi yazdığım bir programdır (Program ikonu yanıltmasın; bu bir EXE, yani bir uygulamadır). DLL gibi kullanılacak Excel dosyanızı (xlsm), korumalı bir eklentiye (xlam) şu an için bildiğim en iyi yöntemle çevirmenize yardımcı olur.



Kullanım gayet basittir. Kaynak Excel dosyasını seçiyorsunuz, hedef xlam dosya adını da verip “Eklentiyi Hazırla” butonunu kullanıyorsunuz. Program çalışırken kaynak dosyanız çok kısa bir süreliğine açılır (işlemin yapılması için açılmak zorunda) ve işlem bittiğinde kapatılarak mesaj ile bilgilenirsiniz.

Programı buradan indirebilirsiniz:
XLAM_Sihirbazi.exe

Aşağıdaki resimde bu metotla korunmuş eklentinin üyelerini görüyorsunuz.
Eklentiyi buradan indirin:
AddinDLL.xlam

Eklentinin kaynak projesini buradan indirin:
Class_DLL_Project.xlsm






Kullanım örnekleri aşağıdadır.

Sub Test1()
    Dim fso As FileSystemObject
    Dim fold As Folder, folds As Folders, fil As File, fils As Files
    
    Set fso = New_FileSystemObject
    
    Set fold = fso.GetFolder("c:\windows")
    
    Debug.Print "[GetFolder Özellikleri]"
    
    Debug.Print _
        "IsRootFolder="; fold.IsRootFolder; "; Name="; fold.Name; _
        "; ParentFolder="; fold.ParentFolder; "; Path="; fold.Path
    
    Set folds = fold.SubFolders
    Set fils = fold.Files
    
    Debug.Print ""
    
    Debug.Print "[SubFolders Koleksiyonu]"
    
    For Each fold In folds
        Debug.Print _
            "IsRootFolder="; fold.IsRootFolder; "; Name="; fold.Name; _
            "; ParentFolder="; fold.ParentFolder; "; Path="; fold.Path
    Next
    
    Debug.Print ""
    
    Debug.Print "[Files Koleksiyonu]"
    
    For Each fil In fils
        Debug.Print "Name="; fil.Name; "; Path="; fil.Path; "; Size="; fil.Size; "Bayt"
    Next
End Sub


Sub Test2()
    Dim fso As FileSystemObject, fold As Folder
    
    Set fso = New_FileSystemObject
    
    Set fold = fso.GetFolder("c:\").SubFolders.Add("Test")
    
    Debug.Print fold.Path & " klasörü oluşturuldu. "
    
    RmDir fold.Path
    
    Debug.Print fold.Path & " klasörü silindi."

End Sub



** Excelde “FileSearch” sınıfı gizli olarak yine mevcuttur. Ancak bu sınıfı kullanamazsınız. İsim çakışmasından dolayı aşağıda proje adıyla birlikte kullanılmıştır.

Sub Test3()
    Dim fs As ZGRSY.FileSearch
    
    Set fs = ZGRSY.New_FileSearch
    
    With fs ' veya With ZGRSY.New_FileSearch
        .Filename = "*.xlsm"
        .LookIn = "c:\"
        .SearchSubFolders = False
        .Execute
        
        For i = 1 To .FoundFiles.Count
            Debug.Print .FoundFiles(i)
        Next
    End With

End Sub



GELİŞTİRİCİLER İÇİN FAYDALI BİLGİLER :

Office 2010 ile gelenler :

VBA7 : Office 2010 derleyici versiyon sabitidir. Öncekiler VBA6 idi.
Win64 : 64 bit Office sabitidir.
Win32 : 32 bit Office sabitidir.
PtrSafe : 64 bit Office sisteminde API deklarasyonunda kullanılan sözcüktür.
LongPtr : Hem 32 bit, hem de 64 bit Office sisteminde API fonksiyonlarında Handle ve Pointer için getirilen yeni “Long” tipidir.
LongLong : Yalnız 64 bit Office sisteminde API fonksiyonlarında Handle ve Pointer için getirilen yeni “Long” tipidir.
CLngPtr : LongPtr tipine dönüştürür.
CLngLng : LongLong tipine dönüştürür.


Direktifler (#If...#ElseIF…#Else…#EndIf) :

#If Win64 Then
    ' Win64=true, Win32=true, Win16= false dir.
#ElseIf Win32 Then
    ' Win32=true, Win16=false dir.
#Else
    ' Win16=true dur.
#End If


Direktiflerde doğru bir sınama için “yeniden eskiye” kuralına uymanız gerekir. Örneğin; Win64, Win32 den önce sınanmalıdır. Keza VBA7 de, VBA6 dan önce sınanmalıdır. Win64 ve Win32, 64 bit Office versiyonunda “True” değeri döndürecektir.

Şimdi, Win64 ve Win32 sabitleri 64 bit sistemde “True” ise, derleyici bunu nasıl seçiyor?

Anladığım kadarıyla direktifler “Select Case” gibi çalışıyor. Hatırlayın; Select Case yapısında ilk doğru koşul bulunduğunda sonraki doğru koşullara bakılmaksızın Select Case bloğundan çıkılıyordu. “Yeniden eskiye” sırasında yazılma sebebi budur. Buna göre API deklarasyonu için en iyi kontrol direktif bloğu aşağıdaki gibi olmalıdır :

#If VBA7 Then ' Office 2010 derleyicisi ise
    #If Win64 Then ' 64 bit Office ise
    
    #Else
    
    #End If
#Else

#End If


Office 2010 32 bit versiyonunda API tanımları önceki versiyonlar gibi tanımlandığı için aşağıdaki gibi kısaltabiliriz.

#If VBA7 Then ' Office 2010 derleyicisi ise
    #If Win64 Then ' 64 bit Office ise
    
    #End If
#Else

#End If


Veya,

#If VBA7 And Win64 Then ' Office 2010 derleyicisi ve 64 bit Office ise

#Else

#End If


Dikkat ederseniz VBA7, Office 2010 ile önceki versiyonlar kontrolünü, Win64 de Office 2010 versiyonunun 32 ve 64 bit kontrolünü yapmaktadır. API deklarasyonu için Microsoft.com’ daki dökümanlara bakınız.

Bir başka kullanım şekli de aşağıdaki gibidir. Function-End Function bloklarına dikkat edin.




ByRefOption = “abc” ise “ByRef” li function başlığı, değilse “ByVal” lı function başlığı okunacaktır. Resimde de görüldüğü üzere function blok bitişi olan “End Function” her iki başlık için ortaktır.

#Const Sabiti : Derleyicinin “Win64”, “Win32”, “VBA7” den başka tanımlı birkaç sabiti daha vardır. Eğer istersek biz de kişisel sabit tanımlayabiliriz.
Bir sabit tanımladığımızda, bu sabite eşit olan ve olmayan şeklinde sınayabiliyoruz. Yukarıdaki resimde “ByRefOption” sabitini sınarken “abc” ye eşitse sonuç doğru, “abc” dışındaki her şey sonucu “False” olacaktır. Yani;

#If ByRefOption = -1 Then şeklindeki kullanım da sınama için geçerlidir. Önemli olan ByRefOption sabitinin tanımında da görüldüğü gibi “abc” değerine eşit olup olmadığıdır.

Daha kompleks sınamalar için birden fazla sabit tanımlayarak iç içe geçmiş #If ifadeleri kullanılabilir.

--------------------------------
Faydalı olması dileğimle…
Kolay gelsin.

< Z e k i G Ü R S O Y >

 

Başa Dön