Control the HTML Help Viewer OCX control

Gustav BrockMVP
Edited by: Andrew Leniart
It is not that simple from VBA to display a compressed help file as it should be. API calls and a few tricks are needed. This demo and full code will show you how.

Help for the user

Years ago, a printed manual and a comprehensive on-line help - a help file - were mandatory parts of any decent application for commercial usage.

Both are rare these days, as applications are expected to be self-explanatory, or the user is directed to obtain help from an online forum or a downloaded PDF.

Still, a good and context-sensitive help file can be the optimum help for the user, and - if present - the fastest method, ready at hand as it is; no time wasted on browsing and sorting good info from all the irrelevant stuff.

Writing a good help file is an art in itself, which not will be touched here. However, if this is what you need, a good place to start is one of these sites (examples only, no affiliation to these): HelpScribble or Innovasys Help Studio, which is also the origin of the sample help file used here (see Download later).

Viewing the help file

The help file itself is of a special compressed format - with the extension .chm - which requires a special viewer. That viewer is often the native viewer of Windows, Microsoft HTML Help Viewer, an OCX control.

From VBA, the viewer is opened via one of two API calls to the control, hhctrl.ocx, which normally is present on all newer Windows installs. This would be very simple, if not for these traps:

  • If a context ID is passed for no purpose, the application may crash
  • If the help viewer is not closed before exiting the application, the application may crash
  • the help viewer will not by itself move to the Contents tab when a context is opened

The documentation on the control is sparse and rudimentary, almost nil, and these traps are to be found by trial and error or to be extracted bit by bit at various fora and blog posts. However, once taken care of, the usage is straight-forward.

The API calls

These days, API calls should be declared to work in both 32- and 64-bit VBA environments, as more and more corporations move to the 64-bit version of Microsoft Office.


This is done in the header of the code module, and also a special UDT (User Defined Type) for the search function is declared:

' API calls for the HTML Help Viewer control.
' Sample help file for download:
' Open a compiled HTML help file (.chm) or close all opened help files.
#If VBA7 Then
    Private Declare PtrSafe Function HTMLHelpShowContents Lib "hhctrl.ocx" Alias "HtmlHelpA" ( _
        ByVal hwnd As LongPtr, _
        ByVal lpHelpFile As String, _
        ByVal wCommand As Long, _
        ByVal dwData As Long) _
        As Long
    Private Declare Function HTMLHelpShowContents Lib "hhctrl.ocx" Alias "HtmlHelpA" ( _
        ByVal hwnd As Long, _
        ByVal lpHelpFile As String, _
        ByVal wCommand As Long, _
        ByVal dwData As Long) _
        As Long
#End If
' Open a compiled HTML help file (.chm) with the Search tab active.
#If VBA7 Then
    Private Declare PtrSafe Function HTMLHelpShowSearch Lib "hhctrl.ocx" Alias "HtmlHelpA" ( _
        ByVal hwnd As LongPtr, _
        ByVal lpHelpFile As String, _
        ByVal wCommand As Long, _
        ByRef dwData As HhFtsQuery) _
        As Long
    Private Declare Function HTMLHelpShowSearch Lib "hhctrl.ocx" Alias "HtmlHelpA" ( _
        ByVal hwnd As Long, _
        ByVal lpHelpFile As String, _
        ByVal wCommand As Long, _
        ByRef dwData As HhFtsQuery) _
        As Long
#End If

' User Defined Types.
' UDT for HTMLHelpShowSearch.
Private Type HhFtsQuery
    cbStruct          As Long       ' Size of structure in bytes.
    fUniCodeStrings   As Long       ' TRUE if all strings are unicode.
    pszSearchQuery    As String     ' String containing the search query.
    iProximity        As Long       ' Word proximity.
    fStemmedSearch    As Long       ' TRUE for StemmedSearch only.
    fTitleOnly        As Long       ' TRUE for Title search only.
    fExecute          As Long       ' TRUE to initiate the search.
    pszWindow         As String     ' Window to display in.
End Type

The interesting parameter is wCommand which controls what the viewer control does and how it appears. It must have one of a few special values which - for ease of use and to prevent errors - has been collected in an Enum:

' Commands to control the appearance of the viewer when launched.
Public Enum hhCommand
    ' Select the last opened tab.
    DisplayTopic = &H0
    ' Select the Contents tab.
    DisplayContents = &H1
    ' Select the Index tab.
    DisplayIndex = &H2
    ' Select the Search tab.
    DisplaySearch = &H3
    ' Select the Contents tab and open a topic by its index.
    OpenContext = &HF
    ' Close all windows opened by the viewer.
    CloseAll = &H12
End Enum

Now, everything is prepared.

Operating the Help Viewer control

One function does it all, controlled by the key parameter: Command.

The next parameters are HelpFile to specify to help file to open. The last parameter, Context, is used to pass the  Context ID of a specific help page that should be opened, ready to be read.

Some typical examples for the usage are listed in the header of the function, HelpControl:

' Displays a compressed HTML help file using the HTML Help File Viewer.
' Alternatively, closes all windows of the viewer that may be open.
' Returns True if the operation was successful.
' Returns False if the operation couldn't be carried out, for example
' if one or more parameter is invalid, or - for closing windows - if
' no windows were open.
' Example, display the Contents tab:
'   Success = HelpControl(DisplayContents, "d:\path\HelpStudioSample.chm")
' Example, display the Contents tab and open the page with context id 7:
'   Success = HelpControl(OpenContext, "d:\path\HelpStudioSample.chm", 7)
' Example, display the Index tab:
'   Success = HelpControl(DisplayIndex, "d:\path\HelpStudioSample.chm")
' Example, display the Search tab:
'   Success = HelpControl(DisplaySearch, "d:\path\HelpStudioSample.chm")
' Example, display the last opened tab and the main page:
'   Success = HelpControl(DisplayTopic, "d:\path\HelpStudioSample.chm")
' Example, close all opened Help Viewer windows:
'   Success = HelpControl(CloseAll)
' Note:
'   If the help file is stored on a networked folder and will open, but
'   will not display the context pages, move the file to a local folder.
' 2018-04-26. Gustav Brock, Cactus Data ApS, CPH.
Public Function HelpControl( _
    ByVal Command As hhCommand, _
    Optional ByVal HelpFile As String, _
    Optional ByVal Context As Long) _
    As Boolean
    ' Use default owner handle (Application).
    Const OwnerHandle   As Long = 0

    ' Neutral values.
    Const NoHandle      As Long = 0
    Const NoTopic       As Long = 0
    Const NoFile        As String = ""

    ' Handle of the current Help Viewer window (if any).
    Static OpenHandle   As Long
    Dim SearchQuery     As HhFtsQuery
    Dim Handle          As Long
    ' Manage the Help Viewer.
    Select Case Command
        ' Open the Help Viewer and display a tab.
        Case hhCommand.DisplayTopic, _
            hhCommand.DisplayContents, _
            Handle = HTMLHelpShowContents(OwnerHandle, HelpFile, Command, NoTopic)
        ' Open the Help Viewer and display the topic having the ID of Context.
        Case hhCommand.OpenContext
            ' Reset displayed tab to Contents.
            Handle = HTMLHelpShowContents(OwnerHandle, HelpFile, hhCommand.DisplayContents, NoTopic)
            ' Open help context page.
            Handle = HTMLHelpShowContents(OwnerHandle, HelpFile, Command, Context)
        ' Open the Help Viewer and display the Search tab.
        Case hhCommand.DisplaySearch
            SearchQuery.cbStruct = Len(SearchQuery)
            Handle = HTMLHelpShowSearch(OwnerHandle, HelpFile, Command, SearchQuery)
        ' Close all windows opened by the Help Viewer.
        Case hhCommand.CloseAll
            If OpenHandle = NoHandle Then
                ' Don't waste time on closing non-existing windows.
                ' A help file has been opened.
                ' Set Handle to return success.
                Handle = OpenHandle
                ' Make sure, all help windows are closed, and reset OpenHandle.
                OpenHandle = HTMLHelpShowContents(OwnerHandle, NoFile, Command, NoTopic)
            End If
        Case Else
            ' Ignore.
    End Select
    If Command <> hhCommand.CloseAll Then
        If Handle <> NoHandle Then
            ' Store the handle of the window.
            OpenHandle = Handle
        End If
    End If
    ' Return True if success.
    HelpControl = (Handle <> NoHandle)

End Function

The in-line comments explain the details of the operation but pay attention to two details.

First, the command OpenContext causes two calls of the API. First, it sets the viewer to display tab Contents, then it opens the specific context page. This is to avoid that the context page is opened while, say, the Search tab is displayed, which would confuse the user.

Second, the function maintains hold of the handle (the "ID") of the help window opened with the static variable OpenHandle

This will be 0 (zero) if no help window has been opened. If that is the case, there is no need to call the close command which, otherwise, would take about half second. Not much but, in some cases, that is exactly the level of delay, that could make the application appear "slow" to the user.

An Access example

In the download, a small Access application which demonstrates the use of the function in a form, is included.

It contains an option group to set the action, a button to carry the command out, and a button to close the Help Viewer window:

The full code is held at a minimum:

Private Sub CloseHelp_Click()

    HelpControl CloseAll

End Sub

Private Sub Form_Close()

    ' Make sure the Help Viewer is closed, or Access may crash when closing.
    HelpControl CloseAll

End Sub

Private Sub OpenHelp_Click()

    ' Name of help file.
    Const FileName  As String = "HelpStudioSample.chm"

    Dim Command     As hhCommand
    Dim Context     As Long
    Dim HelpFile    As String
    ' Position the help file in the folder of the application.
    HelpFile = CurrentProject.Path & "\" & FileName
    ' Open the Help Viewer.
    Select Case Me!HelpGroup.Value
        Case 1
            Command = DisplayContents
        Case 2
            Command = DisplayIndex
        Case 3
            Command = DisplaySearch
        Case 4
            Command = OpenContext
            Context = Val(Nz(Me!ID.Value))
    End Select
    HelpControl Command, HelpFile, Context
End Sub

Note, in the Close event, the call to close the Help Viewer window, should it have been left open. As mentioned earlier, an open Help Viewer window may cause the application to crash when closing.


Full code and an Access demo application as presented here will provide a good starting point for implementing an on-line help function for an application using either 32- or 64-bit VBA.

Another example of usage can be found in another of my articles:

Modern/Metro style message box and input box for Microsoft Access 2013+


The full and current code is available for download at GitHub: VBA.HtmlHelp

Also, code and a demo application is here: HelpDemo

Sample help file: HelpStudioSample

I hope you found this article useful. You are encouraged to ask questions, report any bugs or make any other comments about it below.

Note: If you need further "Support" about this topic, please consider using the Ask a Question feature of Experts Exchange. I monitor questions asked and would be pleased to provide any additional support required in questions asked in this manner, along with other EE experts.

Please do not forget to press the "Thumbs Up" button if you think this article was helpful and valuable for EE members.

Gustav BrockMVP

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.