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.
Click the Example Form button to open a form with a variety of different control types:
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.
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
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
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.
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.
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
|