1
votes

I am new to this forum, so thank you in advance for your help with this thread.

I am trying to solve what I think is an age-old problem with Access Forms. I am developing a database where a pop-up main form opens several other pop-up forms that I want to remain within the border of the main form. When the main form is moved or resized I need to move and resize also the other open forms accordingly. I am basically trying to give my database a look similar to a real application where a main form has several child forms.

I am using the event Detail_Paint() to detect when the main form is changed in size to adjust the size of the other forms and it seems to work. However there is no event associated with the movement on screen of the form. MouseMove() does not seem to work because when the user moves the mouse curson on the form caption it doesn't fire. I have solved the problem using a Timer that checks every 10 ms the position of the main form and changes the position of the other forms accordingly. However, this creates an annoying flickering of the monitor and also problems when the user is entering text in the controls.

I have read that is possible to subclass the form and catch the WM_MOVING message to the window. I have developed some test code for this but when I try to run it Access stops working and I have to use the Task Manager to shut it down. I am using Access 2016 Professional 64bit on a Windows 10 64bit system.

Here is the code I have written so far.

' This code goes into a general module (mdl_subclass)
' When subclassing shows the coordinates of the window in its caption

Option Explicit

Declare PtrSafe Function SetWindowLongPtr Lib "user32" Alias     "SetWindowLongPtrA" _
        (ByVal hWnd As LongPtr, _
         ByVal nIndex As LongPtr, _
         ByVal dwNewLong As LongPtr) As LongPtr

Declare PtrSafe Function CallWindowProc Lib "user32" Alias "CallWindowProcA" _
        (ByVal lpPrevWndFunc As LongPtr, _
         ByVal hWnd As LongPtr, _
         ByVal Msg As LongPtr, _
         ByVal wParam As LongPtr, _
         ByVal lParam As LongPtr) As LongPtr

Public Const GWL_WNDPROC = (-4)
Private Const WM_MOVE = &H3

Dim m_PrevProc As LongPtr

Public Sub SubClass_On(ByVal hWnd As Long)
  m_PrevProc = SetWindowLongPtr(hWnd, GWL_WNDPROC, AddressOf WindowProc)
End Sub

Public Sub SubClass_Off(ByVal hWnd As Long)
  SetWindowLongPtr hWnd, GWL_WNDPROC, m_PrevProc
End Sub

Private Function WindowProc(ByVal hWnd As LongPtr, ByVal uMsg As LongPtr,      ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr

  WindowProc = CallWindowProc(m_PrevProc, hWnd, uMsg, wParam, lParam)

  If uMsg = WM_MOVE Then

    Form_frm_main.Me_OnMove lParam And CLng(&HFFFF&), lParam \   CLng(&HFFFF&)

  End If

End Function


' This code instead goes into the form module, starts subclassing on form   loading and stop subclassing on form unload

Private Sub Form_Load()

  SubClass_On Me.hWnd

End Sub

Private Sub Form_Unload(Cancel As Integer)

  SubClass_Off Me.hWnd

End Sub


Friend Sub Me_OnMove(ByVal xPos As Long, ByVal yPos As Long)

  Me.Caption = "x=" & xPos & "; y=" & yPos

End Sub

...any idea on how to fix my code? Or any alternative suggestions to my problem?

Thanks

1
Your declarations are wrong, nIndex and Msg are Long. - Hans Passant

1 Answers

3
votes

I think I have found a solution to my problem! What I am trying to recreate is an MDI application environment with Access Forms. I have found an API function that allows to set the property of a form as child. The function is SetParent().

If you try use this function in the Form_Open() event of any access form it will see that form as a child of any other specified form. Once the child property has been set Windows will automatically move the child form with the main form anywhere on the screen. The effect is very nice!

Here is an example.

Declare the following WinAPI functions in a module

Public Declare PtrSafe Function SetParent Lib "user32" _
                               (ByVal hWndChild As LongPtr, _
                                ByVal hWndParent As LongPtr) As Long

Public Declare PtrSafe Function SetWindowPos Lib "user32" _
                               (ByVal hwnd As LongPtr, _
                                ByVal hWndInsertAfter As LongPtr, _
                                ByVal x As Long, _
                                ByVal y As Long, _
                                ByVal cx As Long, _
                                ByVal cy As Long, _
                                ByVal wFlags As Long) As Long

Note I am using LongPtr because I am working in a 64bit environment. Then, in the child form_open event, use the following code

Private Sub Form_open(Cancel As Integer)

  Dim hWndParent As LongPtr
  Dim hWndChild As LongPtr

  hWndParent = Form_frm_parent.hwnd
  hWndChild = Form_frm_child.hwnd

  SetParent hWndChild, hWndParent   ' open the form as a child

                                    ' this is used to position the form
  SetWindowPos hWndChild, hWndParent, 163, 44, 725, 437, &H4

End sub

I have only tried it with pop-up forms, so I don't know if it works with other forms as well. However, if you set both forms (parent and child) as pop-up form you'll see Windows moves the child form with the parent form automatically. You can also add a child of a child form, it still works nicely.

However, once you have declared the form as a child, the command docmd.move produces some strange effects. A better approach is to use the WinAPI function SetWindowPos to position your child form. The coordinate system will be relative to the position of the parent form. So 0,0 coordinates is the top-left corner of the parent form.

I have found useful to use the function GetWindowRect to get the coordinates of my parent form and then set the position of the child form.

Public Type RECT
    wdw_left As Long
    wdw_top As Long
    wdw_right As Long
    wdw_bottom As Long
End Type

Public Declare PtrSafe Function GetWindowRect Lib "user32" _
                               (ByVal hwnd As LongPtr, _
                                lpRect As RECT) As Long

Then in the form_resize() event for the child form

Dim hWndParent As LongPtr   ' handle finestra
Dim hWndChild As LongPtr    ' handle finestra
Dim mainRECT As RECT        ' coordinate form_home (struct RECT)

hWndParent = Form_frm_parent.hwnd
hWndChild = Form_frm_child.hwnd

GetWindowRect hWndParent, mainRECT

And once you have the coordinates of the main form you can decide where to position your child form. Finally if you want to adapt the position of the child forms dynamically based on the size of the parent form you can use the form_resize() event of the parent form to call the form_resize() events of the child forms and change their size and position. In order to do this form_resize() of the child forms must be declares as public, then you can call it from any other form (the parent form in our case). In this way you can add scrollbars to your child forms if the parent form is too small.

I hope this will help any other user interested in this kind of solution.

Cheers