Wird in einer Formular-Entwurfsansicht ein Unterformular hinzugefügt, dann passt Access das Unterformular-Steuerelement an die Größe des Unterformulars an.
Diese Anpassung ist leider nicht sehr exakt, was wieder manuelle Nachbesserung erfordert.
Noch problematischer ist es, wenn nachträglich die Größe des Unterformulars geändert wird. Dann erfolgt leider keine Anpassung mehr.
Schon alleine das ändern der Bildlaufleisten oder das hinzufügen von einem Formularkopf haben bereits Auswirkung auf die Formulargröße.
Auch genervt von ständigen Anpassen der Unterformular-Steuerelemente?
Dann lassen wir doch einfach zur Laufzeit die Unterformular-Steuerelement-Größe an die tatsächliche Unterformulargröße anpassen.
Um die Größe des Unterformular korrekt auszulesen, müssen alle Elemente die eine Auswirkung auf die Größe haben berücksichtigt werden, wie z.B. Navigationsschaltflächen/Datensatzmarkierer/Bildlaufleisten/Formularkopf-/fuß
Genau das erledigt die folgende Prozedur:
Public Sub ResizeControlFromSubform(SubformControl As Access.SubForm _
, Optional ByRef SubformWidth As Long _
, Optional ByRef SubformHeight As Long)
Dim FormReportOBJ As Object, SectionOBJ As Section
Const SCROLLBARWIDTH As Integer = 290
Const BORDERFACTOR As Byte = 20, MINIBORDERWIDTH As Byte = 7
If Not SubformControl Is Nothing Then
Set FormReportOBJ = SubformControl.Form
Select Case FormReportOBJ.ScrollBars
Case 1
FormReportOBJ.ScrollBars = 0
Case 3
FormReportOBJ.ScrollBars = 2
End Select
SubformHeight = MINIBORDERWIDTH
SubformWidth = MINIBORDERWIDTH
If FormReportOBJ.Section(acDetail).Visible = True Then
SubformHeight = SubformHeight + FormReportOBJ.Section(acDetail).Height
End If
If SectionExists(FormReportOBJ, acHeader, SectionOBJ) Then
If SectionOBJ.Visible = True Then
SubformHeight = SubformHeight + SectionOBJ.Height
End If
End If
If SectionExists(FormReportOBJ, acFooter, SectionOBJ) Then
If SectionOBJ.Visible = True Then
SubformHeight = SubformHeight + SectionOBJ.Height
End If
End If
If FormReportOBJ.NavigationButtons = True Then
SubformHeight = SubformHeight + SCROLLBARWIDTH
End If
SubformWidth = SubformWidth + FormReportOBJ.Width
If FormReportOBJ.ScrollBars = 2 Or FormReportOBJ.ScrollBars = 3 Then
SubformWidth = SubformWidth + SCROLLBARWIDTH
End If
If FormReportOBJ.RecordSelectors = True Then
SubformWidth = SubformWidth + SCROLLBARWIDTH
End If
If Not SubformControl.BorderStyle = 0 Then
SubformHeight = SubformHeight + (SubformControl.BorderWidth * BORDERFACTOR)
SubformWidth = SubformWidth + (SubformControl.BorderWidth * BORDERFACTOR)
End If
SubformControl.Width = SubformWidth
SubformControl.Height = SubformHeight
End If
End Sub
Hinweis: Es wird die Funktionsprozedur SectionExists() benötigt, die hier mit ausführlicher Beschreibung hinterlegt ist.
Jetzt brauchen wir nur beim starten des Hauptformulars die Prozedur ausführen zu lassen, in dem wir das abgebildete Formular-Objekt übergeben:
Private Sub Form_Load()
ResizeControlFromSubform (me.MeinUnterformularsteuerelement)
End Sub
Noch besser, wir belasten unser Hauptformular erst gar nicht damit, sondern wir hinterlegen den Code einfach im Unterformular:
Private Sub Form_Load()
ResizeControlFromSubform ControlFromSubform (Me)
End Sub
Durch die Funktion ControlFromSubform() wird das unterformular-Steuerelement im übergeordneten Formular ermittelt. Diese Funktion ist hier ausführlich beschrieben.
Somit können wir jetzt unser Unterformular in beliebigen Formularen einbetten, es wird immer vollständig dargestellt, byby das Gefummel mit der Steuerelementgröße.
Und an den Hauptformularen muss kein Code hinterlegt werden, den dieser befindet sich ja dank der Funktion ControlFromSubform() im Unterformular :-)
Ein kleines Beispiel mit ausführlicher Kommentierung kann hier heruntergeladen werden.
Anmerkung: Die Werte der Konstanten SCROLLBARWIDTH / BORDERFACTOR / MINIBORDERWIDTH müssen ggf. angepasst werden, wenn besondere Windows-Design-Einstellungen vorgenommen wurden. Diese könnte man dynamische über Windows-Funktionen auslesen. Vielleicht findet sich jemand, der eine Lösung dazu hier postet.
PS:
Unsere Prozedur ResizeControlFromSubform liefert sogar die entsprechenden ermittelten Werte über die Übergabeparameter-Variablen SubformWidth & SubformHeight zurück.
Wenn gewünscht kann man im Formularentwurf des Hauptformulars die Eigenschaften Breite+Höhe manuell mit den ermittelten Werten befüllen. Achtung, die manuelle Angabe muss in cm erfolgen, geliefert werden jedoch twips. Also einfach die Werte durch 567
teilen ;-)
Tipps und Tricks für Microsoft® Office insbesondere Access und Excel VBA-Programmierung und Funktionen
Translate
Mittwoch, 21. Dezember 2016
Dienstag, 20. Dezember 2016
ACCESS: Wie lautet das Unterformular-Steuerelement in welches das aktuelle Formular sich befindet?
Das einbetten eines Formulars als Unterformular in einem Hauptformular, findet über das Unterformular-Steuerelement statt.
Nehmen wir mal an, wir möchten in Abhängigkeit von Datenzustände Eigenschaften von unserem Unterformular-Steuerelement wie z.B. Visible/Height/Width/BorderWidth ändern.
Aus dem Hauptformular ist es einfach, weil das Hauptformular ja alle seine Steuerelemente und Untersteuerelemente kennt: Me.MeinUnterformular.Visible = True
Das ändern eines Steuerelementes im Unterformular ist auch einfach.
Z.B. wird folgendermaßen das Steuerelement Nachname welches sich im Unterformular befindet deaktiviert: Me.MeinUnterformular.Form.Nachname.Enabled = False
Angenommen die Abhängigkeit der durchzuführenden Änderung befindet sich jedoch im Unterformular, dann muss aus dem Unterformular heraus auf das übergeordnete Formular zugegriffen werden.
Auch das ist mit Bordmittel direkt zu bewerkstelligen: Me.Parent.Name
Jetzt möchten wir jedoch das Unterformular-Steuerelement in welches das aktuelle Unterformular abgebildet wird ändern. Und genau dieses ist nicht bekannt.
Die folgende Funktion findet dies heraus:
Public Function ControlFromSubform(SubformOBJ As Access.Form) As Access.Subform
Dim ctl As Access.Control
On Error GoTo ErrHandler
For Each ctl In SubformOBJ.Parent
If ctl.ControlType = acSubform Then
If ctl.SourceObject = SubformOBJ.Name Then
If ctl.Form.Hwnd = SubformOBJ.Hwnd Then
Set ControlFromSubform = ctl
Exit For
End If
End If
End If
Next
ExitProc:
Exit Function
ErrHandler:
Resume ExitProc
End Function
Erläuterung: Die ForEach Schleife durchsucht alle Controls welche sich im Übergeordneten (Hauptformular) befinden und prüft ab, ob das Control ein Unterformular-Steuerelement ist und ob der Herkunftsobjektname dem Unterformularname entspricht.
An dieser Stelle könnten wir sagen: Treffer, wir haben es gefunden. Aber das gleiche Unterformular könnte ja mehrfach im selben Hauptformular eingebettet sein, daher müssen wir noch die WindowHandle-Nummer vergleichen. Wenn auch die stimmt, dann haben wir das richtige Unterformular-Steuerelement gefunden.
Beim Aufrufen der Funktion wird das Formular in die Variable SubformOBJ übergeben, dabei muss es sich um ein eingebettetes Formular handeln, sonst macht die Funktion keinen Sinn.
Wird ein Hauptformular übergeben, erzeugt das Objekt SubformOBJ.Parent einen Fehler, der Dank der Fehlerbehandlungsroutine sofort die Prozedur beendet und es wird Nothing als Rückgabewert geliefert (was ja völlig korrekt ist).
Anwendungsbeispiel:
Der Rahmen vom Unterformular-Steuerelement soll die Farbe Rot bekommen, wenn die Rechnung fällig ist, ansonsten die Farbe Blau. Die Information ob die Rechnung fällig ist steht im Steuerelement Rechnungfällig welches nur im Unterformular sich befindet.
Hier der Code dazu, der sich im Unterformular befindet:
Dim cSubForm As Access.SubForm
Private Sub Form_Load()
Set cSubForm = ControlFromSubform(Me)
End Sub
Private Sub Form_Current()
If Not cSubForm Is Nothing Then
If Me.Rechnungfällig = True Then
cSubForm.BorderColor = RGB(255, 0, 0) ' Rot
Else
cSubForm.BorderColor = RGB(0, 0, 255) 'Blau
End If
End If
End Sub
Hier noch ein weitere Beispiel wo diese Funktion ihre Anwendung findet.
Fazit:
Der Programmcode von Formularen sollte möglichst autark programmiert werden (also für sich funktionieren, unabhängig von anderen Objekten). Dazu müssen ggf. 'intelligente' globale Prozeduren geschrieben werden, die sich jedoch mittelfristig auszahlen :-)
Nehmen wir mal an, wir möchten in Abhängigkeit von Datenzustände Eigenschaften von unserem Unterformular-Steuerelement wie z.B. Visible/Height/Width/BorderWidth ändern.
Aus dem Hauptformular ist es einfach, weil das Hauptformular ja alle seine Steuerelemente und Untersteuerelemente kennt: Me.MeinUnterformular.Visible = True
Das ändern eines Steuerelementes im Unterformular ist auch einfach.
Z.B. wird folgendermaßen das Steuerelement Nachname welches sich im Unterformular befindet deaktiviert: Me.MeinUnterformular.Form.Nachname.Enabled = False
Angenommen die Abhängigkeit der durchzuführenden Änderung befindet sich jedoch im Unterformular, dann muss aus dem Unterformular heraus auf das übergeordnete Formular zugegriffen werden.
Auch das ist mit Bordmittel direkt zu bewerkstelligen: Me.Parent.Name
Jetzt möchten wir jedoch das Unterformular-Steuerelement in welches das aktuelle Unterformular abgebildet wird ändern. Und genau dieses ist nicht bekannt.
Die folgende Funktion findet dies heraus:
Public Function ControlFromSubform(SubformOBJ As Access.Form) As Access.Subform
Dim ctl As Access.Control
On Error GoTo ErrHandler
For Each ctl In SubformOBJ.Parent
If ctl.ControlType = acSubform Then
If ctl.SourceObject = SubformOBJ.Name Then
If ctl.Form.Hwnd = SubformOBJ.Hwnd Then
Set ControlFromSubform = ctl
Exit For
End If
End If
End If
Next
ExitProc:
Exit Function
ErrHandler:
Resume ExitProc
End Function
Erläuterung: Die ForEach Schleife durchsucht alle Controls welche sich im Übergeordneten (Hauptformular) befinden und prüft ab, ob das Control ein Unterformular-Steuerelement ist und ob der Herkunftsobjektname dem Unterformularname entspricht.
An dieser Stelle könnten wir sagen: Treffer, wir haben es gefunden. Aber das gleiche Unterformular könnte ja mehrfach im selben Hauptformular eingebettet sein, daher müssen wir noch die WindowHandle-Nummer vergleichen. Wenn auch die stimmt, dann haben wir das richtige Unterformular-Steuerelement gefunden.
Beim Aufrufen der Funktion wird das Formular in die Variable SubformOBJ übergeben, dabei muss es sich um ein eingebettetes Formular handeln, sonst macht die Funktion keinen Sinn.
Wird ein Hauptformular übergeben, erzeugt das Objekt SubformOBJ.Parent einen Fehler, der Dank der Fehlerbehandlungsroutine sofort die Prozedur beendet und es wird Nothing als Rückgabewert geliefert (was ja völlig korrekt ist).
Anwendungsbeispiel:
Der Rahmen vom Unterformular-Steuerelement soll die Farbe Rot bekommen, wenn die Rechnung fällig ist, ansonsten die Farbe Blau. Die Information ob die Rechnung fällig ist steht im Steuerelement Rechnungfällig welches nur im Unterformular sich befindet.
Hier der Code dazu, der sich im Unterformular befindet:
Dim cSubForm As Access.SubForm
Private Sub Form_Load()
Set cSubForm = ControlFromSubform(Me)
End Sub
Private Sub Form_Current()
If Not cSubForm Is Nothing Then
If Me.Rechnungfällig = True Then
cSubForm.BorderColor = RGB(255, 0, 0) ' Rot
Else
cSubForm.BorderColor = RGB(0, 0, 255) 'Blau
End If
End If
End Sub
Hier noch ein weitere Beispiel wo diese Funktion ihre Anwendung findet.
Fazit:
Der Programmcode von Formularen sollte möglichst autark programmiert werden (also für sich funktionieren, unabhängig von anderen Objekten). Dazu müssen ggf. 'intelligente' globale Prozeduren geschrieben werden, die sich jedoch mittelfristig auszahlen :-)
Montag, 19. Dezember 2016
ACCESS: Prüfen ob Formularkopf/-fuß existiert. Wie viele Sections liegen vor?
Wie kann mit VBA festgestellt werden, ob ein Formular/Report einen gewissen Bereich (z.B. Formularkopf) enthält?
Bei der Programmierung innerhalb eines Formulars, stellt sich nicht wirklich die Frage, den es ist ja bekannt was im aktuellen Formular an Bereichen eingerichtet ist und was nicht.
Bei der Programmierung einer allgemeinen Formularübergreifenden Prozedur, ist der Prozedur dies jedoch nicht bekannt, und da stellt sich sehr wohl die Frage, ob eine gewisse Section (Bereich) vorhanden ist oder nicht.
Dazu müssten wir eigentlich nur die VBA Auflistung Section befragen.
Doch Leider gibt es nicht solch eine Auflistung für dieses Objekt :-(
Also schreiben wir uns einfach eine Globale-Funktion die diesen Job für uns erledigt:
Public Function SectionExists(FormReportOBJ As Object, SectionIndex As AcSection _
, Optional ByRef SectionOBJ As Section) As Boolean
On Error Resume Next
Set SectionOBJ = FormReportOBJ.Section(SectionIndex)
SectionExists = (Err.Number = 0)
End Function
Hinweis: Das Objekt Section stellt uns leider nicht die Information zur Verfügung, wie viel Sections vorhanden sind. Somit bleibt uns nichts anderes übrig, als dies mit der Fehlerabfangmethode zu lösen. Nicht elegant, aber es funktioniert ;-)
Andere Fehler haben wir nicht zu erwarten, so dass hier keine weitere Fehlerbehandlung benötigt wird.
Und so kann jetzt unsere Funktion angewandt werden:
Private Sub Form_Load()
Dim SectionOBJ As Section
If SectionExists(Me, acHeader, SectionOBJ) Then
SectionOBJ.BackColor = RGB(255, 0, 0) 'Farbe Rot
End If
End Sub
Erläuterung: Bei Laden des Formulars wird geprüft ob das aktuelle Formular (ME) einen Formularkopf enthält. Wenn dies der Fall ist, liefert unsere Prüf-Funktion SectionExists() den Wert True zurück und die abgefragte Section wird mit der Hintergrundfarbe Rot versehen. Die Variable SectionOBJ ist übrigens per Referenz-Übergabe in der Funktionsprozedur SectionExists() gesetzt worden.
Wenn wir schon dabei sind, mit der gleichen Technik schreiben wir uns noch eine Funktion, die uns die Anzahl der vorhandenen Sections zurück liefert:
Public Function SectionsCount(FormReportOBJ As Object) As Byte
Dim i As Byte, SectionOBJ As Section
On Error Resume Next
For i = 0 To 255
Set SectionOBJ = FormReportOBJ.Section(i)
If Not Err.Number = 0 Then Exit For
Next
SectionsCount = i
End Function
Dies kann z.B. interessant sein, wenn per VBA alle Sections im Formular mit einer Farbe versehen werden soll. Und so geht's:
Private Sub Form_Load()
Dim i As Byte
For i = 0 To SectionsCount(Me)-1
me.Section(i).BackColor = RGB(255, 0, 0) 'Farbe Rot
Next
End Function
Das ganze funktioniert auch mit Berichten. Gerade diese haben durchaus noch weitere Bereiche wie acGroupLevel1Footer, acGroupLevel2Footer usw...
Bei der Programmierung innerhalb eines Formulars, stellt sich nicht wirklich die Frage, den es ist ja bekannt was im aktuellen Formular an Bereichen eingerichtet ist und was nicht.
Bei der Programmierung einer allgemeinen Formularübergreifenden Prozedur, ist der Prozedur dies jedoch nicht bekannt, und da stellt sich sehr wohl die Frage, ob eine gewisse Section (Bereich) vorhanden ist oder nicht.
Dazu müssten wir eigentlich nur die VBA Auflistung Section befragen.
Doch Leider gibt es nicht solch eine Auflistung für dieses Objekt :-(
Also schreiben wir uns einfach eine Globale-Funktion die diesen Job für uns erledigt:
Public Function SectionExists(FormReportOBJ As Object, SectionIndex As AcSection _
, Optional ByRef SectionOBJ As Section) As Boolean
On Error Resume Next
Set SectionOBJ = FormReportOBJ.Section(SectionIndex)
SectionExists = (Err.Number = 0)
End Function
Hinweis: Das Objekt Section stellt uns leider nicht die Information zur Verfügung, wie viel Sections vorhanden sind. Somit bleibt uns nichts anderes übrig, als dies mit der Fehlerabfangmethode zu lösen. Nicht elegant, aber es funktioniert ;-)
Andere Fehler haben wir nicht zu erwarten, so dass hier keine weitere Fehlerbehandlung benötigt wird.
Und so kann jetzt unsere Funktion angewandt werden:
Private Sub Form_Load()
Dim SectionOBJ As Section
If SectionExists(Me, acHeader, SectionOBJ) Then
SectionOBJ.BackColor = RGB(255, 0, 0) 'Farbe Rot
End If
End Sub
Erläuterung: Bei Laden des Formulars wird geprüft ob das aktuelle Formular (ME) einen Formularkopf enthält. Wenn dies der Fall ist, liefert unsere Prüf-Funktion SectionExists() den Wert True zurück und die abgefragte Section wird mit der Hintergrundfarbe Rot versehen. Die Variable SectionOBJ ist übrigens per Referenz-Übergabe in der Funktionsprozedur SectionExists() gesetzt worden.
Wenn wir schon dabei sind, mit der gleichen Technik schreiben wir uns noch eine Funktion, die uns die Anzahl der vorhandenen Sections zurück liefert:
Public Function SectionsCount(FormReportOBJ As Object) As Byte
Dim i As Byte, SectionOBJ As Section
On Error Resume Next
For i = 0 To 255
Set SectionOBJ = FormReportOBJ.Section(i)
If Not Err.Number = 0 Then Exit For
Next
SectionsCount = i
End Function
Dies kann z.B. interessant sein, wenn per VBA alle Sections im Formular mit einer Farbe versehen werden soll. Und so geht's:
Private Sub Form_Load()
Dim i As Byte
For i = 0 To SectionsCount(Me)-1
me.Section(i).BackColor = RGB(255, 0, 0) 'Farbe Rot
Next
End Function
Das ganze funktioniert auch mit Berichten. Gerade diese haben durchaus noch weitere Bereiche wie acGroupLevel1Footer, acGroupLevel2Footer usw...
Abonnieren
Posts (Atom)