Visual Basic Subclassing Routines
WM_NCHITTEST: Simulating a Non-Client Action to Move a Form
Posted:   Wednesday July 1, 1998
Updated:   Monday December 26, 2011
Applies to:   VB5, VB6
Developed with:   VB6, Windows 98
OS restrictions:   None
Author:   VBnet - Randy Birch
VB5 or VB6.

In an application presenting a title-less form, it may be desirable to allow the user to reposition the form by clicking anywhere on the form body and dragging.  While one method for accomplishing this was presented in DrawFocusRect: Simulating Non-Client Form Movement, VB5/VB6 and subclassing provides another means.

This demo subclasses and captures the mouse click on a form. Normally, this action generates a HTCLIENT message in Windows, but by changing this message to HTCAPTION then returning this value to Windows for subsequent processing, we can fool Windows into thinking that the mouse click occurred on the caption area of the form. Thus dragging while holding the mouse moves the form as though the title bar had been clicked and dragged. 

It is not necessary to remove the title bar for this to work; the technique can also be applied to a normal form where the ability to move it wherever it is clicked is desired. Note that in this code movement can not be achieved when clicking the button control, although clicking any lightweight control that may be on the form, e.g. a label, shape, line or image control - will pass the message through and allow form repositioning.

This example was provided to VBnet by Domenico Statuto, author of the CCRP Extended FileDialogs Control.

 BAS Module Code
Place the following code into the general declarations area of a bas module:

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.
Public defWindowProc As Long

Public Const GWL_WNDPROC As Long = (-4)
Public Const WM_NCHITTEST As Long = &H84
Public Const HTCAPTION As Long = 2
Public Const HTCLIENT As Long = 1

Public Declare Function GetWindowLong Lib "user32" _
    Alias "GetWindowLongA" _
   (ByVal hwnd As Long, _
    ByVal nIndex As Long) As Long
Public Declare Function SetWindowLong Lib "user32" _
    Alias "SetWindowLongA" _
   (ByVal hwnd As Long, _
    ByVal nIndex As Long, _
    ByVal dwNewLong As Long) As Long
Public Declare Function CallWindowProc Lib "user32" _
    Alias "CallWindowProcA" _
   (ByVal lpPrevWndFunc As Long, _
    ByVal hwnd As Long, _
    ByVal Msg As Long, _
    ByVal wParam As Long, _
    ByVal lParam As Long) As Long

Public Function WindowProc(ByVal hwnd As Long, _
                    ByVal Msg As Long, _
                    ByVal wParam As Long, _
                    ByVal lParam As Long) As Long

   'Subclass form to trap messages
    On Error Resume Next
    Dim retVal As Long     'Where on the form is the mouse?
    Dim Mouse_X As Long    'Mouse coordinates
    Dim Mouse_Y As Long
   'First let the original Window Procedure process the message.
   'CallWindowProc returns the part of the form the mouse is on.
    retVal = CallWindowProc(defWindowProc, _
                            hwnd, _
                            Msg, _
                            wParam, _
  'What message received?
   Select Case Msg
      Case WM_NCHITTEST      'Every mouse action
        'in lParam there are the mouse co-ordinates
        'for info only 
         Mouse_X = LoWord(lParam)
         Mouse_Y = HiWord(lParam)
         Form1.Label1.Caption = "X: " & Mouse_X & vbCrLf & _
                                "Y: " & Mouse_Y
        'If mouse on client area, tell Windows the mouse is
        'on the caption bar!
         If retVal = HTCLIENT Then  'action on client area
            retVal = HTCAPTION      'tell Windows its the caption
         End If                     
        Case Else
           'Other WM_xxx messages you want to intercept
           'Act as appropriate
    End Select
       'return the value HTCAPTION to Windows
        WindowProc = retVal
End Function

Public Function HiWord(dw As Long) As Integer

    If dw And &H80000000 Then
       HiWord = (dw \ 65535) - 1
       HiWord = dw \ 65535
    End If
End Function

Public Function LoWord(dw As Long) As Integer

    If dw And &H8000& Then
       LoWord = &H8000& Or (dw And &H7FFF&)
      LoWord = dw And &HFFFF&
    End If
End Function
 Form Code
Create a borderless form, and add any controls desired to test their effect. Just be sure to include a means to invoke the unload method of the form to unsubclass the form before ending (ie the Exit button above), and a Label (Label1) Add the following code:

Option Explicit

Private Sub Command1_Click()

   Unload Me

End Sub

Private Sub Form_Load()

   'Begin the subclassing of Form1 by passing the
   'address of our new Window Procedure. SetWindowLong
   'returns the address of the original Window Procedure,
   'so we store it in a global variable to restore
   'when stopping the subclassing (typically, in the
   'Unload event).
    defWindowProc = SetWindowLong(Form1.hwnd, _
                                  GWL_WNDPROC, _
                                  AddressOf WindowProc)
End Sub

Private Sub Form_Unload(Cancel As Integer)

   'restore the original Window Procedure 
   'before unloading the form, or GPF will occur
    If defWindowProc Then
       Call SetWindowLong(Form1.hwnd, _
                          GWL_WNDPROC, _
       defWindowProc = 0
    End If

End Sub
Save then run the project. Clicking anywhere on the form or lightweight control and dragging will move the form as though the title bar had been used.


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