Working with Icons in Visual Basic
Pages: 1, 2, 3, 4
Retrieving a handle to the icon we want seems straightforward, and indeed it is. The difficulty arises, however, when we try to use our icon as if it were an icon. Most code samples (actually, all of the code samples that I've seen) show how you can write an icon to a PictureBox control using the Win32 DrawIcon function. The problem, though, is that PictureBox is a COM component, and it expects to be provided an object of type StdPicture to display. DrawIcon is a non-COM function, and it is able to draw our icon in the PictureBox control only because the control provides a handle to a device context. But once the icon is written, there's nothing further that you can do with it. Because COM was circumvented when the icon was drawn, the PictureBox control doesn't even know it's there. Attempts to access it produce a syntax error.
Fortunately, the COM Automation library provides a solution. It lets us convert our icon handle into an object of type StdPicture and then directly assign it to a form's Icon property. To do this, we use the OleCreatePictureIndirect method, which has the following syntax:
Public Declare Sub OleCreatePictureIndirect Lib "oleaut32.dll" ( _
ByRef lpPictDesc As PictDesc, _
ByVal riid As Guid, _
ByVal fOwn As Long, _
ByRef lplpvObj As StdPicture)
The members of the PICTDESC structure describe the picture. The structure is defined as follows:
Public Type PictDesc
cbSizeofStruct As Long ' Set to Len(PictDesc)
picType As Long ' Set to vbPicTypeIcon
hImage As Long ' Set to hIcon
xExt As Long ' Horizontal size in twips
yExt As Long ' Vertical size in twips
End Type
We can supply arbitrary values for the size members.
The second parameter to the OleCreatePictureIndirect method is an IID, or a globally unique identifier (GUID) that identifies the StdPicture object's interface, named IPicture. The IID of the IPicture interface is {7BF80980-BF32-101A-8BBB-00AA00300CAB}. We can supply it to a GUID data structure, which breaks up the GUID into distinct numeric components and is defined as follows:
Public Type Guid
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type
The function's third parameter is a Boolean value that indicates whether the application is to own the GDI picture handle (or in our case the icon handle). We should set this to True and remember to call DestroyIcon once when we are finished with our icon, either before we assign a new icon or when our form closes.
The final parameter is a pointer to a StdPicture object. This is an out parameter; we pass the method an uninitialized picture object and receive back a StdPicture object representing the icon we've extracted. To simplify the method call, we can wrap the OleCreatePictureIndirect method in the following function:
Public Function ConvertIconHandle(hIcon As Long) As StdPicture
Dim iid As Guid
Dim icondesc As PictDesc
Dim icn As StdPicture
iid.Data1 = &H7BF80980
iid.Data2 = &HBF32
iid.Data3 = &H101A
iid.Data4(0) = &H8B
iid.Data4(1) = &HBB
iid.Data4(2) = &H0
iid.Data4(3) = &HAA
iid.Data4(4) = &H0
iid.Data4(5) = &H30
iid.Data4(6) = &HC
iid.Data4(7) = &HAB
OleCreatePictureIndirect icondesc, iid, False, icn
Set ConvertIconHandle = icn
End Function
We can then assign the icon dynamically with code like the following, which prompts the user for the index to an icon resource in Shell32.dll and then assigns that icon to the form's Icon property:
Private Sub cmdChangeIcon_Click()
Dim ndxIcon As Integer
Dim hIcon As Long, hOldIcon As Long
Dim hwnd As Long, hWndCur As Long
Dim strPath As String, strIndex As String
Dim gd As Guid
Dim icn As StdPicture
strPath = Space(MAX_PATH + 1)
' Get folder location and form path/filename
SHGetFolderPath Me.hwnd, CSIDL_SYSTEM, vbNull, SHGFP_TYPE_CURRENT, strPath
PathAppend strPath, "shell32.dll"
strIndex = InputBox("Enter icon index: ", "Icon in Shell32.dll", 0)
If strIndex = vbNullString Or Not IsNumeric(strIndex) Then Exit Sub
If nHandleCtr > 0 Then DestroyIconHandles
ndxIcon = CInt(strIndex)
' Retrieve Icon
hIcon = ExtractIcon(App.hInstance, strPath, ndxIcon)
If hIcon = 0 Or hIcon = vbNull Then
MsgBox "Invalid icon"
Exit Sub
Else
IconHandles(nHandleCtr) = hIcon
nHandleCtr = nHandleCtr + 1
End If
' Display icon in picture box
'DrawIcon Me.picIcon.hDC, 0, 0, hIcon
'Me.picIcon.Refresh
Set icn = ConvertIconHandle(hIcon)
Set Me.Icon = icn
' Assign icon
hOldIcon = SetClassLong(Me.hwnd, GCL_HICONSM, hIcon)
If hOldIcon = 0 Then
Dim errCode As Long
Dim sBuffer As String
sBuffer = Space(256)
errCode = GetLastError()
If errCode > 0 Then
MsgBox FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, ByVal 0&, _
errCode, 0&, sBuffer, Len(sBuffer), ByVal 0)
End If
Else
DestroyIcon hOldIcon
End If
' Force redrawing of frame
SetWindowPos Me.hwnd, HWND_TOP, 0, 0, 0, 0, _
SWP_NOMOVE Or SWP_NOSIZE Or SWP_NOZORDER Or SWP_FRAMECHANGED
' Get top-level (hidden) window handle
hWndCur = Me.hwnd
Do
hWndCur = GetWindowLong(hWndCur, GWL_HWNDPARENT)
If hWndCur > 0 Then hwnd = hWndCur
Loop While hWndCur > 0
hOldIcon = SetClassLong(hwnd, GCL_HICONSM, hIcon)
If hOldIcon > 0 Then DestroyIcon hOldIcon
' Notify of change in large, small icons
SendMessage hwnd, WM_SETICON, ICON_SMALL, hIcon
SendMessage hwnd, WM_SETICON, ICON_BIG, hIcon
End Sub
In order to dynamically change the window icon, we must do more than simply assign a new StdPicture object representing our icon to the window's Icon property. In the call to SetClassLong, we notify Windows that the icon has changed, and then we call SetWindowPos to force a repainting of the nonclient area. This takes care of changing our System Small icon. But when you're working with Visual Basic, more is involved in changing the application icon.
Visual Basic has a hidden top-level application window that provides the icon to the taskbar, the Alt-Tab dialog box, and the Windows shell. In changing our window icon, we haven't changed these icons. In order to do that, we have to retrieve the handle of the application's top-level window by calling GetWindowLong until the function returns a 0. We can then call SendMessage to notify Windows that the large and the small icons for the top-level window have changed.
Ron Petrusha is the author and coauthor of many books, including "VBScript in a Nutshell."
Return to WindowsDevCenter.com.
-
VB icon on desktop
2007-01-06 08:23:20 jbkit [View]

