oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

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
      IconHandles(nHandleCtr) = hIcon
      nHandleCtr = nHandleCtr + 1
   End If
   ' Display icon in picture box
   'DrawIcon Me.picIcon.hDC, 0, 0, hIcon
  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
      DestroyIcon hOldIcon
   End If
   ' Force redrawing of frame
   SetWindowPos Me.hwnd, HWND_TOP, 0, 0, 0, 0, _

   ' Get top-level (hidden) window handle
    hWndCur = Me.hwnd
       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