Code Samples for Businesses, Schools & Developers

First Published 14 Jan 2024                 Last Updated 15 Jan 2024


Following on from my recent article Get Control Properties Using Code, it might be useful to show how you can get the position and size of each form control.

In this article, I will explain two different ways of getting this information:

1.   The standard approach is to use the top, left, width and height properties of the controls.
      The values are in TWIPS and are relative to the FORM SECTION.

2.   A much less well known approach is to use accLocation method. This is part of the Accessibility code.
      All values are in PIXELS and are relative to the SCREEN. For convenience, I have converted these from PIXELS to TWIPS in the example app for comparison.

NOTE:
1440 twips = 1 inch (567 twips = 1cm); With display scaling at 100% (or 96 dots per inch), 96 pixels = 1 inch so 1 pixel = 15 twips
For more information about the various units of measurement, see my article: Accurately Move Forms and Controls

The supplied example app opens to a startup form with the same information as above.

Start Form
Click the Example Form button to open a form with a variety of different control types:

Example Form
NOTE:
The form includes a modern chart which will only be shown in A2019 or later.
If you are using A2016 or earlier, a 'Variable not Defined' error will occur on the line Case acChart in the function GetControlTypeName in modControlProps.

Modern Chart
Disable the two highlighted lines (or use the numerical value) to fix that error in older versions.



As you move the mouse over the various form controls, the control name is displayed in RED together with the control type, size and position (in twips) relative to the section of the form where the control is located.

NOTE:
This information is collected in the Form_Load event using a sub procedure PopulateControlLocationsTable in modControlProps and is saved to a table tblControlInfo.

CODE:

Sub PopulateControlLocationsTable(frm As Form)

On Error Resume Next

      'empty table
CurrentDb.Execute "DELETE * FROM tblControlInfo;", dbFailOnError

      'repopulate table
      strSQL = "SELECT * FROM tblControlInfo"

      Set rst = CurrentDb.OpenRecordset(strSQL, dbOpenDynaset)

      With rst
            'loop through each form section and its controls
            For i = 0 To 4
                  For Each ctl In frm.Section(i).Controls
                        .AddNew
                        !FormName = frm.Name
                        !ControlName = ctl.Name
                        !ControlType = ctl.ControlType
                        !ControlTypeName = GetControlTypeName(ctl.ControlType)
                        !FormSection = GetFormSectionName(i)

                        'get left, top, width & height for each control
                        !Left = ctl.Left 'relative to form
                        !Top = ctl.Top 'relative to form section
                        !Width = ctl.Width 'convert pixels to twips
                        !Height = ctl.Height 'convert pixels to twips

                        .Update

                  Next ctl
           Next i

            .Close

      End With

      Set rst = Nothing

End Sub


Click the View All Control Info button to open another form showing the control info collected in the Form_Load event

Control Positions 1
Returning to the previous form, each control mouse move event calls a function ShowControlProperties

CODE:

Public Function ShowControlProperties()

On Error Resume Next

      Dim strText As String, strName As String, strFilter As String

      strName = GetControlName(Me)

      strFilter = "FormName = '" & Me.Name & "' And ControlName = '" & strName & "'"

      strText = "Control Name = " & strName & ", " & _
           "Control Type = " & Nz(DLookup("ControlTypeName", "tblControlInfo", strFilter), "") & ", " & _
           "Form Section = " & Nz(DLookup("FormSection", "tblControlInfo", strFilter), "") & ", " & _
           "Left = " & Nz(DLookup("Left", "tblControlInfo", strFilter), "") & ", " & _
           "Top = " & Nz(DLookup("Top", "tblControlInfo", strFilter), "") & ", " & _
           "Width = " & Nz(DLookup("Width", "tblControlInfo", strFilter), "") & ", " & _
           "Height = " & Nz(DLookup("Height", "tblControlInfo", strFilter), "")

      'show the label and display the information as its caption
      Me.lblControlInfo.Visible = True
      Me.lblControlInfo.Caption = strText

End Function


The control name used in the above code is obtained using another function GetControlName in modControlProps.
This depends on accessibility code accHitTest to get the name of the control at the mouse cursor position.

CODE:

Private Type POINTAPI
     X As Long
     Y As Long
End Type

Private Declare PtrSafe Function GetCursorPos Lib "user32.dll" _
     (ByRef lpPoint As POINTAPI) As Long

