Individuelle Formular-Icons ohne Zusatzdateien

Im Beitrag “Icons in Access-Formularen und Berichten” (www.access-im-unternehmen.de/1235) haben wir schon einmal eine Möglichkeit aufgezeigt, wie man Formulare und Berichte in Access mit individuellen Icons ausstatten kann. So kann man beispielsweise ein Formular zum Bearbeiten eines Kunden mit dem gleichen Icon ausstatten, das man auch für die Schaltfläche zum Öffnen dieses Formulars im Ribbon untergebracht hat. Der Benutzer kann so noch besser erkennen, worum es im Formular geht. In der vorherigen Fassung der Lösung hatten wir allerdings noch das Problem, dass wir die Icons, die links oben in Formularen und Berichten erscheinen sollten, noch im Dateisystem speichern mussten. Das kann aus diversen Gründen zu Problem führen und daher sind wir froh, hier den nächsten Schritt gehen zu können: Das direkte Einlesen der Icons aus der Tabelle “MSysResources” und anschließendes Anzeigen in Formulare und Berichten.

Ziel: Formular-Icons ohne externe Dateien

Wie bereits erwähnt, sind wir bereits in der Lage, Formulare mit einem eigenen Icon auszustatten (siehe Bild 1). Das Problem ist jedoch bisher gewesen, dass die entsprechende Icon-Datei sich auf der Festplatte befinden muss, damit wir diese laden und per API dem Formular als Icon zuweisen konnten. Damit wir nicht für jede Anwendung einen eigenen Ordner bereitstellen mussten, der die benötigten Icon-Dateien enthält oder sogar die Icon-Dateien lose im Verzeichnis der Access-Datenbank gespeichert haben, befanden sich die .ico-Dateien bis zur Verwendung in der Tabelle USysResources und wurden erst dann zur Anwendung exportiert.

Formular mit individuellem Icon

Bild 1: Formular mit individuellem Icon

Nunmehr haben wir den noch fehlenden Schritt realisiert: Wir können nun die Icon-Dateien direkt aus dem Anlagefeld der Ressourcen-Tabelle auslesen und dem jeweiligen Formular zuweisen. Wie das gelingt, lesen Sie in diesem Beitrag.

Code zum Anzeigen eines Formularicons

Der Code sieht im Wesentlichen wie in Listing 1 aus. Im oberen Teil finden wir einige Konstanten-Deklarationen, die wir im weiteren Verlauf benötigen.

Option Compare Database
Option Explicit
Public Const C_IMAGE_ICON = 1
Public Const C_LR_LOADFROMFILE = &H10
Public Const C_WM_SETICON = &H80
Public Const ICRESVER As Long = &H30000
Public Const LR_DEFAULTSIZE As Long = &H40
Public Const WM_SETICON As Long = &H80
Public Const ICON_SMALL As Long = 0
Public Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As LongPtr, _
     ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr
Public Declare PtrSafe Function CreateIconFromResourceEx Lib "user32.dll" (presbits As Any, dwResSize As Any, _
     ByVal fIcon As Long, ByVal dwVer As Long, ByVal cxDesired As Long, ByVal cyDesired As Long, _
     ByVal flags As Long) As LongPtr
Public Function SetFormIconFromMSysResources(ByVal hWnd As Long, ByVal strIconName As String) As Boolean
     Dim hIcon As LongPtr
     Dim bData() As Byte
     Dim dwOffset As LongPtr
     Dim dwSize As LongPtr
     Dim lngIndex As Long
     Dim lngID As Long
     lngIndex = 0
     lngID = Nz(DLookup("id", "MSysResources", "name=''" & strIconName & "''"), 0)
     If Not lngID = 0 Then
         bData() = BLOB2Binary0710("MSysResources", "Data", "Id", lngID, True)
         dwSize = VarPtr(bData(16& * lngIndex + 14&)) 
         dwOffset = VarPtr(bData(bData(16& * lngIndex + 18&))) 
         hIcon = CreateIconFromResourceEx(ByVal dwOffset, ByVal dwSize, 1, ICRESVER, 0&, 0&, LR_DEFAULTSIZE)
         If hIcon Then
             SendMessage hWnd, WM_SETICON, ICON_SMALL, ByVal hIcon
         End If
     Else
         MsgBox "Icon ''" & strIconName & "'' fehlt in der Tabelle MSysResources", vbExclamation + vbOKOnly, "Icon fehlt"
     End If
