Edit: I made a couple dumb errors in the Demo and fixed them. They weren't important to the point of the Demo so you don't have to download the new ZIP if you don't want to.
cChanged Demo.zip
This is a Changed Class I wrote and use in all my applications that can save data. The reason I wrote it is because I could use the Changed Event in any given control to do whatever but the problem is that if you have multiple ways to get to the same thing (I'll explain that next) then you start triggering a whole lot of changed events and depending on how many different systems you have on one form it can cause a lot of flickering of buttons and such if you want to use Changed to enable/disable controls.
E.g. mnuFileSave is Disabled if nothing is changed.
Multiple ways means maybe you have a Customer form that's a front-end to a database. You have the normal four arrow keys - First, Last, Previous, Next. When you click one of those you reposition the recordset. There's also a ComboBox on the form with a list of customer names. When you reposition, the ComboBox displays the current customer.
But... you can also reposition the recordset by selecting a customer from the ComboBox which triggers the ComboBox again. This is a whole different problem but the way I solved it was in the Reposition Event (after prompting user to save record if applicable), Changed is first checked to see if it's disabled. If it is, the code skips out. If it's not then it Disables it and carries on. There are other ways to prevent unintentional recursive events but that's how I handled it.
I wrote up a demo program that is attached. This is just the cChanged Class code. Ignore the debug stuff. In the sample program I just put in dummy subs to handle that so I don't have to maintain two versions of the code.
cChanged Demo.zip
This is a Changed Class I wrote and use in all my applications that can save data. The reason I wrote it is because I could use the Changed Event in any given control to do whatever but the problem is that if you have multiple ways to get to the same thing (I'll explain that next) then you start triggering a whole lot of changed events and depending on how many different systems you have on one form it can cause a lot of flickering of buttons and such if you want to use Changed to enable/disable controls.
E.g. mnuFileSave is Disabled if nothing is changed.
Multiple ways means maybe you have a Customer form that's a front-end to a database. You have the normal four arrow keys - First, Last, Previous, Next. When you click one of those you reposition the recordset. There's also a ComboBox on the form with a list of customer names. When you reposition, the ComboBox displays the current customer.
But... you can also reposition the recordset by selecting a customer from the ComboBox which triggers the ComboBox again. This is a whole different problem but the way I solved it was in the Reposition Event (after prompting user to save record if applicable), Changed is first checked to see if it's disabled. If it is, the code skips out. If it's not then it Disables it and carries on. There are other ways to prevent unintentional recursive events but that's how I handled it.
I wrote up a demo program that is attached. This is just the cChanged Class code. Ignore the debug stuff. In the sample program I just put in dummy subs to handle that so I don't have to maintain two versions of the code.
Code:
Option Explicit
' Changed Property is Default Member of this Class.
' // Events.
Public Event Changed() ' Raises Event (Changed) when Set. Does not Fire when Changed Object is Disabled.
' // Events.
' // Constants, Enums and Types.
Private Const NAME As String = "cChanged"
Public Enum CHANGED_STATUS ' The Status Changed can have: Changed (Checked), Indeterminate (Grayed) or Unchanged (Unchecked).
idx_ChangedStatusIsChanged = vbChecked ' 1
idx_ChangedStatusIsIndeterminate = vbGrayed ' 2
idx_ChangedStatusIsUnchanged = vbUnchecked ' 0
End Enum
' // Constants, Enums and Types.
' // Properties.
Private nChanged As CHANGED_STATUS ' Manages "Dirty" status of record.
Private fEnabled As Boolean ' Sets Changed Status to idx_ChangedIndeterminate when Disabled = True. Restores previous Changed Status. Previous Changed Status must be Changed or Unchanged.
Private nPriorChangedStatus As CHANGED_STATUS ' Saves Changed Status immediately before Changed is Disabled.
' // Properties.
Public Property Get Changed() As CHANGED_STATUS
If DebugMode = idx_Debug_On Then CallStack.Add NAME & ".Changed(Public Property Get)"
Changed = nChanged
If DebugMode = idx_Debug_On Then CallStack.DeleteProcedureCall
End Property
Public Property Let Changed(ByVal DataChanged As CHANGED_STATUS)
If DebugMode = idx_Debug_On Then CallStack.Add NAME & ".Changed(Public Property Let)"
If Not fEnabled Then GoTo CleanUp
nChanged = DataChanged
RaiseEvent Changed
CleanUp:
If DebugMode = idx_Debug_On Then CallStack.DeleteProcedureCall
End Property
Public Function DestroyObjects() As Long
' Returns Error Code.
If DebugMode = idx_Debug_On Then CallStack.Add NAME & ".DestroyObjects(Public Function)"
' Dummy sub.
If DebugMode = idx_Debug_On Then CallStack.DeleteProcedureCall
End Function
Public Property Get Enabled() As Boolean
If DebugMode = idx_Debug_On Then CallStack.Add NAME & ".Enabled(Public Property Get)"
Enabled = fEnabled
If DebugMode = idx_Debug_On Then CallStack.DeleteProcedureCall
End Property
Public Property Let Enabled(ByRef EnableChanged As Boolean)
If DebugMode = idx_Debug_On Then CallStack.Add NAME & ".Enabled(Public Property Let)"
If EnableChanged = False Then ' Save current Changed Status if Changed Object is going to be disabled.
SavePriorChangedStatus EnableChanged
Else
RestoreChangedStatus EnableChanged ' Restore Saved Changed Status if Changed Object is being Enabled.
End If
fEnabled = EnableChanged
If DebugMode = idx_Debug_On Then CallStack.DeleteProcedureCall
End Property
Private Property Get PriorChangedStatus() As CHANGED_STATUS
If DebugMode = idx_Debug_On Then CallStack.Add NAME & ".PriorChangedStatus(Private Property Get)"
PriorChangedStatus = nPriorChangedStatus
If DebugMode = idx_Debug_On Then CallStack.DeleteProcedureCall
End Property
Private Property Let PriorChangedStatus(ByRef ChangedStatus As CHANGED_STATUS)
If DebugMode = idx_Debug_On Then CallStack.Add NAME & ".PriorChangedStatus(Private Property Let)"
nPriorChangedStatus = ChangedStatus
If DebugMode = idx_Debug_On Then CallStack.DeleteProcedureCall
End Property
Private Sub RestoreChangedStatus(ByRef EnableStatusChange As Boolean)
' This Sub should only be called when Disabled is being set to False. E.g. Changed.Disabled = False
' This Sub is called *Before* the actual Status of Disabled is Changed.
' Restores Changed Status to what it was before Changed was Disabled.
If DebugMode = idx_Debug_On Then CallStack.Add NAME & ".RestoreChangedStatus(Private Sub)"
If (fEnabled = True) Or (EnableStatusChange = False) Then GoTo CleanUp
nChanged = PriorChangedStatus
CleanUp:
If DebugMode = idx_Debug_On Then CallStack.DeleteProcedureCall
End Sub
Private Sub SavePriorChangedStatus(ByRef EnableStatusChange As Boolean)
' This Sub should only be called when Disabled is being set to True. E.g. Changed.Disabled = True
' This Sub is called *Before* the actual Status of Disabled is Changed.
' Current Changed Status is Saved when Changed is Disabled and Restored when Changed is Re-enabled.
' Changed Status is Set to idx_ChangedInderminate.
If DebugMode = idx_Debug_On Then CallStack.Add NAME & ".SavePriorChangedStatus(Private Sub)"
If (fEnabled = False) Or (EnableStatusChange = True) Then GoTo CleanUp ' Changed is currently Disabled so this should already be set.
' Or Status is being switched to Enabled. See RestoreChangedStatus.
If nChanged <> idx_ChangedStatusIsIndeterminate Then PriorChangedStatus = nChanged ' Only save idx_ChangedStatusIsChanged or idx_ChangedStatusIsUnchanged, but not idx_ChangedStatusIsIndeterminate
nChanged = idx_ChangedStatusIsIndeterminate ' Changed Status will always be idx_ChangedStatusIsIndeterminate if Changed is Disabled.
CleanUp:
If DebugMode = idx_Debug_On Then CallStack.DeleteProcedureCall
End Sub
Private Sub Class_Initialize()
If DebugMode = idx_Debug_On Then CallStack.Add NAME & ".Class_Initialize(Private Sub)"
fEnabled = True ' On by default. Otherwise every instance would need to enable its Changed object.
nPriorChangedStatus = idx_ChangedStatusIsIndeterminate
If DebugMode = idx_Debug_On Then CallStack.DeleteProcedureCall
End Sub