Hello Again

By , 25. Oktober 2024

Es ist schon relativ lange her dass der AccessBlog Offline ging – vorallem mangels Interesse an der Materie von meiner Seite.

Doch das soll sich nun Ändern, demnächst wird hier wieder Leben einziehen und auch neue Blogbeiträge erscheinen. Doch zuvor werden Schrittweise die alten Beiträge hier wieder online gestellt. Also bitte um etwas Geduld.

Viele Grüße Andreas

Dynamisches Button-Menü realisieren

By , 28. Mai 2025

In einer bestehenden Anwendung für den Verein habe ich ein Menü aus Befehlsschaltflächen – also Buttons – erstellt:

Jetzt sollte aber die Anordnung der Menüpunkte verändert werden können, und auch einzelne Menüpunkte ein- bzw. ausgeblendet werden können. Hört sich kompliziert an, ist es aber eigentlich nicht.
Was wir brauchen ist eine zusätzliche Tabelle tblSettings mit den Feldern ID (Autowert), cmdIconsOrder (Text), cmdIconsVisibility (Text) und UserID (long). Außerdem ein zusätzliches Formular in dem wir die Menüpunkte bearbeiten, und ein bisschen VBA-Code im Load() Ereignis des Formular wo das Menü sich befindet.

Fangen wir mal mit dem zusätzlichen Formular an.
Wir brauchen für jeden Menüpunkt eine Befehlsschaltfläche und eine Checkbox mit der wir den Menüpunkt sichtbar/unsichtbar schalten. Außerdem brauchen wir für jede Position eine Art „Anker“ mit Hilfe dessen wir die Position des Menüpunktes einstellen. Dazu verwende ich ganz einfach kurze, unsichtbare Linien. Das verschieben bzw. ein-/ausblenden machen wir mit je einer weiteren Befehlsschaltfläche. Und zum Schluss noch eine Befehlsschaltfläche zum Speichern. Bei mir sind es 12 Menüpunkte, und das formular sieht in der Entwurfsansicht wie folgt aus:

Um die Position und die Sichtbarkeit zu speichern, benutze ich 2 Arrays die im Deklarationsteil definiert sind:
Dim buttonOrder() As String
Dim checkOrder() As String
Das Verschieben der Menüpunkte mittels der Buttons hoch/runter geht relativ einfach, indem die Top-Position der jeweiligen Controls getauscht werden.
Klickt man dann auf Speichern wird in einer For/Next-Schleife ein Semikolon-Separierter Textstring gebildet, einer für die Position und einer für die Sichtbarkeit. Mittels Aktionsabfrage werden diese dann in die Tabelle geschrieben.
Das ist relativ viel Code, und ich hab sicherlich viel unnützer Ballast darin, so dass ich den Code im Detail nicht Posten werde. Aber ich werde demnächst eine Beispiel-Datei machen und hier hochladen.

Dann gehts weiter mit dem eigentlichen Formular wo das Menü abgebildet ist.
Um jetzt die einzelnen Punkte anzuordnen bzw. unsichtbar zu schalten, habe ich im Form_load() Ereignis folgenden Code:

Private Sub Form_Load()
    Dim i%, k%, m%

    buttonOrder = Split(fcDomWert("cmdIconsOrder", "tabSettings", "ID=1", ltDLookup), ";")
    checkOrder = Split(fcDomWert("cmdIconsVisibility", "tabSettings", "ID=1", ltDLookup), ";")

    For i = 1 To 12
        k = k + 1
        If checkOrder(i) = "0" Then
            Me("KB" & buttonOrder(i)).visible = False
            k = k - 1
        ElseIf m = 0 Then
            m = 1
        Else
            '...
        End If
        Me("KB" & buttonOrder(i)).Left = Me("ln" & k).Left
    Next i
    Me("KB" & m).SetFocus
End Sub

Lasst euch nicht von der Funktion „fcDomWert“ verwirren, das ist eine Domänenersatzfunktion die ich verwende, also anstelle von Dlookup().

Die Buttons haben den Namen KB1 bis KB12, die Checkboxen den Namen chk1 bis chk12, und die Linien den Namen ln1 bis ln12
Die Position, also an welcher Linie der Button plaziert wird, wird über den Zähler „k“ realisiert. k weicht von i dann ab, wenn einzelne Menüpunkte unsichtbar sind. Man möchte ja im Menü keine Lücken haben. In der Variable m wird die Nummer des ersten sichtbaren Buttons erfasst, um auf diesen dann den Focus zu setzen.