End Function

Listing 1: Code zum Bereitstellen von Icons in Formularen und Berichten

Darunter sehen wir die Deklaration von zwei API-Funktionen, die uns in der folgenden Prozedur unterstützen.

Schließlich folgt die Funktion SetFormIconFromMSysResources, welche die eigentliche Arbeit übernimmt. Die Funktion hat zwei Parameter:

  • hWnd: Das Handle des Fensters, für das das Icon gesetzt werden soll. Das ist normalerweise das Handle des Access-Formulars oder -Berichts.
  • strIconName: Der Name des Icons, dessen ID in der MSysResources-Tabelle gesucht wird.

Die Funktion verwendet die folgenden Variablen:

  • hIcon: Speichert das Handle des eingelesenen Icons.
  • bData: Ein Byte-Array, das die Binärdaten des Icons aus der MSysResources-Tabelle aufnimmt.
  • dwOffset: Speichert das Offset des Icons.
  • dwSize: Speichert die Größe des Icon-Ressourcenblocks.
  • lngIndex: Index des Icons in der .ico-Datei. Eine .ico-Datei kann mehrere Icons enthalten.
  • lngID: Primärschlüsselwert des Eintrags der Tabelle MSysResources mit dem gesuchten Icon.

Die Funktion setzt den Index für das aus der .ico-Datei zu verwendende Icon mit lngIndex auf 0. Der Parameter strIconName der Funktion liefert den Wert des Feldes Name der Tabelle MSysResources, dessen Anlage aus dem Feld Data als Icon verwendet werden soll.

Die Funktion holt per DLookup den Wert des Feldes id für diesen Datensatz und speichert diesen in der Variablen lngID.

Die folgende If…Then-Bedingung prüft, ob der gesuchte Datensatz gefunden werden konnte, anderenfalls erscheint eine entsprechende Meldung.

Anschließend folgt die Extraktion der Icon-Daten aus dem Feld Data. Das erledigen wir mit einem Aufruf der Funktion BLOB2Binary0710 aus dem Modul mdlImages, der wir den Namen der Tabelle, das Anlagefeld, das Feld mit dem Primärschlüssel und den Primärschlüsselwert übergeben. Die Icon-Daten landen danach in einem Byte-Array namens bData. Für dieses Array ermitteln wir nun die Größe und das Offset im Speicher und speichern beides in den Variablen dwSize und dwOffset.

Schließlich folgt der Einsatz der API-Funktionen. Die erste heißt CreateIconFromResourceEx und liefert uns ein Handle auf das Icon. Dieses Handle setzen wir schließlich mit der Funktion SendMessage als Icon des Formulars oder Berichts mit dem Handle aus hWnd ein.

Einsatz der Funktion

Den Aufruf der Funktion SetFormIconFromMSysResources fügen wir nun in die Ereignisprozedur Form_Load des Formulars oder Berichts ein, der ein Icon aus der Tabelle MSysResources anzeigen soll. Das sieht beispielsweise wie folgt aus:

Private Sub Form_Open(Cancel As Integer)
     Call SetFormIconFromMSysResources(Me.hWnd, "alarmclock")
End Sub

Dazu muss nun nur noch die Tabelle MSysResources einen Datensatz enthalten, der für das Feld Name den Wert alarmclock aufweist und dessen Feld data eine entsprechende .ico-Datei enthält. Das ist in unserer Datenbank der Fall (siehe Bild 2).

