Addin-Programmierung - Seite 4
von Frank Schüler
Projekte und Komponenten
Auslesen von Projekt und Komponent - Daten
Die meisten Benutzer wird aber interessieren, wie er Zugriff auf das aktuelle Projekt und dessen Komponenten bzw. auf die aktuell selektierte Komponente und dessen Codefenster erhält, um den Code auszulesen, zu manipulieren oder zu löschen. Das VBE-Objekt enthält ein Property mit dem Namen ActiveVBProject mit dem wir Zugriff auf das Objekt VBProject erhalten, also auf das aktive Projekt im Projektexplorer. Über das Objekt VBProject erhalten wir Zugriff auf verschiedene andere Objekte und Funktionen. So lassen sich über VBProject folgende Objekte ansprechen: VBComponents zum Auslesen aller geladenen Komponenten eines Projektes oder Laden einer Komponente, References zum Auslesen aller geladenen Verweise eines Projektes oder Laden eines Verweises. Darüber hinaus können Informationen zum aktuellen Projekt ausgelesen bzw. gesetzt werden, so z.B. der Name, der Typ und Dateiname des Projekts uvm. Sogar speichern und kompilieren lässt sich das Projekt über dieses Objekt. Hier ein Beispiel, wie man Projekt-Informationen auslesen kann.
Private Sub OKButton_Click() ' Bei Fehler zur Fehlerbehandlung On Error Goto error_handler ' Instanzieren der Objekte Dim VBProj As VBProject 'Projekt ' Variablen zu zwischenspeichern Dim VBProjName As String Dim VBProjFileName As String Dim VBProjType As String Dim VBProjStartMode As String ' Objekt instanzieren Set VBProj = VBInstance.ActiveVBProject 'aktives Projekt VBProjName = VBProj.Name 'Name des aktiven Projektes auslesen ' Typ des Projektes ermitteln Select Case VBProj.Type Case vbext_pt_StandardExe VBProjType = "StandardExe" Case vbext_pt_ActiveXExe VBProjType = "ActiveXExe" Case vbext_pt_ActiveXDll VBProjType = "ActiveXDll" Case vbext_pt_ActiveXControl VBProjType = "ActiveXControl" End Select ' StartMode des Projektes ermitteln Select Case VBProj.StartMode Case vbext_psm_StandAlone VBProjStartMode = "StandAlone" Case vbext_psm_OleServer VBProjStartMode = "OleServer" End Select ' Dateiname des Projektes ermitteln If VBProj.FileName = vbNullString Then VBProjFileName = VBProj.Name Else VBProjFileName = VBProj.FileName End If ' Anzeigen der Eigenschaften Text1.Text = "Aktives Projekt: " & VBProjName & vbNewLine & _ "ProjektTyp: " & VBProjType & vbNewLine & _ "ProjektFileName: " & VBProjFileName & vbNewLine & _ "ProjektStartMode: " & VBProjStartMode Set VBProj = Nothing ' Wenn alles ok, dann Methode verlassen Exit Sub ' Fehlerbehandlung error_handler: ' Fehler anzeigen MsgBox Err.Description End Sub
Wenn es aber darum geht, auf die selektierte Komponente zuzugreifen, dann steht uns das Property "SelectedVBComponent" in dem Objekt "VBE" zur Verfügung. Über dieses Property erhalten wir Zugriff auf das Objekt "VBComponent". Dieses Objekt enthält weitere Objekte z.B. zum Zugriff auf das Objekt "CodeModule". Auch lassen sich hier die Eigenschaften (Properties) der Komponente auslesen bzw. ändern. Über das Objekt "CodeModule" können wir auch den Code einer Komponente auslesen, ändern und löschen. Ein weiteres Objekt im Objekt "CodeModule" ist das Objekt "CodePane". Über diese Objekt lässt sich das Codefenster steuern und Code markieren bzw. markierten Code auslesen. Hier nur ein kleiner Ausschnitt der Möglichkeiten.
Private Sub Command5_Click() ' Bei Fehler zur Fehlerbehandlung On Error Goto error_handler ' Instanzieren der Objekte Dim VBComp As VBComponent 'Komponente ' Variablen zu zwischenspeichern Dim VBCompName As String Dim VBCompFileName As String Dim VBCompType As String Dim VBCompFiles As Long Dim VBCompFilesData As String ' Objekte instanzieren Set VBComp = VBInstance.SelectedVBComponent 'selektierte Komponente VBCompName = VBComp.Name 'Name der sel. Komponente auslesen ' Typ der Komponente ermitteln ' es gibt aber noch mehr Typen ! Select Case VBComp.Type Case vbext_ct_VBForm VBCompType = "Form" Case vbext_ct_StdModule VBCompType = "Modul" Case vbext_ct_ClassModule VBCompType = "Klassenmodul" End Select ' Dateiname der selektierten Komponente ermitteln If VBComp.FileNames(1) = vbNullString Then VBCompFileName = VBComp.Name Else 'Dateiname hat den Index 1 VBCompFileName = VBComp.FileNames(1) End If ' Verbundene Dateien auslesen zB frx VBCompFilesData = "Verbundene Dateien:" & vbNewLine ' ab Index 2 beginnen die verb. Dateien If VBComp.FileCount > 1 Then For VBCompFiles = 2 To VBComp.FileCount VBCompFilesData = VBCompFilesData & VBComp.FileNames(VBCompFiles) & vbNewLine Next VBCompFiles End If ' Anzeigen der Eigenschaften Text1.Text = _ "Selektierte Komponente: " & VBCompName & vbNewLine & _ "KomponenteFileName: " & VBCompFileName & vbNewLine & _ "KomponentenTyp: " & VBCompType & vbNewLine & _ VBCompFilesData Set VBComp = Nothing ' Wenn alles ok, dann Methode verlassen Exit Sub ' Fehlerbehandlung error_handler: ' Fehler anzeigen MsgBox Err.Description End Sub
Außer der Möglichkeit die Daten einer Komponente auszulesen, gibt es noch die Möglichkeit auf den Code einer Komponente zuzugreifen wie z.B. Hinzufügen von Code, Ändern von Code, Löschen von Code, Auslesen des Codes im Deklarationsabschnitt sowie im Codeabschnitt. Hier ein Beispiel zum Einfügen von Code in der selektierten Komponente.
Private Sub Command8_Click() ' Bei Fehler zur Fehlerbehandlung On Error Goto error_handler ' Instanzieren der Objekte Dim VBComp As VBComponent 'Komponente Dim VBCodeM As CodeModule 'CodeModul Dim VBCodeP As CodePane 'CodePane ' Objekte instanzieren Set VBComp = VBInstance.SelectedVBComponent 'selektierte Komponente Set VBCodeM = VBComp.CodeModule 'Codemodul von sel. Komponente Set VBCodeP = VBCodeM.CodePane 'Codefenster von sel. Komponente ' Codefenster anzeigen wenn noch nicht geöffnet VBCodeP.Show ' Einfügen von Text in Zeile 1 im Codemodul VBCodeM.InsertLines 1, "'Neue Zeile einfügen" Set VBComp = Nothing Set VBCodeM = Nothing Set VBCodeP = Nothing ' Wenn alles ok, dann Methode verlassen Exit Sub ' Fehlerbehandlung error_handler: ' Fehler anzeigen MsgBox Err.Description End Sub
Ein Objekt darf hier natürlich nicht fehlen. Das Member-Objekt. Das Member-Objekt enthält alle öffentlichen Elemente einer Komponente wie Variablen, Subs, Funktionen, Properties, Konstanten und Events. Eine Möglichkeit, wie man Enums und Types direkt von der VBIDE auslesen kann, gibt es leider nicht. Auch die Variablen usw. innerhalb einer Sub oder Funktion können nicht direkt ausgelesen werden. Ebenso verhält es sich bei den Parametern einer Sub oder Funktion. Bei einer Methode wird auch nicht angezeigt, ob es sich um eine Sub, Function oder API handelt. Bei Properties kann es unter Umständen dazu kommen, dass dreimal der gleiche Propertyname angezeigt wird und zwar für "Property Let PropertyName", "Property Get PropertyName" und "Property Set PropertyName". Da bleibt einem nur das Durchforsten des Codes Zeile für Zeile um herauszufinden ob es sich um ein Let- ,Get- und/oder Set-Property handelt. Hier das Beispiel zum auslesen von Memberdaten aus der selektierten Komponente.
Private Sub Command13_Click() ' Bei Fehler zur Fehlerbehandlung On Error Goto error_handler ' Instanzieren der Objekte Dim VBComp As VBComponent 'Komponente Dim VBCodeM As CodeModule 'Codemodule Dim VBMember As Member 'Member ' Variable zum zwischenspeichern Dim MemberData As String Dim MemberType As String Dim MemberScope As String ' Objekte instanzieren Set VBComp = VBInstance.SelectedVBComponent 'selektierte Komponente Set VBCodeM = VBComp.CodeModule 'Codemodul von sel. Komponente ' wenn keine Members vorhanden If VBCodeM.Members.Count < 1 Then Exit Sub MemberData = vbNullString For Each VBMember In VBCodeM.Members 'Membertyp ermitteln Select Case VBMember.Type Case vbext_mt_Method MemberType = "Methode" Case vbext_mt_Property MemberType = "Property" Case vbext_mt_Variable MemberType = "Variable" Case vbext_mt_Event MemberType = "Event" Case vbext_mt_Const MemberType = "Const" End Select 'Memberscopetyp ermitteln Select Case VBMember.Scope Case vbext_Private MemberScope = "Private" Case vbext_Public MemberScope = "Public" Case vbext_Friend MemberScope = "Friend" End Select MemberData = MemberData & "Membername: " & VBMember.Name & vbNewLine & _ "Membertyp: " & MemberType & vbNewLine & _ "MemberScope: " & MemberScope & vbNewLine & _ "In Zeile: " & CStr(VBMember.CodeLocation) & vbNewLine & _ "Zeile: " & VBCodeM.Lines(VBMember.CodeLocation, 1) & vbNewLine & _ vbNewLine Next VBMember Text1.Text = MemberData Set VBComp = Nothing Set VBCodeM = Nothing ' Wenn alles ok, dann Methode verlassen Exit Sub ' Fehlerbehandlung error_handler: ' Fehler anzeigen MsgBox Err.Description End Sub
Eine Eigenart der VB-Entwicklungsumgebung ist, dass Codezeilen wie
'Die API ist hier in vier Zeilen aufgeteilt, intern wird diese Zeile 'aber als eine Zeile behandelt Private Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, _ ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, _ ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, _ ByVal ySrc As Long, ByVal dwRop As Long) As Long 'Die Variablen stehen hier in einer Zeilen, intern wird diese Zeile 'aber wie drei Zeilen behandelt Dim Variable6 As Long: Dim Variable7 As Long: Dim Variable8 As Long
intern anders behandelt werden als im Codefenster, so dass über "VBMember.CodeLocation" die falsche Zeilennummer ausgelesen wird. Wenn der Unterstrich und der Doppelpunkt weggelassen werden, werden die korrekten Zeilennummern ausgelesen.
Das Finden von Membern im Code gestaltet sich recht umständlich da man ja nur den Namen des Members hat. Bei Prozeduren ist das noch recht einfach, da es dafür eine entsprechende Funktion gibt. Bei Properties ist es schon etwas schwieriger. Da man nur den Namen hat, aber nicht weiß, ob es sich um eine Get-, Set- oder Let-Property handelt. Hier ein Beispiel zum Finden von Membern im Code. (einfach die Mitglieder über den Button "Memberdaten von Komponente" auflisten und einen Membernamen in das Textfeld kopieren und dann den Button "Suchen und Markieren von Member" drücken.)
Private Sub Command15_Click() ' Variablen zum zwischenspeichern Dim sRow As Long 'Startzeile Dim eRow As Long 'Endzeile Dim sCol As Long 'Startsplate Dim eCol As Long 'Endspalte Dim lngPosition As Long 'gefundene Zeile ' in selektierter Komponente suchen With VBInstance.SelectedVBComponent With .CodeModule ' Temporarily defeat error trapping On Error Resume Next ' für Prozeduren (Sub, Function, API´s) lngPosition = .ProcBodyLine(Text2.Text, vbext_pk_Proc) ' für Property Let lngPosition = .ProcBodyLine(Text2.Text, vbext_pk_Let) ' für Property Get lngPosition = .ProcBodyLine(Text2.Text, vbext_pk_Get) ' für Property Set lngPosition = .ProcBodyLine(Text2.Text, vbext_pk_Set) ' für sonstige Member und Code If .Find(Text2.Text, sRow, sCol, eRow, eCol, False, False, False) Then lngPosition = sRow End If With .CodePane ' Codefenster anzeigen .Window.Visible = True ' setzt den Cursor vor dem Member .SetSelection lngPosition, 1, lngPosition, 1 ' markiert die ganze Zeile vom Member '.SetSelection lngPosition, 1000, lngPosition, 1 ' Focus auf Codefenster setzen .Window.SetFocus End With End With End With End Sub
Dann haben wir noch das Designer-Objekt. Der Zugriff auf das Designerfenster einer Komponente erfolgt über das "VBComponent"-Objekt. Ob eine Komponente ein Designerfenster besitzt, hängt vom Typ der Komponente ab. Ein Modul besitzt z.B. kein Designerfenster aber eine Form schon. Über das "Designer"-Objekt kann man auf die Entwurfsmerkmale einer Komponente zugreifen z.B. die Controls auf einer Form auslesen, hinzufügen und manipulieren. Hier ein Beispiel zum auslesen der Control-Informationen.
Private Sub Command14_Click() ' Bei Fehler zur Fehlerbehandlung On Error Goto error_handler ' Instanzieren der Objekte Dim VBComp As VBComponent 'Komponente Dim VBCtrl As VBControl 'Control-Objekt ' Variable zum zwischenspeichern Dim CtrlData As String Dim CtrlName As String Dim CtrlIndex As String Dim CtrlType As String ' Objekte instanzieren Set VBComp = VBInstance.SelectedVBComponent 'selektierte Komponente ' Komponenttyp ermitteln Select Case VBComp.Type Case vbext_ct_VBForm, vbext_ct_VBMDIForm, vbext_ct_MSForm, _ vbext_ct_DocObject, vbext_ct_PropPage, vbext_ct_UserControl ' Designerfenster anzeigen VBComp.DesignerWindow.Visible = True VBComp.DesignerWindow.SetFocus ' wenn keine Controls vorhanden sind If VBComp.Designer.VBControls.Count < 1 Then Goto ende ' Controldaten auslesen CtrlData = vbNullString For Each VBCtrl In VBComp.Designer.VBControls 'Name des Controls ermitteln CtrlName = VBCtrl.Properties.Item("Name").Value 'Index des Controls ermitteln CtrlIndex = VBCtrl.Properties.Item("Index").Value 'wenn Index =>0, dann handelt es sich um ein Controlarray If CLng(CtrlIndex) >= 0 Then CtrlName = CtrlName & "(" & CtrlIndex & ")" 'Typ des Controls auslesen Select Case VBCtrl.ControlType Case vbext_ct_Light CtrlType = "Light" Case vbext_ct_Standard CtrlType = "Standard" Case vbext_ct_Container CtrlType = "Container" End Select 'restliche Daten auslesen CtrlData = CtrlData & "ControlName: " & CtrlName & vbNewLine & _ "ControlClassName: " & VBCtrl.ClassName & " (" & VBCtrl.ProgId & ")" & vbNewLine & _ "ControlTyp: " & CtrlType & vbNewLine & _ "ControlSelected: " & CStr(VBCtrl.InSelection) & vbNewLine & vbNewLine Next VBCtrl ' Daten anzeigen Text1.Text = CtrlData ' Designerfenster schließen VBComp.DesignerWindow.Close Case Else ' Komponente hat kein Designerfenster Goto ende End Select ende: Set VBComp = Nothing Set VBCtrl = Nothing ' Wenn alles ok, dann Methode verlassen Exit Sub ' Fehlerbehandlung error_handler: ' Fehler anzeigen MsgBox Err.Description End Sub
Bisher wurden immer das aktuelle Projekt und die aktuell selektierte Komponente im Code verwendet. Manchmal aber muss man auch auf die nicht aktuell selektierte Komponente oder auf das zweite Projekt, das gerade nicht das aktuelle Projekt ist, usw. zugreifen. Auch das ist möglich. Dazu werden die Collection-Objekte verwendet wie z.B. das VBProjects-Objekt oder das VBComponents-Objekt. Diese Objekte enthalten eine Funktion mit dem Namen "Item". Dieser Funktion kann der Index eines Objektes übergeben werden oder der Name des Objektes. Den Index eines Objektes wird man kaum verwenden. Hier nur einige Beispiele, wie man auf nicht selektierte bzw. nicht aktuelle Objekte zugreifen kann. Kombinationen sind möglich.
Private Sub Command15_Click() ' Bei Fehler zur Fehlerbehandlung On Error Goto error_handler Dim VBProj As VBProject Dim VBComp As VBComponent Dim VBProp As Property ' Zugriff auf ein nicht aktuelles Projekt Set VBProj = VBInstance.VBProjects("Project2") ' Zugriff auf eine nicht selektierte Komponente in einem nicht aktuelles Projekt Set VBComp = VBInstance.VBProjects("Project1").VBComponents("Module1") ' Zugriff auf eine nicht selektierte Komponente im aktuellen Projekt Set VBComp = VBInstance.ActiveVBProject.VBComponents("Module1") ' Zugriff auf die Eigenschaft "Name" einer nicht selektierter Komponente des aktuellen Projektes Set VBProp = VBInstance.ActiveVBProject.VBComponents("Form1").Properties("Name") '****************** '* sonstiger Code * '****************** Set VBProj = Nothing Set VBComp = Nothing Set VBProp = Nothing ' Wenn alles ok, dann Methode verlassen Exit Sub ' Fehlerbehandlung error_handler: ' Fehler anzeigen MsgBox Err.Description End Sub
Auslesen aller Projektinformationen
Hier noch mal ein grober Überblick, wie man alle Informationen von geladenen Projekten auslesen kann.
Private Sub AllData_Click() ' Bei Fehler zur Fehlerbehandlung On Error Goto error_handler ' Instanzieren der Objekte Dim VBProj As VBProject 'Projekt-Objekt Dim VBRef As Reference 'Referenz-Objekt Dim VBComp As VBComponent 'Komponent-Objekt Dim VBCtrl As VBControl 'Control-Objekt Dim VBMember As Member 'Member-Objekt ' Variable zum zwischenspeichern Dim CompFiles As Long ' wenn kein Projekt geladen, dann goto NoProj If VBInstance.VBProjects.Count < 1 Then Goto NoProjekt ' alle geladenen Projekte durchlaufen For Each VBProj In VBInstance.VBProjects ' Hier können alle Infos zu dem Projekt ' ausgelesen werden ' wenn kein Verweis geladen, dann goto NoReferenz If VBProj.References.Count < 1 Then Goto NoReferenz ' alle Verweise zum Projekt durchlaufen For Each VBRef In VBProj.References ' Hier können alle Infos zu den Verweisen ' ausgelesen werden Next VBRef NoReferenz: ' wenn keine Komponente geladen, dann goto NoComponent If VBProj.VBComponents.Count < 1 Then Goto NoComponent ' alle Komponeten zum Projekt durchlaufen For Each VBComp In VBProj.VBComponents ' Hier können alle Infos zu der Komponente ' ausgelesen werden ' wenn keine verbundenen Datein (z.B. frx), dann goto NoCompFiles If VBComp.FileCount < 2 Then Goto NoCompFiles ' alle verbundenen Dateien zur Komponente durchlaufen For CompFiles = 2 To VBComp.FileCount ' Hier können alle Infos zu den verbundenen Dateien ' ausgelesen werden Next CompFiles NoCompFiles: '*************************************************** '* Hier muss noch die VBComp.Type Abfrage erfolgen * '* um die anderen Objekte der Komponente ohne * '* Fehler auslesen zu können. * '* z.B. enthält eine Res-Komponente kein Designer-,* '* CodeModule-, CodePane- und Member-Objekt * '*************************************************** ' wenn kein Control auf Komponente, dann goto NoControl If VBComp.Designer.VBControls.Count < 1 Then Goto NoControl ' alle Controls zur Komponente durchlaufen For Each VBCtrl In VBComp.Designer.VBControls ' Hier können alle Infos zu den Controls ' ausgelesen werden Next VBCtrl NoControl: ' wenn keine Members vorhanden, dann goto NoMember If VBComp.CodeModule.Members.Count < 1 Then Goto NoMember ' alle Member im Codemodule durchlaufen For Each VBMember In VBComp.CodeModule.Members ' Hier können alle Infos zu den Members ' ausgelesen werden Next VBMember NoMember: Next VBComp NoComponent: Next VBProj NoProjekt: Set VBProj = Nothing Set VBRef = Nothing Set VBComp = Nothing Set VBCtrl = Nothing Set VBMember = Nothing ' Wenn alles ok, dann Methode verlassen Exit Sub ' Fehlerbehandlung error_handler: ' Fehler anzeigen MsgBox Err.Description End Sub