Für wen oder was ist dieser Aufwand denn überhaupt gut? Nun, stellt euch vor, eure Anwendung wird von verschiedenen Personen bedient, aber auch von einem der mehr Berechtigungen haben muss um z.B. bestimmte Einstellungen zu tätigen, Datenimport durchzuführen etc. Dann macht es durchaus Sinn eine Art Benutzeranmeldung beim Start vorzunehmen und einem Standard-Nutzer nur wenige Menüpunkte anzuzeigen. Dazu könnte man dann z.B. einen Benutzer-ID in der Tabelle tblSettings speichern, damit beim Start die Einstellung für genau diesen Benutzer geladen werden.

Soweit jetzt aber mal für heute,
bis dahin, euer Andi.

Quick-Tipp: Daten nach Excel exportieren

By , 21. Mai 2025

Auf die Schnelle die aktuellen Datensätze nach Excel rüberschieben wäre doch nicht schlecht, und beeindruckt doch sicher euren Chef – der von der Materie wieder keine Ahnung hat 😉
Und wenn der wüsste wie einfach das geht…

Hier mal der ganze Code der dazu notwendig ist:

Dim oExcel As Object, i As Integer
Dim db As Database
Dim rs As Recordset
Set db = CurrentDb

On Error Resume Next
Err.Clear
Set oExcel = CreateObject("Excel.Application")
If Err.Number <> 0 Then Set oExcel = CreateObject("Excel.Application")
On Error GoTo 0
With oExcel
    .Visible = True
    .Workbooks.Add
    .ActiveSheet.Name = "Arbeitsblatt1"
    Set rs = db.OpenRecordset(Me.RecordSource, dbOpenSnapshot)
    For i = 0 To rs.Fields.Count - 1
        .Cells(1, i + 1) = rs.Fields(i).Name
    Next i
    .Range("A2").SELECT
    .Selection.CopyFromRecordset rs
End With
rs.close
set rs = Nothing
set db = Nothing
Set oExcel = Nothing

Da obige Methode late Binding ist, benötigt man keinen Verweiss auf Excel.
Mittels .Cells(zeile,spalte) wird die Spaltenüberschriften eingetragen, die aus dem Namen der Datenfelder kommen. .Range(„A2“).SELECT setzt den Cursor auf das Feld A2 in Excel, und mittels .Selection.CopyFromRecordset rs werden alle Daten im aktuellen Recordset auf einen Rutsch nach Excel kopiert.

AV 2009

Mehrstufige abhängige Auswahl

By , 5. November 2024

Vor einiger Zeit stand ich bei einem Programm für die Vereinsmeisterschaft unseres Schützenvereines vor der Aufgabe, die Ergebnisanzeige endlich korrekt zu realisieren. Man sollte folgendes auswählen können:

  1. die Disziplingruppe, also alle Disziplingruppen, Pistole, Gewehr, Flinte, Armbrust etc.
  2. von der Disziplingruppe abhängend die entsprechende Disziplin, also alle Disziplinen, oder einzelne Disziplinen.
  3. die Altersklasse, also alle Klassen, Schüler, Jugend, Junioren, Herren, Damen oder abhängig von der Disziplin Auflageschießen sollen dann Seniorenklassen angezeigt werden. Das ist eine spzielle Eigenart im Deutschen Schützenbund.

Irgendwie dachte ich da spontan an das Portal unseres Landesverbandes, der in der Ergebnisliste genau diese Auswahlmöglichkeit (neben der Vereins- und Schützenauswahl) abbildet. Von da aus war der Schritt, wie konkret die Ergebnisse dargestellt werden sollen, nicht weit, nämlich genau so wie im Webportal.

Also mittels dem Untersuchungs-Werkzeug des Firefox Webbrowsers die Struktur der Darstellung analysiert, teils kopiert teils ergänzt/abgewandelt. Als Ergebnis stand am Schluss eine Prozedur die per Buttonklick aufgerufen wird, welche im ersten Teil in Abhängigkeit der Auswahlen einen SQL-String aufbaut. Eine kleine Schwierigkeit dabei war es die Auswahlen für „Alle …“ zu realisieren.