Das anzuzeigende Icon in der Tabelle MSysResources

Bild 2: Das anzuzeigende Icon in der Tabelle MSysResources

Voraussetzungen für die .ico-Dateien

Nun sind allerdings leider nicht alle .ico-Dateien für die Anzeige als Icon eines Formulars oder Berichts geeignet. Wir benötigen eine bestimmte Auflösung, sonst wird das Icon nicht korrekt angezeigt. Dazu gehören beispielsweise Auflösungen mit 8- oder 24-Bit. 4-Bit reichen nicht aus.

Hinzufügen der .ico-Dateien

Leider können wir die .ico-Dateien nicht auf die gleiche einfache Weise wie .png-Dateien zur Tabelle MSysResources hinzufügen. Dazu brauchten wir einfach nur ein Formular in der Entwurfsansicht zu öffnen und dann ein Bild zum Formular oder zu einer Schaltfläche hinzuzufügen.

Die dabei ausgewählten Dateien werden automatisch als .png-Dateien in der Tabelle MSysResources hinterlegt und können dann in den entsprechenden Eigenschaften der Steuerelemente ausgewählt werden. Wenn wir hier .ico-Dateien auswählen, werden diese als .png-Dateien importiert. .png-Dateien können wir leider nicht als Icon von Formularen und Berichten einsetzen.

.ico-Dateien manuell hinzufügen

Also haben wir zwei Möglichkeiten. Die erste ist, die .ico-Dateien manuell zur Tabelle MSysResources hinzuzufügen. Das gelingt mit den folgenden Schritten:

  • Wir öffnen die Tabelle MSysResources in der Datenblattansicht.
  • Wir klicken für einen neuen, leeren Datensatz doppelt auf das Anlage-Feld.
  • Im Dialog Anlegen klicken wir auf Hinzufügen… und wählen die gewünschten .ico-Datei aus, zum Beispiel apple.ico.
  • Danach tragen wir für das Feld Extension den Wert ico, für Name den Wert apple und für Type den Text img ein.

Danach können wir die Funktion SetFormIconFromMSysResources für dieses Icon aufrufen:

Call SetFormIconFromMSysResources(Me.hWnd, "apple")

Diese Vorgehensweise ist allerdings nicht besonders komfortabel, wenn wir mehrere .ico-Dateien zur Tabelle MSysResources hinzufügen wollen. Also schreiben wir uns schnell Code, um den Vorgang zu automatisieren.

Mehrere .ico-Dateien gleichzeitig in die Tabelle MSysResources einlesen

Was benötigen wir dazu als Erstes? Einen Dateiauswahl-Dialog, mit dem wir mehrere Dateien aus einem Verzeichnis auswählen können. Die notwendigen Elemente finden wir in der Office-Bibliothek, die wir wie in Bild 3 per Verweis referenzieren.

Verweis auf die Office-Bibliothek

Bild 3: Verweis auf die Office-Bibliothek

.ico-Dateien auswählen

Danach können wir uns eine Funktion namens SelectICOFiles wie in Listing 2 zusammenstellen. Diese nimmt mit dem Parameter strFolder ein Verzeichnis entgegen, das als Startverzeichnis des Dateiauswahl-Dialogs verwendet wird. Zuerst werden das Objekt objFiledialog und zwei Variablen für die Verarbeitung der Pfade zu den .ico-Dateien deklariert. Mit der Variablen objFiledialog referenzieren wir den Dateiauswahl-Dialog, den wir mit der Methode Filedialog des Application-Objekts initialisieren. Dabei übergeben wir den Wert msoFileDialogFilePicker als Parameter.

