Visual Basic Common Control API Routines
SendMessage: Simulate 'TopIndex' in ListViews
     
Posted:   Sunday January 25, 2004
Updated:   Monday December 26, 2011
     
Applies to:   VB4-32, VB5, VB6
Developed with:   VB4-32, Windows XP
OS restrictions:   None
Author:   VBnet - Randy Birch
     
 Prerequisites
None.

A listview control's EnsureVisible method provides a means to bring a referenced item into view. Ditto, the LVM_ENSUREVISIBLE message simply "ensures that a list-view item is either entirely or partially visible, scrolling the list-view control if necessary."

If the item of interest is back up the list (its index is less than the index of the current top item) EnsureVisible dutifully scrolls the item into view and positions it at the top of the control. Similarly, if the index of interest is later in the listing calling EnsureVisible just brings the item into view as the bottom-most item.

Ironically, amongst the plethora of Windows' listview messages there is one to allow programmers to determine the index of the item at the top of the control (LVM_GETTOPINDEX). But unlike a listbox, there is no message to set it.

This code, using a combination of the LVM_GETTOPINDEX and LVM_GETCOUNTPERPAGE messages, along with the control's intrinsic behaviour when EnsureVisible is called, remedies that oversight! The EnsureVisible method's quirk of never doing more work than it needs to and always positioning the EnsureVisble'd item to the top or bottom of the control (when the item was outside the viewport) can be used to our advantage here.

The demo uses the control's FindItem method to locate a particular string match in the main column, then calls ListView_SetTopIndex the item to the top. The label on the form simply reflects the new top index resulting from the call (not the selected item index). The demo also creates the listview columns, view and data, as well as labels all controls, so just toss the controls mentioned below onto a form and run.

Illustration 1 shows the item at index at 100 set to the top. Illustration 2 shows how the top index command is handled when the control contains insufficient items to move the desired index to the top. Illustration 3 shows the effect of setting the top index to an item earlier in the list.

 BAS Module Code
None.

 Form Code
Add a listview (Listview1), a label (Label1) and three command buttons (Command1, Command2, Command3) to a form and 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 LVM_FIRST = &H1000
Private Const LVM_GETTOPINDEX = (LVM_FIRST + 39)
Private Const LVM_GETCOUNTPERPAGE As Long = (LVM_FIRST + 40)

Private 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 Sub Form_Load()

   Dim itmx As ListItem
   Dim cnt As Long
   
   With ListView1
      .ColumnHeaders.Add , , "main"
      .ColumnHeaders.Add , , "sub 1"
      .ColumnHeaders.Add , , "sub 2"
      .ColumnHeaders.Add , , "sub 3"

      For cnt = 1 To 200
         Set itmx = .ListItems.Add(, , "main item" & CStr(cnt))
         itmx.SubItems(1) = "subitem 1," & CStr(cnt)
         itmx.SubItems(2) = "subitem 3," & CStr(cnt)
         itmx.SubItems(3) = "subitem 4," & CStr(cnt)
      Next

     .SortKey = 0
     .Sorted = False
     .View = lvwReport
     .FullRowSelect = True
     .LabelEdit = lvwManual
   
   End With
   
   Command1.Caption = "mid-way"
   Command2.Caption = "item 197"
   Command3.Caption = "item 2"
   
End Sub


Private Sub Command1_Click()

   Dim itmx As ListItem
   Dim topIndex As Long

   Set itmx = ListView1.FindItem("main item100", lvwText, 1, lvwPartial)
   
   If Not itmx Is Nothing Then
      topIndex = ListView_SetTopIndex(ListView1, itmx.Index)
      itmx.Selected = True
   End If

   Label1.Caption = topIndex
   
End Sub


Private Sub Command2_Click()

   Dim itmx As ListItem
   Dim topIndex As Long
   
   Set itmx = ListView1.FindItem("main item197", lvwText, 1, lvwPartial)
   
   If Not itmx Is Nothing Then
      topIndex = ListView_SetTopIndex(ListView1, itmx.Index)
      itmx.Selected = True
   End If

   Label1.Caption = topIndex

End Sub


Private Sub Command3_Click()

   Dim itmx As ListItem
   Dim topIndex As Long
   
   Set itmx = ListView1.FindItem("main item2", lvwText, 1, lvwPartial)
   
   If Not itmx Is Nothing Then
      topIndex = ListView_SetTopIndex(ListView1, itmx.Index)
      itmx.Selected = True
   End If

   Label1.Caption = topIndex

End Sub


Private Sub ListView1_ColumnClick(ByVal ColumnHeader As ColumnHeader)

  'sort the items
   ListView1.SortKey = ColumnHeader.Index - 1
   ListView1.SortOrder = Abs(Not ListView1.SortOrder = 1)
   ListView1.Sorted = True
  
End Sub


Private Function ListView_GetTopIndex(hwndlv As Long) As Long

   ListView_GetTopIndex = SendMessage(hwndlv, _
                                      LVM_GETTOPINDEX, _
                                      0&, _
                                      ByVal 0&)
  
End Function


Private Function ListView_GetVisibleCount(ByVal hwndlv As Long) As Long
  
   ListView_GetVisibleCount = SendMessage(hwndlv, _
                                          LVM_GETCOUNTPERPAGE, _
                                          0&, _
                                          ByVal 0&)
   
End Function


Private Function ListView_SetTopIndex(lv As ListView, ByVal itemToTop As Long) As Long

   Dim lvItemsPerPage As Long
   Dim lvNeededItems As Long
   Dim lvCurrentTopIndex As Long
   
  'determine if desired index + number
  'of items in view will exceed total
  'items in the control
   lvCurrentTopIndex = ListView_GetTopIndex(lv.hwnd) + 1 '0-based!
   lvItemsPerPage = ListView_GetVisibleCount(lv.hwnd)
   lvNeededItems = (itemToTop - lvItemsPerPage)
   
  'is current index above or below
  'desired index?
   If lvCurrentTopIndex > itemToTop Then
    
     'it is above the desired index, so
     'scroll up. The item will automatically
     'be positioned at the top
      lv.ListItems((itemToTop)).EnsureVisible
        
   ElseIf (itemToTop - lvCurrentTopIndex) >= lvItemsPerPage Then
      
      'it's below, so based on whether there
      'are sufficient items to set to the topindex ...
       If (itemToTop + lvItemsPerPage) > lv.ListItems.Count Then
      
         'it is below but it can't be set to
         'the top as the control has insufficient
         'items, so just scroll to the end of listview
          lv.ListItems(lv.ListItems.Count).EnsureVisible
          
       Else
       
         'it is below, and since a listview
         'always moves the item just into view,
         'have it instead move to the top by
         'faking item we want to 'EnsureVisible'
         'the item lvItemsPerPage -1 below the actual
         'index of interest.
         lv.ListItems((itemToTop + lvItemsPerPage) - 1).EnsureVisible
      
       End If
   
   End If
   
  'return the 1-based top index
  'as sign of success.
   ListView_SetTopIndex = ListView_GetTopIndex(lv.hwnd) + 1
  
End Function
 Comments

 
 

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