Gelöst habe ich dies indem ich einen String (strCase) zusammengesetzt habe jeweils aus 1 oder 0 für jede Auswahlmöglichkeit. Also „111“ für den Fall dass alle 3 Auswahlen auf „Alle“ stehen bzw. „000“ falls keine oder eben die Zwischenschritte. Danach habe ich die möglichen Fälle bestimmt die jeweils eine andere Zusammensetzung des SQL-Strings bedingen würden und in einer Select Case Schleife abgearbeitet. Das Ergebnis ist relativ komplex, aber ich denke zumindest die Systematik dahinter kann man relativ einfach verstehen:
Read more »

Kleine Code-Helferlein

By , 4. Februar 2020

Hallo,
heute möchte ich euch ein paar nützliche Code-Listings vorstellen, die mir bestimmte Arbeiten erleichtern.
Zum ersten ist es eine Prozedur, die mir geöffnete Objekte – Formulare oder Berichte – schließt ohne dass ich jedesmal „DoCmd.close acForm, Me.Name“ schreiben muss. Entwickler sind auch nur faule Menschen 🙂
Statt dessen schreibe ich der Einfachheit halber nur:

hide Me

Die Prozedur dazu in einem Modul sieht so aus:

Public Sub hide(ByRef obj As Object)
    If obj.Application.Forms.Count > 0 Then
        DoCmd.Close acForm, obj.Name
    Else
        DoCmd.Close acReport, obj.Name
    End If
End Sub

Das zweite Helferlein betrifft Dialog-Formulare die ich in diesem Zusammenhang dazu verwende um mir vom Anwender ein OK für irgendwas zu holen, sei es um was zu löschen oder sonst irgendwas. Die Function incl. der Deklaration einer globalen Variable in einem Modul sieht dazu wie folgt aus:

Global selectedOK As Boolean

Public Function CheckOK(ByVal cFormName As String, Optional ByVal vValue As Variant) As Boolean
    DoCmd.OpenForm cFormName, , , , , acDialog, vValue
    If selectedOK Then
        selectedOK = False
        CheckOK = True
    End If
End Function

Im aufgerufenen Formular kann ich dann im Form_Load-Ereignis Me.OpenArgs auswerten um z.B. einen bestimmten Text anzuzeigen o.Ä. In obiger Function wird in der If-Abfrage die globale Variable sofort wieder zurückgesetzt, so dass ich mir keine Gedanken machen muss um deren Status. Daher verwende ich auch den Rückgabewert dieser Function zur weiteren Code-Steuerung, und nicht die globale Variable irgendwo in einem Formular, weil eben nur hier ein nur einem einzigen Ort die Globale Variable zurückgesetzt wird.

Bei Klick auf einen Löschen-Button oder OK-Button in dem Dialog-Formular schreibe ich im entsprechenden Click-Ereignis dann nur folgendes:

Private Sub cmdDelete_Click()
    selectedOK = True
    hide Me
End Sub

Der dritte Tipp betrifft ein bestimmtes Sonderzeichen, welches beim Verwenden zu unerwünschten Ergebnissen führen kann. Hast du schon mal versucht folgenden Text in ein Bezeichnungsfeld über die Caption-Eigenschaft zu schreiben:

Me.Bezeichnungsfeld1.Caption = "Meier GmbH & Co. KG"

Man wird schnell feststellen dass Access das &-Zeichen anderst interpretiert. Die Lösung ist schlichtweg das &-Zeichen zu verdoppeln. Doch was wenn man statt mit Texten mit Variablen arbeitet? Jedesmal die Replace()-Funktion bemühen? Das war mir zu umständlich, so ist es zu folgendem Mini-Codelisting gekommen:

Public Function doubleAND(ByVal cValue As String) As String
    doubleAND = Replace(cValue, "&", "&&")
End Function

Jetzt kann ich ganz einfach wie folgt das schreiben:

Me.Bezeichnungsfeld1.Caption = doubleAND(Firmenname)

Das soll es jetzt fürs erste gewesen sein.
Bis dahin
©2020 Andreas Vogt

In Bug-Falle getappt: Registersteuerelement verschieben mit Unterformular

By , 14. Januar 2020