Public Function SelectICOFiles(Optional strFolder As String) As String
      Dim objFiledialog As Office.FileDialog
      Dim varFilename As Variant
      Dim strFilenames As String
      Set objFiledialog = Application.FileDialog(msoFileDialogFilePicker)
      objFiledialog.AllowMultiSelect = True
      objFiledialog.InitialFileName = strFolder
      objFiledialog.Filters.Clear
      objFiledialog.Filters.Add "Icon-Files", "*.ico"
      If objFiledialog.Show = True Then
          For Each varFilename In objFiledialog.SelectedItems
              strFilenames = strFilenames & ";" & varFilename
          Next varFilename
          If Len(strFilenames) > 0 Then
             strFilenames = Mid(strFilenames, 2)
          End If
      End If
      SelectICOFiles = strFilenames
End Function

Listing 2: Funktion zum Auswählen der .ico-Dateien

Für den Dialog legen wir mit AllowMultiSelect fest, das mehrere Dateien gleichzeitig ausgewählt werden können. Außerdem stellen wir den Startordner mit InitialFileName ein. Wir leeren die Filter mit der Clear-Methode der Filters-Auflistung und fügen einen neuen Filter für .ico-Dateien hinzu. Dann zeigen wir den Dialog mit der Show-Methode an. Liefert diese den Wert True zurück, hat der Benutzer den Dialog geschlossen und wir werten die gewählten Einträge in einer For Each-Schleife über die Elemente der Auflistung SelectedItems aus.

Dabei stellen wir in strFilenames eine durch Semikola getrennte Liste von Elementen zusammen und geben diese als Funktionsergebnis zurück.

Damit können wir nun .ico-Dateien wie in Bild 4 auswählen.

Auswahldialog für .ico-Dateien

Bild 4: Auswahldialog für .ico-Dateien

.ico-Dateien einlesen

Danach verwenden wir eine Prozedur namens ICOToMSysResources, um die mit dem Dateiauswahl-Dialog selektierten Dateien in die Tabelle MSysResources einzulesen (siehe Listing 3).

Public Sub ICOToMSysResources(Optional bolOverwrite As Boolean)
     Dim db As DAO.Database
     Dim rst As DAO.Recordset
     Dim rstData As DAO.Recordset
     Dim strFileList As String
     Dim strFiles() As String
     Dim strFile As String
     Dim strName As String
     Dim i As Integer
     Set db = CurrentDb
     strFileList = SelectICOFiles(CurrentProject.Path)
     If Len(strFileList) > 0 Then
         strFiles = Split(strFileList, ";")
         Set rst = db.OpenRecordset("SELECT * FROM MSysResources", dbOpenDynaset)
         For i = LBound(strFiles) To UBound(strFiles)
             strFile = strFiles(i)
             strName = Mid(strFile, InStrRev(strFile, "\") + 1)
             strName = Replace(strName, ".ico", "")
             rst.FindFirst "Name = ''" & strName & "''"
             If Not (rst.NoMatch = False And bolOverwrite = False) Then
                 If rst.NoMatch = True Then
                     rst.AddNew
                 Else
                     rst.Edit
                 End If
                 rst!Name = strName
                 rst!Extension = "ico"
                 rst!Type = "img"
                 Set rstData = rst("Data").Value
                 On Error Resume Next
                 rstData.Delete
                 On Error Goto 0
                 rstData.AddNew
                 rstData("FileData").LoadFromFile strFile
                 rstData.Update
                 rst.Update
             End If
         Next i
     End If
End Sub

Listing 3: Prozedur zum Einlesen von .ico-Dateien in die Tabelle MSysResources

Die Prozedur hat einen Parameter namens bolOverwrite. Mit diesem legen wir fest, ob vorhandene Elemente, die den gleichen Namen im Feld Name aufweisen, überschrieben werden sollen.

Neben der Variablen db zum Referenzieren des aktuellen Database-Objekts verwenden wir zwei Recordset-Variablen. Die Erste namens rst referenziert ganz normal die Tabelle MSysResources.

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

den kompletten Artikel im PDF-Format mit Beispieldatenbank

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar