AW: Eigenes Modul für globale Variablen?
21.05.2017 23:21:18
Zwenn
Ich mache es mal etwas anders, als direkt auf Deine Frage und Deinen Codeabschnitt einzugehen. Zunächst werde ich Dir aufzeigen, wie ich versucht habe, mich Deinem Problem zu nähern und worüber ich dann gestolpert bin. Dann gebe ich Dir ein Beispiel, wie man mit Variablen verfährt, die man öfter braucht, wie Du es ausgedrückt hast. Schließlich gebe ich Dir einen Link zu einem Beitrag auf WikiPedia auf einer Ebene, die Du hoffentlich nachvollziehen kannst.
Zunächst einmal muss ich sagen, dass VBA nicht meine "Kernsprache" ist. Ich komme von C (strukturiert) und Java (objektorientiert) und habe deshalb wahrscheinlich ganz anders angefangen Dein Problem zu betrachten, als Gerd und Nepumuk. Ich bin also gar kein Excel und VBA Crack, ich nutze dieses Werkzeug nur leidlich für meine Zwecke ;-)
Wie ich den Fehler nachvollzogen habe
Als erstes habe ich Dein Makro nach Deinen Anweisungen in Deinem ersten Posting gestartet. Dabei bin ich natürlich auf besagten Fehler gestoßen. Allerdings ist mir direkt aufgefallen, dass auch die Meldung kam, dass der Datensatz gespeichert wurde. Nun habe ich zur Prüfung Daten in der UserForm verändert. Die wurden alle gespeichert, der Fehler musste also nach dem Speichern erfolgen.
Ich schaute in den Code und stieß auf diese Funktion:
Sub Stamm_speichern()
bSpeichern = True
sMakro = "Stamm_sp1"
Call Felder_pruefen
If bSpeichern = False Then Exit Sub
' Call Warten_speichern
Run sMakro
End Sub
Du initialisierst bSpeichern direkt mit True. (Ich bin ein großer Fan von Initialisierungen von Variablen, weil in C sonst nur Grütze rauskommt ;-) ) Die Variable selbst wird aber gar nicht deklariert, während oben im Modul Option Explicit zu lesen ist. Ein paar Zeilen später fragst Du If bSpeichern = False Then Exit Sub und ich stehe im Regen. Wo zum Henker soll denn bSpeichern auf False gesetzt werden? In dieser Sub jedenfalls nicht. Ich bin dem dann nachgegangen, obwohl ich wußte, dass das Speichern funktioniert.
Bisher befinden wir uns im Modul basStamm. Die Variable bSpeichern kann aber in einem völlig anderen Modul und einer völlig anderen Funktion auf False gesetzt werden. Nämlich im Modul zzGlobal in der folgenden Funktion, in der drittletzten Zeile:
Sub Felder_pruefen()
Dim bytZaehler As Byte
With UF1.mlp1.Pages(iPage)
For Each obj In .Controls
Select Case TypeName(obj)
Case "TextBox", "ComboBox"
If obj.BorderColor = 16711680 And obj = "" Then bytZaehler = bytZaehler + 1
End Select
Next obj
End With
If bytZaehler 0 Then
Application.Run "Fehler_speichern": bSpeichern = False
End If
End Sub
Es war also klar, dass globale Variablen eingesetzt werden. Schließlich schaute ich mir alle Module an und stieß auf:
Option Explicit
Public pClose As Boolean 'shliessen Arbeitsmappe
Public bSpeichern As Boolean
Public Wkb As Object
Public wks As Object
Public obj As Object
Public Const sD As String = "DO1.xlsm"
Public Const sF As String = "OV1.xlsm"
Public Const sData As String = "Data"
Public wData As Object
Public UF As UserForm
Public sJaNein As String 'MsgBox
Public sFrage As String 'MsgBox
Public sZeichen As String 'MsgBox
Public sTitel As String 'MsgBox
Public sTitel1 As String
Public iPage As Integer
Public sMakro As String
Zu diesem Zeitpunkt hatten Gerd und Nepumuk Dein Problem längst gelöst. Ich war noch nicht mal in der Nähe, habe dann aber geguckt, ob schon jemand geantwortet hat. Ich dachte dann, ich spreche die globalen Variablen mal an.
Wie man globale Variablen vermeidet und lokale Variablen einsetzt.
In all Deinen Modulen verwendest Du Subs und nicht eine einzige Function. Der Unterschied ist, dass Subs etwas abgeschlossenes machen, während Fuctions einen Wert an die aufrufende Routine zurückgeben, den sie als Ergebnis rausbekommen haben.
Beide Arten von Unterprogrammen haben eine Gemeinsamkeit. Das Klammerpar (). Dieses ist bei Dir immer leer, man kann da aber etwas reinschreiben. Nämlich die Funktionsparameter, die dem Code in der Funktion bzw. der Sub übergeben werden, um damit zu arbeiten.
Das erfolgt nach folgendem Muster:
Sub NAME (PARAMETER1 as DATENTYP, PARAMETER2 as DATENTYP, ..., PARAMETERn as DATENTYP)
oder
Function NAME (PARAMETER1 as DATENTYP, PARAMETER2 as DATENTYP, ..., PARAMETERn as DATENTYP) as _
DATENTYP
Die auf diese Weise übergebenen Variablen müssen innerhalb der Sub oder der Function nicht nochmal deklariert werden. Sie werden einfach unter dem Namen verwendet, den sie im Kopf bekommen haben. Der Unterschied zwischen Sub und Function ist, dass eine Sub keinen Rückgabewert hat, während eine Function einen Wert an die aufrufende Routine zurück gibt. Deshalb steht hinter der Klammer () einer Function eine weitere Typenangabe. Diese gibt an, welchen Typ der Rückgabewert hat.
Wie gesagt gibt eine Sub keinen Wert zurück. Du hast sie in diesem Sinne ganz richtig verwendet. Eine Function hingegen ist ein Stück Code, das öfter benötigt wird, um eine wiederkehrende Aufgabe zu erledigen, die ein Ergebnis hat, dass vom aufrufenden Code weiter verarbeitet werden muss.
Beispiel:
Du entwickelst eine Routine, die eingegebene Werte speichern soll. So ein Zufall ;-)
Sub Stamm_speichern(bSpeichern as Boolean)
bSpeichern = Felder_pruefen(bSpeichern)
If bSpeichern = False Then Exit Sub
Call Stamm_sp1
End Sub
Function Felder_pruefen(speicherDing As Boolean) As Boolean
Dim bytZaehler As Byte
With UF1.mlp1.Pages(iPage)
For Each obj In .Controls
Select Case TypeName(obj)
Case "TextBox", "ComboBox"
If obj.BorderColor = 16711680 And obj = "" Then bytZaehler = bytZaehler + 1
End Select
Next obj
End With
If bytZaehler 0 Then
Application.Run "Fehler_speichern": speicherDing = False
End If
Felder_pruefen = speicherDing
End Function
Ich habe die erste Sub als Sub belassen, ohne nachzusehen, ob sie als Function besser wäre (mit einem Rückgabewert). Für die Betrachtung, wie Parameter und Rückgabewerte funktionieren reicht das aber aus.
Was passiert nun in den beiden angegebenen Codeabschnitten?
Der ersten Sub Stamm_speichern wird bereits ein Parameter übergeben. Nämlich bSpeichern. Dieser muss vom Typ Boolean sein. Dieser Parameter wird als Variable direkt in der ersten Zeile bSpeichern = Felder_pruefen(bSpeichern) verwendet.
Den zweite Codeabschnitt habe ich zu einer Funktion umgeschrieben. Eine Funktion gibt immer ein Ergebnis zurück. Dieses Ergebnis wird von der aufrufenden Routine Stamm_speichern auch erwartet. Denn die Zeile bSpeichern = Felder_pruefen(bSpeichern) ist ja eine Zuweisung des Ausdrucks Felder_pruefen(bSpeichern) an die Variable bSpeichern.
Hier sind nun zwei Dinge interressant und zu beachten:
1. Der Rückgabewert der aufgerufenen Funktion Felder_pruefen muss vom Typ Boolean sein, weil der Typ von bSpeichern im Kopf der aufrufenden Funktion Function Stamm_speichern(bSpeichern as Boolean) als Boolean deklariert wurde und genau diese Variable erwartet den Rückgabewert der aufgerufenen Funktion.
2. Die Variable bSpeichern, die aus der Sub Stamm_speichern an die Funktion Felder_pruefen übergeben wird, heißt in der empfengenden Funktion Felder_pruefen plötzlich speicherDing Diese Variable muss ebenfalls vom Typ Boolean sein, weil wir es immer mit der gleichen Art von Wert zu tun haben. Das der Variablenname sich ändern kann, ist der Tatsache geschuldet, dass die Funktionsparameter im Kopf einer Fanktion oder Sub (wobei es sich dabei streng genommen um eine Prozedur handelt, die eben keinen Rückgabewert hat), eine lokale Variablendeklaration sind. Dieses Prinzip nutzen wir für Black Boxes
Exkurs Balck Boxes
Nur eine kurze Erklärung. Eine Black Box ist ein Konstrukt, von dem wir nur wissen, was wir reinstecken müssen und was wir zurück erwarten. Wir wissen nicht, wie sie im inneren arbeitet. Sprich, wir wissen nicht, wie sie zu dem Ergebnis kommt, dass sie zurück liefert.
Warum ist das gut? Weil es uns wiederverwendbaren Code ermöglicht. Ein Funktion oder eine Sub kann eine Black Box sein. Wenn sie definierte Eingabeparameter und einen bestimmten Ausabewert hat, dann können wir sie einfach 1 zu 1 in jedem beliebigen anderen Programm einsetzen. Einfach reinkopieren und mit ihren benötigten Parametern verwenden.
Verwendet eine Black Box globale Variablen, ist sie keine Black Box mehr. Es wird nämlich mindestens ein Wert benötigt, der weder im Funktionskopf, noch in der Funktion selbst deklariert wird. Das Konstrukt ist somit nicht ohne weiteren Aufwand in anderen Programmen zu verwenden.
Weiterführender Link auf WikiPedia
Das Prinzip, dass sich hinter dem ganzen erklärten verbirgt, nennen wir Datenkapselung oder im Englischen Information Hiding. Dahinter verbirgt sich der Ansatz, dass möglichst viele Daten, nur möglichst wenig Programmteilen bekannt sind. Einfach gesagt, ist es dann unterm Strich einfacher, mit dem Gesamtproblem umzugehen.
https://de.wikipedia.org/wiki/Datenkapselung_(Programmierung)
Viele Grüße,
Zwenn