Hallo,
z.Z. arbeite ich an einem Eigenprojekt – einer Belegungssteuerung für die Pension. Dabei bin ich auf ein Problem gestoßen das wohl ein Bug in Access ist, und zwar zumindest der Version 2010 bis zur aktuellen 2019er Version. Der Sachverhalt ist folgender:
Auf einer Seite eines Registersteuerelementes plazierte ich ein Unterformular-Steuerelement, mit Form als Sourceobject. Die Registersteuerelemente stellen in dem Formular eine Box dar, die zusammengeklappt und aufgeklappt werden kann – mit mehreren solcher „Boxen“ die sich dann entsprechend positionieren. Dazu wird die Move() Funktion verwendet.

Den Style-Fehler, den Access nun produziert, ist dieser:
Verschiebe ich ein RegisterControl dann verschiebt sich die Registerseite natürlich mit, und auch das Unterformular-Control. Soweit alles OK.
ABER das Formular innerhalb des Unterformular-Controls bleibt einfach stehen, fällt also sprichwörtlich aus dem Rahmen mit dem es sich mitbewegen sollte.
Kann man sehr leicht reproduzieren. Kollegen habe mir auch bestätigt dass dieses Verhalten in der neuesten Version 2019 ebenso vorhanden ist.

Nun, tun kann man daran nichts, aber es ist gut dass man das weiß. Ich habe es jetzt so gelöst dass ich hier jetzt kein Register einsetze sondern nur das Unterformular-Control und dieses dann hier verschiebe. Zum Testen hab ich euch ein kleines Beispiel gemacht mit mehreren Optionen:
Testdatenbank

Bis dahin
©2020 Andreas Vogt

Ersatz für DLookup() Funktion

By , 11. Januar 2020

Hallo,
über 4 Jahre Pause mit Access Bloggen – doch heute kommt wieder mal ein erstes Lebenszeichen von mir 😉
Es geht um Domänenfunktionen die eigentlich jeder kennt, wie z.B. Dlookup() oder DCount(). Da diese – zumindest in den Access-Versionen bis 2010 welche ich verwende – wenig performant in bezug auf Geschwindigkeit sind gibt es auch seit vielen Jahren Ersatzfunktionen. Ich selbst verwende immer wieder eine Version die mal von Sascha Trowitzsch (siehe MS-Office-Forum.net) erstellt wurde.
Diese liefert genau 1 Diskreten Wert zurück.
Oftmals braucht man aber nicht einer sondern mehrere Feldwerte eines Datensatzes – daher habe ich die Funktion etwas umgestrickt – ich nenne sie jetzt mal DSLookup(), und das sieht dann so aus:

Public Function DSLookup(ByVal Expression As String, ByVal Domain As String, Optional ByVal Criteria As String) As Variant
    Dim strSQL As String
    Dim retArr As Variant
    Dim ExpressionCounter As Long
    Dim i As Long

    ExpressionCounter = UBound(Split(Trim(Expression), ","))
    strSQL = "SELECT " & Expression & " FROM " & Domain
    If Not Criteria = vbNullString Then strSQL = strSQL & " WHERE " & Criteria

    If ExpressionCounter = 0 Then
        DSLookup = DBEngine(0)(0).OpenRecordset(strSQL, dbOpenForwardOnly)(0)
    Else
        ReDim retArr(ExpressionCounter)
        With DBEngine(0)(0).OpenRecordset(strSQL, dbOpenForwardOnly)
            For i = 0 To ExpressionCounter
                retArr(i) = .Fields(i)
            Next i
        End With
        DSLookup = retArr
    End If
End Function

Die Parameter sollten selbsterklärend sein, sind die selben wie bei der Eingebauten DLookup() Funktion.
Der Rückgabewert ist jetzt vom Typ Variant, d.H. es können einzelne Werte als auch ganze Arrays zurückgegeben werden.

