Version 3.0 Approx 1.1 MB (zipped) First Published 23 Feb 2024 Last Updated 8 Nov 2026
UPDATED 2026-11-08: Version 3.0 removes dependency on .NET 3.5 being installed. No changes to functionality.
Section Links (this page):
Introduction
Download
Using the Example App
How Does the Example App Work?
Version History
Video
More Continuous Forms Examples
Feedback
Introduction
Return To Top
This is the third part of the ninth article in my series showing how functionality can be added to continuous forms.
In the
first part of this article, I discussed the built-in freeze fields feature in datasheets and then built similar functionality in continuous forms.
The original approach used for continuous forms involved a main form with 2 subforms. The left subform was fixed. The right subform could be scrolled horizontally, sorted and filtered.
The
second article demonstrated a much improved approach with a continuous form and no subforms. Form navigation was managed using an ActiveX horizontal scrollbar.
This took into account the specified number of frozen columns which remained fixed in position as the form was scrolled horizontally.
This was a completely new approach which I was very pleased with. It did everything it was designed to do perfectly.
However, I completely forgot about navigation using the tab (or shift+tab) keys as I rarely use this method myself.
The result was that tabbing completely ignored the frozen columns and the whole form was scrolled horizontally. Clearly far from satisfactory.
That issue is addressed in this article. It wasn't difficult to get the code to work when tabbing but shift+tab proved more tricky.
The solution discussed here was a collaboration between myself and Xevi Batlle . It uses no APIs and requires no additional references (if using late binding)
It makes use of class module code by A.P.R. (Ramachandran) Pillai to streamline the code and make it easily transferable to other forms.
For more details on Ramachandran's approach to streamlining code, see his excellent series of articles starting with
Streamlining Form Module Event Procedures
You can also watch a video of his presentation from Jan 2024 to the Access Europe user group on YouTube:
AEU23: Streamline Form Module Code in the standalone Class Module
I am very grateful to both Xevi and Ramachandran for the significant contributions each made to the development of this application. Many thanks
Download Return To Top
Click to download: FreezeColumns_v3.0 ACCDB file Approx 1.1 MB (zipped)
Using the Example App Return To Top
As before, the app opens to a startup form:
Click Continue to open the main (continuous) form. In this case, the first three columns are fixed but this can be changed between 0 and 8 frozen columns
This is the form in design view. As before, it is just a standard continuous form with an ActiveX horizontal scrollbar.
All the functionality from the previous version still applies here but you can now also scroll the form horizontally to navigate using the TAB / SHIFT+TAB keys.
The frozen columns now remain fixed in position.
Navigation using the TAB key behaves exactly as it does in a datasheet form. The columns are only scrolled horizontally once the cursor moves to the right side of the screen.
With further tabbing, the columns are scrolled horizontally and the scrollbar moves further to the right each time.
Similarly when using SHIFT+TAB, the columns are only scrolled horizontally when the cursor is adjacent to the frozen columns with hidden columns needing to be made visible
This continues until all hidden columns are made visible again after which further use of SHIFT+TAB moves the cursor onto the frozen columns
You can also use a mixture of navigation by tabbing or using the scroll bar.
As before, the number of frozen columns can be changed and the form can be sorted and filtered
How Does the Example App Work? Return To Top
As already stated, the app requires no APIs and works in both 32-bit & 64-bit Access. The only non standard features are:
a) an ActiveX scrollbar for horizontal scrolling of the columns that are not frozen (fixed)
b) VBA code to create a dictionary and collection of the column names, widths and positions
If using early binding, you need an additional reference, Microsoft Scripting Runtime. However, I recommend using late binding in which case no additional reference is required.
The type of binding used and related code is defined in the declarations section of modManageColumns
c) class module code to control the behaviour of the continuous form controls.
In this example all controls are textboxes, but similar code is provided for use with checkboxes or combo box controls on your own forms.
The main class module is clsForm which has the following code:
Option Compare DatabaseOption Explicit'======================================================='Based on code by APR Pillai'https://www.msaccesstips.com/2023/04/reusing-form-module-vba-code-for-new.html'Class module to manage events for each control type used in continuous form'=======================================================Private Coll As New CollectionPrivate iFrm As FormPublic Property Get t_Form() As Form Set t_Form = iFrmEnd PropertyPublic Property Set t_Form(ByRef vFrm As Form) Set iFrm = vFrm Call Class_InitEnd PropertyPrivate Sub Class_Init()Dim labelName As String, controlName As StringDim iTxt As clsControlTextBoxDim iCombo As clsControlComboBox 'not used in this exampleDim iCheck As clsControlCheckBox 'not used in this exampleDim ctl As ControlDim ctlToProcess As ControlConst EP = "[Event Procedure]" For Each ctl In iFrm.Controls If ctl.ControlType = acLabel And ctl.Tag = "DetachedLabel" Then labelName = ctl.Name controlName = GetControlNameFromLabel(labelName) Set ctlToProcess = iFrm(controlName) Select Case ctlToProcess.ControlType Case acTextBox Set iTxt = New clsControlTextBox Set iTxt.t_Control = ctlToProcess iTxt.t_Control.OnEnter = EP iTxt.t_Control.OnKeyDown = EP Coll.Add iTxt Set iTxt = Nothing Case acComboBox Set iCombo = New clsControlComboBox Set iCombo.t_Control = ctlToProcess iCombo.t_Control.OnEnter = EP iCombo.t_Control.OnKeyDown = EP Coll.Add iCombo Set iCombo = Nothing Case acCheckBox Set iCheck = New clsControlCheckBox Set iCheck.t_Control = ctlToProcess iCheck.t_Control.OnEnter = EP iCheck.t_Control.OnKeyDown = EP Coll.Add iCheck Set iCheck = Nothing Case Else End Select End If NextEnd SubPrivate Sub Class_Terminate()'Delete Collection Object contents Do While Coll.Count > 0 Coll.Remove 1 Loop Set iFrm = NothingEnd Sub
The related code in clsControlTextBox is:
Option Compare DatabaseOption Explicit'======================================================='Xevi Batlle - Based on code by APR Pillai'https://www.msaccesstips.com/2023/04/reusing-form-module-vba-code-for-new.html'Used with textbox controls in continuous form'=======================================================Private WithEventsTxtAsTextBoxPublic Property Gett_Control()AsTextBoxSett_Control=TxtEnd PropertyPublic Property Sett_Control(ByRefpcboAsTextBox)SetTxt=pcboEnd PropertyPrivate Subtxt_KeyDown(KeyCodeAs Integer,ShiftAs Integer)SetPreviousControl Txt.Parent,KeyCode,Shift,Screen.ActiveControl.NameEnd SubPrivate Subtxt_Enter()SetNewFirstUnFrozenColumn Txt.Parent,TxtEnd Sub
The code in clsControlCheckBox and clsControlComboBox is almost identical to that in clsControlTextBox. Neither of those are used in this example app.
d) Referencing the class module code in the form frmMain needs minimal code.
One new line is added to the declarations section and the Form_Load event to load the class. The Form_Close event is used to reset it to nothing.
Option Compare DatabaseOption ExplicitPrivateFormObjectAs NewclsFormPrivate SubForm_Load()Application.EchoFalseSetFormObject.t_Form=Me'other existing Form_Load code goes here ...End SubPrivate SubForm_Close()SetFormObject=NothingEnd Sub
The Form_Open event is used to set up the dictionary used to store column names, positions & widths. A collection stores the detached labels ordered by left position.
'Uses Dictionary to store column names, positions & widths'Xevi BatllePrivate SubForm_Open(CancelAs Integer)DimcurrentControlNameAs String,currentLabelNameAs String,PreviouslabelNameAs StringDimJAs Long#IfEarlyBindThen' these are defined in modManageColumns' Dim dict As New Scripting.Dictionary' Dim colOrdered As New Collection#Else' these are also defined in modManageColumns' Dim dict As Object' Dim colOrdered As CollectionSetdict=CreateObject("Scripting.Dictionary")SetcolOrdered=NewCollection#End IfFor EachctlIn Me.ControlsIfctl.ControlType=acLabelOrctl.ControlType=acTextBoxThendict.Add ctl.Name& "_Left",ctl.Leftdict.Add ctl.Name& "_Width",ctl.WidthEnd IfNext' * * * Stores detached labels in a Collection ordered by Left PositionFor EachctlIn Me.ControlsIfctl.ControlType=acLabelAndctl.Tag= "DetachedLabel"ThenForJ= 1TocolOrdered.CountIfctl.Left<dict(colOrdered(J) & "_Left")ThencolOrdered.Add ctl.Name,Before:=JExit ForEnd IfNextJIfJ>colOrdered.CountThencolOrdered.Add ctl.NameEnd IfEnd IfNextctlForI= 1TocolOrdered.CountcurrentLabelName=colOrdered(I)' Sets the distance from previous controlIfI= 1Thendict.Add currentLabelName& "_Offset", 0ElsePreviouslabelName=colOrdered(I- 1)currentControlName=GetControlNameFromLabel(currentLabelName)dict.Add currentControlName& "_Offset",dict(currentControlName& "_Left") - (dict(PreviouslabelName& "_Left") _+dict(PreviouslabelName& "_Width"))End IfNextIEnd Sub
As all the control event code is controlled from the class modules, there is almost no event procedure code for the form controls.
Nor is there any code in the property sheets for any controls.
The only exception is code to handle the SHIFT+TAB key combination and the LEFT, UP & DOWN arrow keys in the KeyDown event of the first control (PupilID).
This was needed to prevent unwanted side effects and to ensure consistent behaviour both in current Access versions e.g. 365 and older versions of Access such as 2010.
Private SubPupilID_KeyDown(KeyCodeAs Integer,ShiftAs Integer)'Move to next record using Down'Move to previous record using Up or Shift+Tab or Left (when at start of field)'Disable action on first or last record'v2.8 Colin RiddingtonDimintShiftDownAs IntegerDimintCtrlDownAs IntegerDimpositionAs IntegerConstcontrolNameAs String= "PupilID"'Debug.Print acAltMask, acCtrlMask, acShiftMaskintShiftDown= (ShiftAndacShiftMask)intCtrlDown= (ShiftAndacCtrlMask)'check cursor position (affected by client settings)position= 0IfHasProperty(Me(controlName), "SelStart")Thenposition=Me(controlName).SelStartEnd If'handle Shift+Tab & Left / Up / Down arrows. Also disable End & Ctrl+End keysIf(intShiftDownAndKeyCode=vbKeyTab)Or(KeyCode=vbKeyLeftAndposition= 0)OrKeyCode=vbKeyUp_OrKeyCode=vbKeyEndOr(intCtrlDownAndKeyCode=vbKeyEnd)ThenKeyCode= 0If Me.CurrentRecord> 1ThenDoCmd.GoToRecord, ,acPreviousElseIfKeyCode=vbKeyDownThenIf Me.CurrentRecord<Me.Form.Recordset.RecordCountThenDoCmd.GoToRecord, ,acNext'above line makes it go down two records! Workround - fix by moving back up one recordDoCmd.GoToRecord, ,acPreviousEnd IfEnd IfEnd Sub
e) There are a few code changes in modManageColumns.
AssignScrollBarValues has been updated with error handling code to handle extreme cases where the form is made very narrow or very wide:
SubAssignScrollBarValues(frmAsForm)'Xevi Batlle / Colin RiddingtonDimnewBarSizeAs LongOn Error GoToErr_HandlerWithfrm.MainScrollBar.Max=colOrdered.Count.Min=frm.cboFrozenColumns+ 1.Value=frm.cboFrozenColumns+ 1.Width= 1'Added to prevent from raising an error when changing the frozen columns number.Left=dict(colOrdered(GetCountFrozenColumns+ 1) & "_Left")'next section prevents error 2100 if form width reduced so scroll bar doesn't fit'error handling prevents same error for users with large monitorsnewBarSize=frm.InsideWidth- .Left- 500' Debug.Print frm.InsideWidth, .Left, newBarSizeIfnewBarSize> 0Then.Width=newBarSizeEnd IfEnd WithExit_Handler:Exit SubErr_Handler:'err 6 = Overflow (exceeds integer limit)'err 2100 = The control or subform control is too large for this location.'The number you entered for the Left, Top, Height, or Width property is too large or is a negative number.'Reduce the size of the control or subform control, or enter a positive number.'err 91 = Object variable or With block variable not set - triggered after err 2100IfErr= 2100OrErr= 6Then'don't allow scrollbar width to exceed integer limit. Set max to 32000 for safetyfrm.MainScrollBar.Width= 32000 -frm.MainScrollBar.Left- 500ElseIfErr= 91ThenResume NextElseMsgBox"Error "&Err.Number& " in AssignScrollBarValues procedure : "&Err.DescriptionEnd IfResumeExit_HandlerEnd Sub
Four new procedures have been added for use when tabbing through the form
Public FunctionFullShownInWindow(frmAsForm,ctlAsControl)As Boolean'Xevi Batlle v2.4FullShownInWindow=TrueIfctl.Left+ctl.Width>frm.InsideWidth- 500ThenFullShownInWindow=FalseEnd IfEnd Function'http://allenbrowne.com/AppPrintMgtCode.html#HasPropertyPublic FunctionHasProperty(objAs Object,strPropNameAs String)As Boolean'Purpose: Return true if the object has the property.DimvarDummyAs VariantOn Error Resume NextvarDummy=obj.Properties(strPropName)HasProperty= (Err.Number= 0)End FunctionPublic FunctionSetPreviousControl(frmAsForm,KeyCodeAs Integer,ShiftAs Integer,controlNameAs String)As Boolean'2.8 Colin Riddington / Xevi BatlleDimintCtrlDownAs IntegerDimintShiftDownAs IntegerDimFirstLabelNameNotFrozenAs String,FirstControlNameNotFrozenAs StringDimctlAsControlDimpositionAs IntegerSetctl=frm(controlName)position= 0IfHasProperty(frm(controlName), "SelStart")Thenposition=ctl.SelStartEnd If'Debug.Print acAltMask, acCtrlMask, acShiftMaskintShiftDown= (ShiftAndacShiftMask)intCtrlDown= (ShiftAndacCtrlMask)'Disable End & Ctl+End keysIf(intCtrlDownAndKeyCode=vbKeyEnd)OrKeyCode=vbKeyEndThenKeyCode= 0Exit FunctionEnd If'Trap Shift + TAB & Left / Up / Down arrows to change default behaviourIf(intShiftDownAndKeyCode=vbKeyTab)Or(KeyCode=vbKeyLeftAndposition= 0)ThenFirstLabelNameNotFrozen=colOrdered(frm.MainScrollBar)FirstControlNameNotFrozen=GetControlNameFromLabel(FirstLabelNameNotFrozen)IfcontrolName=FirstControlNameNotFrozenThen' If true, the control is the first non frozen columnIffrm.MainScrollBar>frm.MainScrollBar.MinThenfrm.MainScrollBar=frm.MainScrollBar- 1KeyCode= 0End IfEnd IfElseIfKeyCode=vbKeyUpThen'move to previous recordIffrm.CurrentRecord> 1ThenDoCmd.GoToRecord, ,acPreviousctl.SetFocusEnd IfElseIfKeyCode=vbKeyDownThen'move to next recordIffrm.CurrentRecord<frm.Form.Recordset.RecordCountThenDoCmd.GoToRecord, ,acNextctl.SetFocusEnd IfEnd IfEnd FunctionPublic FunctionSetNewFirstUnFrozenColumn(frmAsForm,ctlAsControl)As Integer'Xevi Batlle v2.5DimtotalSizeAs LongDimlabelNameToShowAs StringDimlabelNameAs String,controlNameAs StringDimnextOriginAs LongDimIAs IntegerIfFullShownInWindow(frm,ctl)Then Exit FunctionnextOrigin=frm.MainScrollBarlabelNameToShow=GetLabelNameFromControl(ctl.Name)totalSize= 0ForI=frm.MainScrollBarTocolOrdered.CountlabelName=colOrdered(I)controlName=GetControlNameFromLabel(labelName)totalSize=totalSize+frm(labelName).WidthIftotalSize>ctl.WidthThennextOrigin=IExit ForEnd IfNextIIfnextOrigin<colOrdered.CountThennextOrigin=nextOrigin+ 1End IfSetNewFirstUnFrozenColumn=nextOriginfrm.MainScrollBar=nextOriginEnd Function
The app now appears to work correctly both when navigating using the scrollbar and with the tab key.
It also handles the behaviour when using arrow keys to navigate the form.
The app should also be resilient against different client settings and show consistent behaviour in all versions of Access from 2010 through to 365.
I hope you find this feature useful for your own applications. Feedback welcomed!
To use this approach in your own apps, you need to import the following items:
a) Table tblSettings
b) Standard modules: modFunctions / modManageColumns / modNavPane
c) Class modules: clsForm, clsControlTextBox and (if required) clsControlCheckBox, clsControlComboBox
d) Use late binding or add the Microsoft Scripting Runtime reference library.
The form code should then be easily transferable to your own forms with little or no changes required.
e) Import code from PupilID_KeyDown event to the same event in the first control on your form.
f) Make sure the form property sheet has KeyPreview = Yes
Version History Return To Top
v1.3 14/02/2024 (Page 1 of this series of articles)
- initial release using a main form with 2 subforms. Some limitations in functionality
NOTE: Versions 2.3 to 2.9 all require .NET version 3.5 to be installed as an optional Windows feature. If it is missing, automation and other errors will occur.
v2.3 2024-02-16 (Page 2 of this series of articles)
- new approach suggested by Xevi Batlle using a dictionary & array list of column names, widths and positions
- completely revised to work on a continuous form with no subforms using an ActiveX scrollbar
- added code to specify the number of frozen columns & optionally lock those columns
- horizontal & vertical scrolling now works correctly together with sorting & filtering
- BUG: use of tab key to navigate didn't take into account frozen colunns
v2.6 2024-02-23
- updated code to also work correctly when navigating the form using Tab or Shift+Tab keys
- added class module code based on an approach by A.P.R. Pillai to manage continuous form controls and streamline form code
v2.9 2024-02-25
- fixed issues with use of Shift+Tab key in the first column. This had been a particular problem in older versions of Access e.g. 2010
- added code to also handle Left/Up & Down arrow keys together disabling use of End / Ctrl+End keys
- other minor bug fixes to ensure consistent behaviour in different versions of Access
v3.0 2026-11-08
- updated code to remove dependency on .NET version 3.5 which was needed for the array list
- Many thanks to Xevi Batlle for detecting the issue and updating the code
Video
Return To Top
UPDATE 15 Mar 2024
I have now created a video (16:22) for YouTube to demonstrate freezing (and optionally locking) selected columns in a continuous form.
You can watch the Freeze Columns in a Continuous Form video on my Isladogs YouTube channel or you can click below:
If you liked the video, please subscribe to my Isladogs on Access channel on YouTube. Thanks.
More Continuous Form Examples
Return To Top
The following articles provide further examples of functionality added to continuous forms:
• Highlight Current Record
• Highlight Selected Control or Column
• Highlight Filtered Columns
• Add Multiselect Filter (2 pages)
• Paint Me Transparent (Selectively Hide Controls)
• Sort columns
• Hide duplicate values in a column (as for reports)
• Hide & Restore Selected Columns (2 pages)
• Freeze Columns (3 pages)
• Move and Resize Columns (2 pages)
Feedback
Please use the E-Mail button in the contact form below to let me know whether you found this article useful or if you have any questions.
Please also consider making a donation towards the costs of maintaining this website. Thank you
Colin Riddington Mendip Data Systems Last Updated 8 Nov 2026
Return to Example Databases Page
Page 2 of 3
1
2
3
Return To Top
|
|
|