[VB6] CommandButton with image and text: No UCs, ActiveX, or OwnerDraw/subclassing
Most of the solutions to place an image on a button either use a control or owner drawing. If all you want is a simple image button, with the image on the left and text on the right, it turns out all you need to do is call BM_SETIMAGE. Don't even need to set the style to graphical, or change the style with API. Transparency is preserved, and the button style doesn't change like it does if you set it to 'graphical' in vb6; so if you're using xp style manifests the button still stays that style.
A bonus with this sample, it shows how the icon you use can be stored in a resource file, as a custom resource, which bypasses VB's limitations. You can use any valid Windows icon, with any size (or multiple sizes) and any color depth, and even more, this sample will load the size closest to what you requested.
To use this sample, create a project with a form with a command button, and a module. Add a new resource file, then choose add custom resource (NOT icon or bitmap), and name it something like "ICO_01" as the id.
Then, this code is for the form, and all you need is this one line for any command button:
Code:
Option Explicit
Private Sub Form_Load()
Call SendMessage(Command1.hWnd, BM_SETIMAGE, IMAGE_ICON, ByVal ResIconTohIcon("ICO_01"))
End Sub
and this is the code for the module:
Code:
Option Explicit
Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, _
Source As Any, _
ByVal Length As Long)
Public Declare Function CreateIconFromResourceEx Lib "user32.dll" (ByRef presbits As Any, _
ByVal dwResSize As Long, _
ByVal fIcon As Long, _
ByVal dwVer As Long, _
ByVal cxDesired As Long, _
ByVal cyDesired As Long, _
ByVal flags As Long) As Long
Public Declare Function SendMessage Lib "User32" Alias "SendMessageA" (ByVal hWnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
lParam As Any) As Long
Private Type IconHeader
ihReserved As Integer
ihType As Integer
ihCount As Integer
End Type
Private Type IconEntry
ieWidth As Byte
ieHeight As Byte
ieColorCount As Byte
ieReserved As Byte
iePlanes As Integer
ieBitCount As Integer
ieBytesInRes As Long
ieImageOffset As Long
End Type
Public Const BM_SETIMAGE = &HF7
Public Const IMAGE_BITMAP = 0
Public Const IMAGE_ICON = 1
Public Function ResIconTohIcon(id As String, Optional cx As Long = 24, Optional cy As Long = 24) As Long
'returns an hIcon from an icon in the resource file
'For unknown reasons, this will not work with the 'Icon' group in the res file
'Icons must be added as a custom resource
Dim tIconHeader As IconHeader
Dim tIconEntry() As IconEntry
Dim MaxBitCount As Long
Dim MaxSize As Long
Dim Aproximate As Long
Dim IconID As Long
Dim hIcon As Long
Dim I As Long
Dim bytIcoData() As Byte
On Error GoTo e0
bytIcoData = LoadResData(id, "CUSTOM")
Call CopyMemory(tIconHeader, bytIcoData(0), Len(tIconHeader))
If tIconHeader.ihCount >= 1 Then
ReDim tIconEntry(tIconHeader.ihCount - 1)
Call CopyMemory(tIconEntry(0), bytIcoData(Len(tIconHeader)), Len(tIconEntry(0)) * tIconHeader.ihCount)
IconID = -1
For I = 0 To tIconHeader.ihCount - 1
If tIconEntry(I).ieBitCount > MaxBitCount Then MaxBitCount = tIconEntry(I).ieBitCount
Next
For I = 0 To tIconHeader.ihCount - 1
If MaxBitCount = tIconEntry(I).ieBitCount Then
MaxSize = CLng(tIconEntry(I).ieWidth) + CLng(tIconEntry(I).ieHeight)
If MaxSize > Aproximate And MaxSize <= (cx + cy) Then
Aproximate = MaxSize
IconID = I
End If
End If
Next
If IconID = -1 Then Exit Function
With tIconEntry(IconID)
hIcon = CreateIconFromResourceEx(bytIcoData(.ieImageOffset), .ieBytesInRes, 1, &H30000, cx, cy, &H0)
If hIcon <> 0 Then
ResIconTohIcon = hIcon
End If
End With
End If
On Error GoTo 0
Exit Function
e0:
Debug.Print "ResIconTohIcon.Error->" & Err.Description & " (" & Err.Number & ")"
End Function
Thanks to Leandro Ascierto for the basis of the code to load an icon from a resource file into memory.
Requires Common Controls 6.0 manifest, CommandButton style must be Normal, NOT Graphical.
Last edited by fafalone; May 4th, 2014 at 10:37 PM.
Re: [VB6] CommandButton with image and text: No UCs, ActiveX, or OwnerDraw/subclassin
Can you clarify, on XP have you tried it when you're not using OwnerDraw?
The BM_SETIMAGE (and all other API) lists compatibility as Win2000 Pro and above; I'll look into whether it behaves differently on earlier systems but do let me know if you've tried non-owner draw themed xp. The platform I made it on was Win7 x64 ultimate with common control manifests for both ide and compiled (which, and I'll edit the first post to reflect this, is a requirement-- doesn't work without CC6 manifest).
Re: [VB6] CommandButton with image and text: No UCs, ActiveX, or OwnerDraw/subclassin
Originally Posted by fafalone
A bonus with this sample, it shows how the icon you use can be stored in a resource file, as a custom resource, which bypasses VB's limitations.
Icon resources seems to work just fine when loaded via the LoadImage function. The attached demo project below shows 4 ways of associating an icon with a CommandButton by sending the BM_SETIMAGE message.
Originally Posted by wqweto
Does not work on themed XP.
Someone mentioned the following in the Community Additions section of the BM_SETIMAGE message:
Originally Posted by Jakub Nietrzeba
BM_SETIMAGE on button without BS_ICON or BS_BITMAP flag has no effect on Windows XP
On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
Re: [VB6] CommandButton with image and text: No UCs, ActiveX, or OwnerDraw/subclassin
Icon resources seems to work just fine when loaded via the LoadImage function.
The issue isn't loading an icon resource once it's there... the issue is VB will not let you add icons with large sizes and color depths to the icon resources group. Maybe you could force it in with an external resource editor, but this is a much easier solution.
Re: [VB6] CommandButton with image and text: No UCs, ActiveX, or OwnerDraw/subclassin
Originally Posted by fafalone
... the issue is VB will not let you add icons with large sizes and color depths to the icon resources group.
Or more accurately, the IDE's Resource Editor add-in doesn't know about such icon sizes and depths, and consequently doesn't allow them.
Originally Posted by fafalone
Maybe you could force it in with an external resource editor, but this is a much easier solution.
There's no need to resort to third-party resource editors since the underlying resource compiler (RC.EXE) is already capable of compiling the newer icon sizes and depths. So, instead of managing resources through the IDE's very limited Resource Editor add-in, you just need to write a resource script (and optionally, a batch file) and then compile the resource file and add it manually to a VB6 project. Sure, there are more steps involved, but the benefits are definitely worth it. Personally, I only use the IDE's Resource Editor for quick projects that I won't be saving. For most projects, I prefer to manually create a resource file and add it to the project. The attached demo project in this post has an example of how I usually compile icon resources.
On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0