Visual Basic List API
BitBlt: Present a Non-Selectable 'No Data' Picture in a ListBox
Posted:   Saturday November 29, 2003
Updated:   Monday December 26, 2011
Applies to:   VB4-32, VB5, VB6
Developed with:   VB6, Windows XP
OS restrictions:   None
Author:   VBnet - Randy Birch


VBnet CoolTabs
GetTextExtentPoint32: Right-Align List Box Data
SetWindowLong: Right-Align List Box Data and/or the Scrollbar
SetWindowLong: Right-Align List Contents in a Combo
SendMessage: Align Text Box Contents Using Tabstops

SendMessage: Align List Box Contents Using Tabstops
WM_LBUTTONDOWN: Substitute a Tabbed List for a Combo's Dropdown List
WM_LBUTTONDOWN: Substitute a ListView for a Combo's Dropdown List

The VBnet page SendMessage: Add a Background Image to a ListView showed how to use SendMessage with the LVBKIMAGE structure to display images in the data window of a listview control.  Although Windows' normal list box does not support such functionality, we can still fake this functionality using the BitBlt API to impress an image on the hDC of a normal VB list box data window.

While this method will allow the presentation of an image along with selectable list data, unlike the listview control there is no mechanism provided by which the image will scroll or act as a watermark as the user navigates the list. However, this technique could be useful for displaying a corporate logo on start-up when the list is empty, or - as shown here - to add a message to the list indicating the list was not ready to use.

The traditional way to do this is to utilize the list's AddItem method to add a single list item to an empty list (i.e. "(no data)") and then perform a check in the list's click event to determine if the list contains data and if the string selected was the 'no data' entry.  The method shown here actually uses BitBlt to apply a bitmap image to the list's hDc (as shown), which means the data displayed is no longer limited strictly to strings. Furthermore, unlike the aforementioned method to determine the selection, clicking an empty list with only the image applied (as shown) will not generate a click. A further bonus is the list will not display a focus rect when the Blt'ted image is displayed.

The three buttons on the form perform slightly different functions to demo the options available. 'Fill List' is straightforward - 20 numbers are added to the list at each press. The 'No data' button executes the routine to display the no data message only when the list does not contain data (the bForceClear flag in the call is False). The 'Clear list' button calls the same routine as the 'No data' button, but passes True when the list contains data, forcing a List1.Clear and subsequent display of the 'no data' message. Note that, at a given point, the control can only display either the Blt'ted image or the list items - in other words, if you modify the code to apply an image or text via BitBlt to a list containing data, the data will be erased when the image is applied. Selecting a list item will destroy the image at that line. To see this in effect, comment out the 'If List1.ListCount = 0 Then' line in Command1.

The code below could be applied to other controls through appropriate modification. 

 BAS Module Code

 Form Code
Add a listbox (List1), three command buttons (Command1, Command2, Command3), and a picture box (Picture1) to the form. The code takes care of setting the captions and sizing/setting up the picture box. Add the following code:

Option Explicit
' Copyright 1996-2011 VBnet/Randy Birch, All Rights Reserved.
' Some pages may also contain other copyrights by the author.
' Distribution: You can freely use this code in your own
'               applications, but you may not reproduce 
'               or publish this code on any web site,
'               online service, or distribute as source 
'               on any media without express permission.
Private Const SRCCOPY = &HCC0020
Private Declare Function GetDC Lib "user32" _
  (ByVal hwnd As Long) As Long
Private Declare Function BitBlt Lib "gdi32" _
   (ByVal hDestDC As Long, _
    ByVal x As Long, _
    ByVal y As Long, _
    ByVal nWidth As Long, _
    ByVal nHeight As Long, _
    ByVal hSrcDC As Long, _
    ByVal xSrc As Long, _
    ByVal ySrc As Long, _
    ByVal dwRop As Long) As Long
Private Declare Function ReleaseDC Lib "user32" _
  (ByVal hwnd As Long, _
   ByVal hdc As Long) As Long

Private Const HORZRES = 8
Private Const VERTRES = 10
Private Declare Function GetDeviceCaps Lib "gdi32" _
  (ByVal hdc As Long, _
   ByVal nIndex As Long) As Long

Private Sub Form_Load()

  'set up controls
   Command1.Caption = "No data"
   Command2.Caption = "Fill list"
   Command3.Caption = "Clear list"
  'Optional - force the form on-screen
  'then execute the 'no data' routine
  'so the message appears at startup.
  'A doevents is required here.
   Command1.Value = True
End Sub

Private Sub Command1_Click()

   If List1.ListCount = 0 Then
      BoxShot Picture1, List1, "No data - a category must be selected first.", False
      MsgBox "Nope ... the list contains data!"
   End If
End Sub

Private Sub Command2_Click()

  'demo - add dummy data
   Dim x As Long
   For x = 1 To 20
      List1.AddItem x
End Sub

Private Sub Command3_Click()

   BoxShot Picture1, List1, "No data - a category must be selected first.", (List1.ListCount > 0)
End Sub

Private Sub BoxShot(ctlMsgSource As PictureBox, _
                    ctlTarget As ListBox, _
                    msg As String, _
                    bForceClear As Boolean)

   Dim hDcTarget As Long
   Dim hDcSource As Long
   Dim w As Long
   Dim h As Long
   If bForceClear Then
      If TypeOf ctlTarget Is ListBox Then
      End If   'If TypeOf
   End If   'If bForceClear
  'set up and create message to display
  'in the listbox
   If TypeOf ctlMsgSource Is PictureBox Then
      With ctlMsgSource
         .Visible = False
         .BorderStyle = 0
         .Appearance = 0
         .AutoRedraw = True
         .BackColor = ctlTarget.BackColor
         .Width = ctlTarget.Width
         .Height = .TextHeight("A")
         .Font.Name = ctlTarget.Font.Name
         .Font.Size = ctlTarget.Font.Size
         'the print command can not be
         'used with a 'With' block! (try
         'deleting the 'ctlMsgSource' below -
         'an error occurs.
          ctlMsgSource.Print msg
      End With
     'need the hDcs for the controls
      hDcTarget = GetDC(ctlTarget.hwnd)
      hDcSource = ctlMsgSource.hdc

      If hDcTarget Then
         If hDcSource Then
           'get the size of the pixbox, in pixels
            w = ctlMsgSource.Width \ Screen.TwipsPerPixelX
            h = ctlMsgSource.Height \ Screen.TwipsPerPixelY
           'blt the message onto the list hDc
            BitBlt hDcTarget, 0, 0, w, h, hDcSource, 0, 0, SRCCOPY
         End If  'If hdcSource
         ReleaseDC 0, hDcTarget
      End If   'If hdcTarget
   End If   'If TypeOf

End Sub


PayPal Link
Make payments with PayPal - it's fast, free and secure!


Copyright 1996-2011 VBnet and Randy Birch. All Rights Reserved.
Terms of Use  |  Your Privacy



Hit Counter