'--------------------------------------------------------

Public Function GetControlName(frm As Form)
'Colin Riddington - 1 Jan 2024 (with thanks to Karl Donaubauer for alerting me to accHitTest)

     Dim pt As POINTAPI
     Dim accObject As Object
     GetCursorPos pt
     Set accObject = frm.AccHitTest(pt.X, pt.Y)
     If Not accObject Is Nothing Then GetControlName = accObject.Name

End Function


NOTE:
1.   The control position /size data collection works for all control types.
      However the data isn't displayed on mouse over for lines (no event procedures) or subforms (no mouse move event).

2.   The accHitTest method is an important part of the Accessibility code used in all Windows applications. See the MS article: IAccessible: accHitTest method

      I will describe its use in more detail in a forthcoming article: Streamlining the Multiselect Filter code in a Continuous Form



Next click the View All Control AccLocation Info button on the form. This runs another procedure PopulateControlAccLocations to collect location info using the accLocation Accessibility method then opens another form to display the results

Control Positions 2
NOTE:
1.   accLocation requires just one line of code (and no APIs) to get the position and size info for each control: ctl.accLocation X, Y, W, H, 0&
      where X, Y are left & top values relative to screen; W, H are width & height. All values are in PIXELS

2.   For more information, see the MS articles: IAccessible: accLocation method and Navigation Through Hit Testing and Screen Location

3.   The values returned by accLocation are for the bounding rectangle containing the control.
      In the case of checkboxes and option buttons, this is usually slightly larger than the visible control itself.

Form Design
CODE:

Sub PopulateControlAccLocations(frm As Form)

'Alternative approach using Accessibility code: accLocation

On Error Resume Next

      Dim X As Long, Y As Long, W As Long, H As Long

      'empty table
      CurrentDb.Execute "DELETE * FROM tblControlInfo;", dbFailOnError

      'repopulate table
      strSQL = "SELECT * FROM tblControlInfo"

      Set rst = CurrentDb.OpenRecordset(strSQL, dbOpenDynaset)

      With rst
            For i = 0 To 4
                  For Each ctl In frm.Section(i).Controls

                        .AddNew

                        'get left, top, width & height for each control
                        'X, Y are left & top values relative to screen
                        'W, H are width & height
                        ctl.accLocation X, Y, W, H, 0&

                        !FormName = frm.Name
                        !ControlName = ctl.Name
                        !ControlType = ctl.ControlType
                        !ControlTypeName = GetControlTypeName(ctl.ControlType)
                        !FormSection = GetFormSectionName(i)

                        'All values in PIXELS - multiply by 15 to get TWIPS values
                        !Left = X * 15       'relative to screen
                        !Top = Y * 15       'relative to screen
                        !Width = W * 15
                        !Height = H * 15

                        .Update

                  Next ctl
            Next i

            .Close

      End With

      Set rst = Nothing

End Sub


The mouse over code works exactly as before but now the left & top values displayed are relative to the SCREEN not the FORM

This could be very useful if you want to align two or more forms precisely on the screen as in my extended article: Accurately Move Forms and Controls



Download

Click to download the example app with all the above code:       Get Control Position Size v1.2      ACCDB file - Approx 0.8 MB (zipped)



UPDATE 15 Jan 2024

I have just discovered a strange issue with the accessibility code where a dual monitor setup is used with the primary monitor on the right.

Dual Monitors
The code works perfectly on the primary monitor.
Also, if the accLocation code is run with the form on the secondary monitor, it correctly returns negative co-ordinates for the controls' screen positions as expected.

However, the mouse move code fails on the line Set accObject = frm.AccHitTest(pt.X, pt.Y)
Using error handling this line returns error 5: Invalid procedure call or argument.

Instead, if the left monitor is made the primary monitor, the position co-ordinates are positive on both monitors and the accHitTest code works on each.
These findings have also been confirmed by another experienced Access developer

It appears that accHitTest cannot handle negative co-ordinates!
I will update both the article and code if and when I have a solution for this issue.



Feedback

Please use the contact form below to let me know whether you found this article interesting/useful or if you have any questions/comments.

Please also consider making a donation towards the costs of maintaining this website. Thank you



Colin Riddington           Mendip Data Systems                 Last Updated 15 Jan 2024



Return to Code Samples Page




Return to Top