|
Den oben stehenden Dialog kennen Sie sicher. Über ihn können
Sie das Icon einer Verknüpfung in den Datei-Eigenschaften im
Explorer ändern. Enthält eine Datei eigene Icons, werden diese
zunächst zur Verfügung gestellt. Enthält sie keine Icons, werden
nach der Anzeige einer Warnung die Icons aus der Datei Shell32.DLL
gezeigt. Und schließlich können Sie in dem Dialog auch noch eine
ganz andere Datei als Lieferanten eines Icons ausgucken.
Sie könnten ihn sogar für eigene Zwecke selber aufrufen, wenn
Sie die dafür zuständige API-Funktion wüssten. Nun, sie könnte
beispielsweise SHChangeIconDialog heißen. Könnte?
Ja, die zuständige API-Funktion ist nicht nur nicht
dokumentiert. Sie hat eigentlich noch nicht einmal einen Namen,
sondern nur eine so genannte Ordinal-Nummer in der Funktionstabelle.
Da Visual Basic eine solche Nummer glücklicherweise zumindest als
Alias einer Funktions-Deklaration akzeptiert, steht dem nichts im
Wege, sie VB-konform zu taufen und zu verwenden. Ich habe sie im
Stil der übrigen Shell-Dialog-Funktionen SHChangeIconDialog
genannt. Sie fragen noch nach den Funktions-Parametern? Kein Problem
- hier haben Sie die vollständige Deklaration:
Private Declare Function SHChangeIconDialog Lib "shell32" _
Alias "#62" (ByVal hOwner As Long, ByVal szFilePath As String, _
ByVal Reserved As Long, lpIndex As Long) As Long
Fragen Sie mich jetzt bitte nicht, woher ich die Deklaration
habe. Sie schwirrt im Internet umher. Und genau so wie ich, hätten
auch Sie schon (sogar mehrfach) darüber stolpern können...
Die Funktion sieht so eigentlich ganz gebrauchsfertig aus. Im
Parameter hOwner übergeben sie das Handle des Fensters, an dem sich
der Dialog ausrichten soll. Übergeben Sie hier 0, wird
der Desktop zum Besitzer und der Dialog landet weit links oben im
Bildschirm. Im zweiten Parameter szFilePath übergeben Sie den
vollständigen Pfadnamen der Datei, deren Icons Sie einsehen
möchten. Den dritten Parameter, Reserved, ignorieren Sie einfach
und übergeben in jedem Fall lediglich den Wert 0. Im
vierten, letzten Parameter übergeben Sie den Index des Icons, das
beim Start des Dialogs vorausgewählt sein soll.
Erste Experimente mit dieser Funktion werden Ihnen zeigen, dass
das so weit ganz gut klappt - solange tatsächlich die Icons der
übergebenen Datei angezeigt werden.
Wie nämlich bereits Eingangs gesagt, können auch die Icons
einer ganz anderen Datei zur Anzeige kommen. Die Vermutung liegt
nahe, dass der geänderte Dateiname im Parameter szFilePath
zurückgegeben werden könnte. Auf den ersten Blick scheint das auch
zu stimmen, im Prinzip jedenfalls. Doch wenn der vollständige Pfad
der zurückgegebenen Datei länger als der Pfad der ursprünglich
übergebenen Datei ist, wird er auf deren Länge zurückgestutzt.
Wie in solchen Fällen üblich, müssen Sie einen Puffer verwenden,
der ausreichend lang ist. Solch ein Puffer ist nichts anderes, als
ein ebenso genügend langer String. In der Regel wird für
Dateipfade eine Länge von 260 angenommen. In diesen Puffer kopieren
Sie den ursprünglichen Dateipfad, an den Sie zuvor noch ein
Null-Zeichen (Ascii-Code 0, VB-Konstante vbNullChar)
anhängen, damit Windows den C-Konventionen
entsprechend weiß, wo der Dateipfad aufhört.
Das Kopieren erledigen Sie ganz einfach mit der Mid$-Zuweisung,
bei der Sie die Start-Position auf 1 setzen, ohne eine
Länge anzugeben. Fehlt nämlich die Längenangabe, nimmt Visual
Basic automatisch die Länge des zugewiesenen Strings an und lässt
den Rest des Ausgangsstrings unberührt.
Nach dem Aufruf von SHChangeIconDialog enthält der Puffer-String
gegebenenfalls den neuen Dateipfad. Dieser ist ebenfalls mit einem
Null-Zeichen abgeschlossen, wenn er kürzer als der Puffer ist. Sie
brauchen also nur mit InStr die Position des Null-Zeichens zu
ermitteln und den Puffer mit der Left$-Funktion
entsprechend zu kürzen.
Die Rückgabe des gewählten Indexes erfolgt nach dem gleichen
Prinzip. Da aber Long-Werte immer gleich lang sind, erübrigt sich
die Pufferschieberei. Der Parameter lpIndex gibt schlichtweg den
gewählten Index zurück, ob vom Anwender geändert oder nicht.
In eine handliche Funktion gepackt (sie gibt False zurück, wenn
der Anwender den Dialog abgebrochen hat, anderenfalls True), sieht
das folgendermaßen aus:
Private Declare Function SHChangeIconDialog Lib "shell32" _
Alias "#62" (ByVal hOwner As Long, ByVal szFilePath As String, _
ByVal Reserved As Long, lpIcon As Long) As Long
Private Const MAX_PATH = 260
Public Function ChangeIconDialog(FilePath As String, Index As Long, _
Optional ByVal OwnerWnd As Long) As Boolean
Dim nIndex As Long
Dim nFilePath As String
Dim nOK As Boolean
Dim nPos As Integer
nIndex = Index
nFilePath = Space$(MAX_PATH)
Mid$(nFilePath, 1) = FilePath & Chr$(0)
nOK = CBool(SHChangeIconDialog(OwnerWnd, nFilePath, 0, nIndex))
If nOK Then
nPos = InStr(nFilePath, vbNullChar)
If nPos Then
FilePath = Left$(nFilePath, nPos - 1)
Else
FilePath = nPos
End If
Index = nIndex
End If
ChangeIconDialog = nOK
End Function
So weit, so gut. Nun haben Sie also den Dateipfad und den Index
eines sich in dieser Datei steckenden Icons. Damit können Sie in
Visual Basic aber noch gar nichts anfangen Es sei denn, Ihnen
reichen diese beiden Informationen völlig aus, um sie etwa per Code
in eine Verknüpfung zu schreiben. Anderenfalls sind weitere Griffe
in die API-Kiste notwendig, um ein in VB darstellbares Icon zu
erhalten.
Der nächste Schritt ist also, das unter der angegebenen
Index-Position in der Datei befindliche Icon zu extrahieren. Ich
habe bewusst das Fremdwort "extrahieren" gewählt.
Übersetzt ins Englische wird nämlich "extract" daraus,
und fast schon auf den ersten Blick werden Sie in der Liste der
API-Funktionen die Funktionen ExtractIcon
und ExtractIconEx
sehen. Auf die nächsten zwei Blicke hin, nämlich die in die
MSDN-Dokumentation zu den beiden Funktionen, scheint die erstere
Funktion erheblich einfacher aufzurufen zu sein, während die zweite
recht kompliziert klingt.
Widerstehen Sie der Versuchung der Bequemlichkeit und verwenden
Sie die zweite Funktion. Die erste stammt nämlich noch aus der Zeit
vor Windows 95, als es Icons nur in der einen Größe
von 32x32 Pixels gab. Die zweite Funktion kann dagegen
ganz nach Wunsch das kleine 16x16-Icon, das 32x32-Icon
oder auch beide zugleich liefern. Die beiden Größen liegen, so
denn beide vorhanden sind, von außen betrachtet an der gleichen
Index-Position.
Private Declare Function ExtractIconEx Lib "shell32.dll" _
Alias "ExtractIconExA" (ByVal lpszFile As String, _
ByVal nIconIndex As Long, phiconLarge As Long, phiconSmall As Long, _
ByVal nIcons As Long) As Long
Wenn Sie Lust haben, können Sie mit der Funktion ExtractIconEx
ein wenig herumexperimentieren und die in der Dokumentation
beschriebenen Möglichkeiten, auch gleich mehrere Icons
hintereinander oder gar alle Icons zu extrahieren, umzusetzen
versuchen. Ein kleiner Tipp dazu: Nehmen Sie alles wörtlich und
übergeben Sie statt Arrays als solchen tatsächlich nur das jeweils
erste Element, etwa LargeIcons(0) und/oder
SmallIcons(0). Da wir hier aber nur genau ein Icon
brauchen, gegebenenfalls in beiden Größen, sparen wir uns die
Arrays und verwenden einfache Long-Variablen, die die Handles der
Icons aufnehmen. Aus API-Sicht ist eine einfache Variable nichts
anderes als ein Array mit nur einem einzigen Element. Es geht
einfach nur um die Speicheradresse der Variablen oder eben des
ersten Elements als Anfangsadresse des Arrays - und deswegen will
Windows ja auch separat wissen, um wie viele Elemente es sich
handeln soll.
Nun haben Sie also ein oder zwei Icon-Handles und können in
Visual Basic immer noch nichts damit anfangen, nicht wahr? Keine
Bange - die Lösung der letzten Teilaufgabe halten wir schon seit
längerem in unserem Tipp-Sortiment bereit: "Vom
Handle zum Picture". Und das war's denn auch. Sie brauchen
lediglich sowohl für das kleine als auch das große Icon jeweils
einmal die Funktion PictureFromHandle aufzurufen und dabei als
Bildtyp vbPicTypeIcon anzugeben - und schon haben Sie beide Icons
als VB-bequeme Picture-Objekte zur Hand.
Damit es noch bequemer wird, packen wir auch die Ausleserei in
eine Funktion, und stecken auch gleich die Möglichkeit, den
Auswahl-Dialog in einem Gang aufzurufen, mit hinein. Im Prinzip
reicht es, die folgende Funktion IconFromFile mit der Angabe des
Dateipfades und der Option ShowDialog = True
aufzurufen, um im einfachsten Fall den Anwender ein großes Icon
auswählen zu lassen.
Wie bei der oben stehenden Funktion (sie wird hier in der
Funktion intern aufgerufen) können Sie aber auch die Vorauswahl
durch die Übergabe einer Index-Position festlegen. Dazu können Sie
im Parameter IconSize eine der drei Konstanten aus der Enumeration
IconSizeConstants angeben, um das kleine oder das große Icon an der
ausgewählten Position direkt als Picture-Obnjekt zu erhalten, oder
beide Icons in einem Array mit zwei Elementen (Array-Index 1
und 2). Und wie bei ChangeIconDialog können Sie
schließlich noch das Fenster-Handle übergeben.
Public Enum IconSizeConstants
isLarge
isSmall
isBoth
End Enum
Public Function IconFromFile(FilePath As String, _
Optional Index As Long, _
Optional ByVal IconSize As IconSizeConstants, _
Optional ByVal ShowDialog As Boolean, _
Optional ByVal OwnerWnd As Long) As Variant
Dim nIconLarge As Long
Dim nIconSmall As Long
Dim nFilePath As String
Dim nIndex As Long
Dim nIcons() As StdPicture
nFilePath = FilePath
nIndex = Index
If ShowDialog Then
If ChangeIconDialog(nFilePath, nIndex, OwnerWnd) = False Then
Exit Function
End If
FilePath = nFilePath
Index = nIndex
End If
Select Case IconSize
Case isLarge
ExtractIconEx nFilePath, nIndex, nIconLarge, 0, 1
Set IconFromFile = PictureFromHandle(nIconLarge, vbPicTypeIcon)
DestroyIcon nIconLarge
Case isSmall
ExtractIconEx nFilePath, nIndex, 0, nIconSmall, 1
Set IconFromFile = PictureFromHandle(nIconSmall, vbPicTypeIcon)
DestroyIcon nIconSmall
Case isBoth
ReDim nIcons(1 To 2)
ExtractIconEx nFilePath, nIndex, nIconLarge, nIconSmall, 1
Set nIcons(1) = PictureFromHandle(nIconLarge, vbPicTypeIcon)
Set nIcons(2) = PictureFromHandle(nIconSmall, vbPicTypeIcon)
IconFromFile = nIcons
DestroyIcon nIconLarge
DestroyIcon nIconSmall
End Select
End Function
|