Der Beginn ist noch ziemlich einfach gehalten, es wird ein Abfragestring in der Variable strSQL gebildet, und je nach dem ob der optionale Parameter „Criteria“ angegeben wurde wird auch eine Where-Klausel angehängt mit diesem Parameter.
Je nach dem ob in Expression ein einzelnes Tabellenfeld oder eine Kommaliste an Tabellenfeldern steht wird danach unterschieden. ExpressionCounter ermittelt über die Split()-Funktion die Anzahl der Tabellenfelder.
Bei einem einzelnen Feld ist der Wert 0, und es wird die Methode OpenRecordset auf das Datenbankobjekt mit der entsprechenden Abfrage angewendet. Die (0) am Ende liefert genau den ersten gefundenen Eintrag. Fragt jetzt nicht warum ich DBEngine(0)(0) und nicht CurrentDB verwende, ersteres soll(te) bei bestimmten Datenbankabläufen schneller sein. Wer das genauer wissen möchte kann hier mal nachlesen: https://www.access-programmers.co.uk/../
Falls mehrere Tabellenfelder im Parameter „Expression“ stehen, wird zuerst das Rückgabe-Array Dimensioniert und in einer For/Next Schleife dieses mit Werten belegt.

Das wars auch schon.
Der Aufruf ist auch ziemlich simple wie nachfolgendes Beispiel zeigt:

Private Sub GetUsername()
    Dim Userdata As Variant
    Userdata = DSLookup("Vorname, Nachname", "dbo_User", "ID=21")
    MsgBox Userdata(0) & " " & Userdata(1)
End Sub

Aus der Tabelle dbo_User wird der Vorname und Nachname des Users mit ID 21 in das Array Userdata geschrieben und ausgegeben.

Alternativen und verschiedene Spielarten gibts natürlich auch noch. Z.B. könnte man ein leeres fertig Dimensioniertes Array ByRef an die Prozedur übergeben. Es wird dann aber vom Entwickler auch Selbstdisziplin verlangt – weil die Prozedur verlangt dann auch je nach Dimensionierung die korrekte Anzahl an Feldern im Recordsetobjekt. Aber wenn man bei klarem Kopf während des Entwickelns ist kann man sowas schon mal machen. Die Prozedur verkürzt sich dadurch stark auf fast die Hälfte:

Public Sub DSLookup(ByVal Expression As String, ByVal Domain As String, ByVal Criteria As String, ByRef retArr As Variant)
    Dim strSQL As String
    Dim i As Long

    strSQL = "SELECT " & Expression & " FROM " & Domain
    If Not Criteria = vbNullString Then strSQL = strSQL & " WHERE " & Criteria

    With DBEngine(0)(0).OpenRecordset(strSQL, dbOpenForwardOnly)
        For i = 0 To UBound(retArr)
            retArr(i) = .Fields(i)
        Next i
    End With
End Sub

Der Aufruf ist jetzt ein klein wenig anderst, und zwar ist der Rückgabewert über das Array jetzt zwingend ein Array, und dieses muss auch dimensioniert sein zumindest der 0 für 1 Element. Und auch bei der Ausgabe/Weitergabe ist zu beachten dass auch nur bei einem einzelnen Tabellenfeld jetzt ein Array angesprochen werden muss mit dem ID 0.

Private Sub GetUsername()
    Dim Userdata(0) As Variant
    DSLookup "Vorname", "dbo_User", "ID=21", Userdata
    MsgBox Userdata(0)
End Sub

So, das wars jetzt aber erstmal für Heute, es wird sicherlich nicht weitere 4 Jahre dauern bis ihr was von mir hört.

Bis dahin
©2020 Andreas Vogt

Umgang mit globalen Variablen

By , 28. August 2015

Hallo,
heute möchte ich euch einen etwas anderen Umgang von globalen Variablen zeigen.
Wie diese definiert werden sollte bekannt sein, in einem Standardmodul erstellt man im Deklarationsbereich mittels dem Schlüsselword „Global“ oder „Public“ eine globale Variable die dann überall verwendet werden kann. Und genau dieser Vorteil ist auch der größte Nachteil. Diese Variablen können temporär von jeder Stelle aus überschrieben werden, auch durch die Deklaration von privaten Variablen gleichen Namens.
Das bedeutet, wenn Sie eine globale Variable haben, in z.B. einem Formularmodul eine private Variable gleichen Namens deklarieren, dann verwenden Sie innerhalb des Gültigkeitsbereichs der privaten Variable diese so als ob es keine globale Variable gleichen Namens gäbe. Sie können sich nicht mehr sicher sein welchen Inhalt die Variable z.Z. besitzt.

Gerade bei größeren Projekten erzeugen globale Variablen chaos, das Sie später kaum nachvollziehen können.
Das ist mir gestern erst selbst passiert bei einem VB.Net Projekt. Gewöhnt euch schon mal an den Gedanken globale Variablen nicht mehr zu verwenden!
Aber was ist die Alternative? Die Antwort ist so simple. Verwendet statt dessen globale Properties in einer Klasse.

Das funktioniert ziemlich simple:
Erstellt ein leeres Klassenmodul, benennt es z.B. „globVar“.
Dann erstellt ihr eure Properties, für jede globale Varialbe ein Get/Let Paar, bzw. bei Objektvariablen ein Get/Set-Paar, und jeweils dazu 1 private Membervariable.
Hier mal ein Beispiel:

Option Compare Database
Option Explicit
Private m_distance As Long
Private m_strsql AS String
Private m_frm AS Form

Public Property Get Distance() As Long
    Distance = m_distance 
End Property
Public Property Let Distance (ByVal value As Long)
    m_distance = value
End Property

Public Property Get strSQL() As Long
    strSQL = m_strsql 
End Property
Public Property Let strSQL(ByVal value As Long)
    m_strsql = value
End Property

Public Property Get FRM() As Form
    FRM = m_frm 
End Property
Public Property Set FRM(ByRef value As Form)
    m_frm = value
End Property

Verwendet wird es wie folgt:
In einem Modul wird das Klassenobjekt global deklariert:
Public globe As globVar
Dies sollte die einzige global deklarierte Variable sein, die du in deinem Projekt verwendest. In dem Codeteil der zuerst gestartet wird, z.B. das Form_Load() Ereignis eines Formulares, wir das Klassenobjekt gesetzt:
Set globe = New globVar

Jetzt können wir mit den Properties arbeiten wie mit Variablen:

Sub SetValues()
    With globe
        .Distance = 997
        .strSQL = "Select * From Tabelle1"
        Set .FRM = Form_Formular1
    End With
End Sub

Sub DataFormOpen()
    With globe
        .FRM.RecordSource = .strSQL
        DoCmd.OpenForm .FRM.Name
    End With
End Sub

Was zum Schluss noch fehlen würde, ist das löschen der Objektvariable beim Schließen des Programms, aber das ist nicht nötig da danach keine Variablen mehr existieren können.
ist doch ne coole Sache – oder? Kein Chaos mehr mit globalen Variablen!

Es macht zwar ein wenig mehr Aufwand, aber man kann sich das Leben etwas vereinfachen wenn man die MZ-Tools verwendet, einen Codeblock erstellt, und diesen dann per Tastenkombi einfach in den Code einfügt und den Variablennamen überschreibt. Aber probiert es einfach aus, der Aufwand wird sich lohnen und er wird zur Stabilität eurer Anwendung beitragen.

Bei sehr großen Projekten macht es übrigends Sinn für jeden Variablentyp eine eigene Klasse anzulegen, so behält man den Überblick wenn man einen Fehler sucht oder was ändern möchte.

Noch was zum Schluss: seit ihr in mehreren Entwicklungsumgebungen zuhause, dann könnt ihr das Prinzip meist direkt übertragen. Zumindest was VB6 angeht kann man per Copy+Paste das 1:1 übernehmen. In VB.Net sehen die Properties etwas anderst aus (besser!), aber das Prinzip ist das gleiche.

Bis dahin
©2015 Andreas Vogt

Excel-Dokumente aus Access heraus formatieren

By , 27. Juli 2015

Hallo,
angenommen ihr exportiert Daten von Access nach Excel und möchtet nach Bearbeitung das Excel-Sheet ausdrucken.
Das Anpassen der Seitenränder für den Druck kann man nämlich auch gleich aus Access heraus erledigen.
Dazu benötigen wir zuerst den Verweis auf die aktuelle Microsoft Excel Library.

Danach benötigen wir die 3 Excel-Objekte für die Anwendung, das Workbook und das Worksheet:

    Dim xlApp As Excel.Application
    Dim xlBook As Workbook
    Dim xlSheet As Worksheet

    Set xlApp = CreateObject("Excel.Application")
    Set xlBook = xlApp.Workbooks.Open("c:\output.xlsx")
    Set xlSheet = xlBook.Worksheets(1)

Das Anwendungsobjekt ist definiert, das Workbook geöffnet und der Verweiss auf das erste Arbeitsblatt gesetzt. Jetzt gehts los:

    With xlSheet.PageSetup
        .LeftHeader = "&""Arial,Standard""&8&F;"
        .CenterHeader = "&""Arial,Standard""&8&A;"
        .RightHeader = "&""Arial,Standard""&8&D;"
        .LeftFooter = ""
        .CenterFooter = ""
        .RightFooter = "&""Arial,Standard""&8Seite &P;"
        .LeftMargin = xlApp.InchesToPoints(0.5)
        .RightMargin = xlApp.InchesToPoints(0.25)
        .TopMargin = xlApp.InchesToPoints(0.75)
        .BottomMargin = xlApp.InchesToPoints(0.75)
        .HeaderMargin = xlApp.InchesToPoints(0.5)
        .FooterMargin = xlApp.InchesToPoints(0.5)
        ....
    End With

Man kann also alles einstellen was auch in Excel einzustellen geht. Zu beachten ist, dass die Angaben in Points zu machen sind. Dazu kann man die Excel-Funktion InchesToPoints() verwenden.
Seht euch die Schreibweise für die Schriftart und -Größe an. Beachtet die doppelte Anführungszeichen.
Man kann auch bequem Einstellungen wie Druckqualität, Papiergröße oder Seitenzahlen einfügen wie oben im rechten Footer.

Bis dahin
© 2015 Andreas Vogt

Datentypen und Berechnungen

By , 25. Juli 2015

Hallo,
heute möchte ich euch ein paar Fallstricke zeigen in die man bei Berechnungen innerhalb VBA tappen kann.
Doch Zuvor unsere kleine Testprozedur:

Sub berechnen()
    Dim i As Long, k As Long
    i = 1 * 64000
    k = 2 * 32000
End Sub

Wir setzen auf die Prozedur einen Haltepunkt und starten sie.
Ihr werdet sehen dass i korrekt berechnet wird, aber bei k ein Überlauf-Fehler kommt.
Der Grund liegt in der Größe der Zahlen.

Macht einfach mal folgendes:
Haltepunkt setzen und Prozedur starten.
Markiert 1 * 64000, rechte Maustaste klicken und auf „Überwachung hinzufügen“ klicken.
Im Fenster für Überwachungsausdrücke seht ihr jetzt als Datentyp „Long“ stehen.
Dann macht das gleiche für 2 * 32000. Jetzt sehr ihr als Datentyp „Integer“.

Das bedeutet, dass obwohl ihr die Variablen als Long deklariert habt, die Berechnung selbst unter einem anderen Datentyp laufen kann. Access nimmt sich hier einfach die größte Zahl aus der Berechnung und ermittelt anhand deren den Datentyp. Und 32000 passt nunmal ziemlich gut in den signed Integer Wertebereich hinein.

Hier ein paar gebräuchliche Datentypen mit ihren Wertebereichen:

Byte:     -128 bis 127 bzw. unsigned von 0 bis 255
Integer:  -32768 bis 32767 bzw. unsigned von 0 bis 65535
Long:     −2.147.483.648 bis 2.147.483.647 bzw. unsignet von 0 bis 4.294.967.295

Um jetzt das ganze noch verwirrender zu machen folgende Multiplikationen:

i = 2.1 * 32000
k = 2 = 32000

Wie Wunder gibt es bei i keinen Überlauf-Fehler, bei k aber schon. Wieder als Überwachungsausdruck eingefügt zeigt der Debugger nun für 2.1 * 32000 jetzt als Datentyp Double an.
Das liegt an der Nachkommastelle, die VBA dazu bewegt hat den Double-Datentyp zu verwenden.

Um nun aber 2 * 32000 berechnen zu können gibts einfach einen Trick.
entweder man schreibt hinter die 2 das Formarzeichen „#“ oder man schreibt einfach 2.0, welches automatisch auch in ein 2# umgewandelt wird.
Werden nun aber Variablen multipliziert, so ist der deklarierte Datentyp der Variablen maßgebend.

Ihr seht, eine kleine Berechnung kann ganz schön Kopfzerbrechen verursachen, aber nun wisst ihr wie ihr das umschiffen könnt.

Bis dahin
© 2015 Andreas Vogt

OfficeFolders theme by Themocracy