User:Woozle/VB/Settings manager
Overview
This is a set of classes for automating the loading and saving of user settings in a VB application. It's not as fully-developed as the ones I wrote in C++, but it seems to have gotten to the point of being usable.
The settings are saved in XML format, courtesy of Microsoft's baffling and largely undocumented MSXML2 ActiveX interface from the "Microsoft XML, v3.0" library (filename is c:\windows\system\MSXML3.DLL), although the classes are intended to be pluggable on both ends (you should be able to swap out the XML code for code that handles other formats, as well as being able to add "savers" for more data types).
- The "*API" classes are abstract classes which define the basic Settings API without actually doing anything.
- The "*XML" classes implement the API classes using MSXML2 to load and save the data.
Bugs
Items deleted from the saver-list may still be saved; something inside the XML objects is remembering them. You have to destroy the main Setting object and rebuild it at save-time in order to prevent this from happening. Fortunately, this doesn't seem to slow anything down noticeably.
Code
clsSettingAPI
This is an abstract class for translating between internal data and the string to actually save. It is implemented by #clsSettingText and #xtForm.
Option Explicit ' CLASS: clsSettingAPI ' PURPOSE: abstract class for saving a single item's settings Public Name As String Public Value As String Public Sub Save(iNode As clsSettingsNodeAPI) ' abstract End Sub Public Sub Load(iNode As clsSettingsNodeAPI) ' abstract End Sub
clsSettingsAPI
Option Explicit
' CLASS: clsSettingsAPI
' PURPOSE: abstract settings-saving class
Public Function Root() As clsSettingsNodeAPI
' ACTION: Return the root node
' USAGE: abstract; this is model code:
Dim objRoot As clsSettingsNodeAPI
Set objRoot = New clsSettingsNodeAPI
objRoot.Init Me, Nothing
Set Root = objRoot
End Function
Public Sub Load(iForce As Boolean)
' abstract
End Sub
Public Sub Save()
' abstract
End Sub
Private Sub Class_Initialize()
' abstract; sample code
Me.Load False
End Sub
Private Sub Class_Terminate()
' abstract; sample code
Me.Save
End Sub
clsSettingsNodeAPI
' CLASS: clsSettingsNodeAPI
' PURPOSE: set of values for clsSettingsAPI
' USAGE: Abstract
Option Explicit
Private objMain As clsSettingsAPI
Private objParent As clsSettingsNodeAPI
Friend Sub Init(iSettings As clsSettingsAPI, iParent As clsSettingsNodeAPI)
Set objMain = iSettings
Set objParent = iParent
End Sub
Public Property Get Name() As String
' abstract
End Property
Public Function Node(iName As String, iCreate As Boolean) As clsSettingsNodeAPI
' ACTION: Return the root node
' USAGE: abstract; this is model code:
Dim objNode As clsSettingsNodeAPI
Set objNode = New clsSettingsNodeAPI
' find subnode with name iName; initialize objNode with it (not Nothing)
objNode.Init Me, Nothing
Set Node = objNode
End Function
Public Function AddItem(iItem As clsSettingAPI) As clsSettingsNodeAPI
' ACTION: Add an item to the tree; return the item's node object
' abstract
End Function
Public Property Get ValueStored() As String
' ACTION: Returns the *stored* value (not the active value)
' abstract
End Property
Public Property Let ValueStored(iValue As String)
' ACTION: Sets the *stored* value (not the active value)
' abstract
End Property
Public Sub Load()
' abstract
End Sub
Public Sub Save()
' abstract
End Sub
Public Property Get ListNodes() As Scripting.Dictionary
' abstract
End Property
clsSettingsXML
Private strSection As String
Private strAppName As String
Private strFileSpec As String
Private xmlDoc As MSXML2.DOMDocument
Private objRoot As clsSettingsNodeXML
Private isLoaded As Boolean
Public Sub Init(Optional iSection As String = "", Optional iAppName As String = "")
If iSection <> "" Then
strSection = iSection
End If
If iAppName <> "" Then
strAppName = iAppName
End If
strFileSpec = App.Path & "\" & App.Title & ".prefs.xml"
isLoaded = False
End Sub
Public Function Root() As clsSettingsNodeAPI
' ACTION: Return the root node
' Dim objRootSet As clsSettingText
Dim xmlRoot As MSXML2.IXMLDOMNode
If objRoot Is Nothing Then
' Set objRootSet = New clsSettingText
' objRootSet.Init strSection, ""
Set objRoot = New clsSettingsNodeXML
With Me.GetXMLDoc
If .childNodes.Length = 0 Then
Stop ' this should never happen - GetXMLDoc is supposed to create the root node
Else
Set xmlRoot = .firstChild
End If
End With
objRoot.InitRoot strSection, xmlRoot, Me
End If
Set Root = objRoot
End Function
Public Property Get FileSpec() As String
FileSpec = strFileSpec
End Property
Public Property Let FileSpec(iSpec As String)
strFileSpec = iSpec
End Property
Public Sub Load(iForce As Boolean)
' ACTION: Read settings data from file
If iForce Or (Not Me.Loaded) Then
Me.GetXMLDoc.Load Me.FileSpec
' Set xmlDoc = Nothing ' loading the document clears all nodes
isLoaded = True
End If
End Sub
Public Sub Save()
' Dim objNode As clsSettingsNodeXML
' Set objNode = Me.Root
' objNode.Save
Me.GetXMLDoc.Save Me.FileSpec
isLoaded = True
End Sub
Public Function Loaded() As Boolean
Loaded = isLoaded
End Function
Friend Function GetXMLDoc() As MSXML2.DOMDocument
Dim xmlRoot As MSXML2.IXMLDOMNode
' create document if needed
If xmlDoc Is Nothing Then
Set xmlDoc = New MSXML2.DOMDocument
End If
' create root node if needed
If xmlDoc.childNodes.Length = 0 Then
With xmlDoc
' create the root node
Set xmlRoot = .createNode(NODE_ELEMENT, strSection, "")
.appendChild xmlRoot
End With
End If
Set GetXMLDoc = xmlDoc
End Function
Private Sub Class_Initialize()
strSection = "settings" ' default
strAppName = App.EXEName
End Sub
Private Sub clsSettingsAPI_Load(iForce As Boolean)
Me.Load iForce
End Sub
Private Function clsSettingsAPI_Root() As clsSettingsNodeAPI
clsSettingsAPI_Root = Me.Root
End Function
Private Sub clsSettingsAPI_Save()
Me.Save
End Sub
===clsSettingsNodeXML===
<pre>
Option Explicit
Implements clsSettingsNodeAPI
Private strName As String
Private objMain As clsSettingsXML
Private objParent As clsSettingsNodeXML
Public XMLNode As MSXML2.IXMLDOMNode
Private objItem As clsSettingAPI
Private lstNodes As Scripting.Dictionary ' list of sub-nodes
Private lstXMLs As Scripting.Dictionary ' list of XML sub-nodes (refresh before using)
Friend Sub Init(iItem As clsSettingAPI, iXML As MSXML2.IXMLDOMNode, iParent As clsSettingsNodeAPI)
Set objItem = iItem
Set objParent = iParent
Me.InitRoot iItem.Name, iXML, objParent.Main
End Sub
Friend Sub InitRoot(iName As String, iXML As MSXML2.IXMLDOMNode, iMain As clsSettingsXML)
' USAGES: (1) Internally, and (2) for clsSettingsXML to initialize root node only
strName = iName
Set XMLNode = iXML
Set objMain = iMain
End Sub
Friend Sub InitHolder(iName As String, iXML As MSXML2.IXMLDOMNode, iParent As clsSettingsNodeXML)
' USAGE: For initializing a node when we don't yet know what Item it will have
Me.InitRoot iName, iXML, iParent.Main
Set objParent = iParent
Set objItem = Nothing ' this will be set later
End Sub
Friend Property Set Item(iItem As clsSettingAPI)
Set objItem = iItem
End Property
Friend Property Get Item() As clsSettingAPI
Set Item = objItem
End Property
Friend Property Get Main() As clsSettingsXML
Set Main = objMain
End Property
Friend Property Set Main(iDoc As clsSettingsXML)
Set objMain = iDoc
End Property
Public Function Node(iName As String, iCreate As Boolean) As clsSettingsNodeXML
' ACTION: Return the node with the given name
Dim objNode As clsSettingsNodeXML
' Set objNode = New clsSettingsNodeXML
' find subnode with name iName; initialize objNode with it (not Nothing)
With Me.ListNodes
If .Exists(iName) Then
Set objNode = .Item(iName)
Else
If iCreate Then
Set objNode = New clsSettingsNodeXML
objNode.InitHolder iName, MakeXML(iName), Me
Else
Set objNode = Nothing
End If
End If
End With
Set Node = objNode
End Function
Public Property Get Name() As String
Name = strName
End Property
Public Property Get ValueStored() As String
' ACTION: Returns the *stored* value (not the active value)
If Not (XMLNode Is Nothing) Then
ValueStored = XMLNode.Text
End If
End Property
Public Property Let ValueStored(iValue As String)
' ACTION: Sets the *stored* value (not the active value)
If XMLNode Is Nothing Then
Stop ' does this ever happen?
End If
XMLNode.Text = iValue
End Property
Public Property Get ValueActive() As String
If Not (objItem Is Nothing) Then
ValueActive = objItem.Value
End If
End Property
Public Property Let ValueActive(iValue As String)
' ACTION: Sets the *stored* value (not the active value)
If Not (objItem Is Nothing) Then
objItem.Value = iValue
End If
End Property
Public Sub Load()
' ACTION: Read values from the XML DOM tree and stash them in clsSettingAPI objects
Dim xmlThis As MSXML2.IXMLDOMNode
Dim objSub As clsSettingsNodeXML
' 1. Look through the document and pull out values into the value list; save pointer to each node found
Set xmlThis = XMLNode.firstChild
Do While Not (xmlThis Is Nothing)
With xmlThis
If Not Me.ListNodes.Exists(.nodeName) Then
' ' this block duplicates code in me.List; should probably put it in a subroutine
' Set objSub = New clsSettingsNodeXML
' objSub.InitHolder .baseName, xmlThis, Me
' lstNodes.Add .baseName, Nothing
AddHolder xmlThis
End If
Set objSub = Me.Node(.nodeName, False)
objSub.ValueActive = .Text
End With
Set xmlThis = xmlThis.nextSibling
Loop
' 2. Go through the list and call Load() recursively
LoadSub
End Sub
Private Sub AddHolder(iXML As MSXML2.IXMLDOMNode)
Dim objSub As clsSettingsNodeXML
Dim strName As String
strName = iXML.nodeName
Set objSub = New clsSettingsNodeXML
objSub.InitHolder strName, iXML, Me
lstNodes.Add strName, objSub
End Sub
Private Sub LoadSub()
Dim idxSub As Long
Dim objNode As clsSettingsNodeXML
For idxSub = 0 To Me.ListNodes.Count - 1
Set objNode = Me.ListNodes.Items(idxSub)
objNode.Load
Next
End Sub
Public Sub Save()
' ACTION:
Dim xmlDoc As MSXML2.DOMDocument
Dim xmlParent As MSXML2.IXMLDOMNode
Dim xmlThis As MSXML2.IXMLDOMNode
Dim strName As String
Set xmlDoc = objMain.GetXMLDoc
' ' there doesn't seem to be any way to find out if a child node already exists,
' ' so for now we just kill them all.
' Set xmlThis = xmlNode.firstChild
' Do While Not (xmlThis Is Nothing)
' Set xmlNext = xmlThis.nextSibling
' xmlNode.removeChild xmlThis
' Set xmlThis = xmlNext
' Loop
' If xmlSelf.childNodes.Length <> 0 Then Stop
If Not (objItem Is Nothing) Then
objItem.Save Me ' tell the object to save itself, in case it has any sub-items
' Me.XMLNode.Text = objItem.Value
End If
' recursively repeat for all sub-nodes:
SaveSub
End Sub
Private Sub SaveSub()
Dim idxSub As Long
' Dim objNode As clsSettingAPI
' Dim objNode As Object
Dim objNode As clsSettingsNodeXML
Dim strKey As String
Dim xmlThis As MSXML2.IXMLDOMNode
Set lstXMLs = ListXMLs
With Me.ListNodes
For idxSub = 0 To .Count - 1
Set objNode = .Items(idxSub)
strKey = objNode.Name
If Not lstXMLs.Exists(strKey) Then
' if an XML node does not already exist, create it and add it to the parent node (i.e. Me)
Set xmlThis = objMain.GetXMLDoc.createNode(NODE_ELEMENT, strKey, "")
' Don't save the data here; that's done in objNode.Save()
' xmlThis.Text = objNode.ValueActive
Me.XMLNode.appendChild xmlThis
Debug.Assert Not (xmlThis.parentNode Is Nothing)
End If
objNode.Save
' Debug.Print "NAME=" & strName
' Set xmlText = xmlDoc.createTextNode(.Items(intItem))
' xmlThis.appendChild xmlText
Next
End With
End Sub
Friend Property Get ListXMLs() As Scripting.Dictionary
' ACTION: get a list of XML subnodes
' maybe this is doing more processing than needed...
Dim xmlKid As MSXML2.IXMLDOMNode
Set lstXMLs = New Scripting.Dictionary
Set xmlKid = Me.XMLNode.firstChild
Do Until xmlKid Is Nothing
lstXMLs.Add xmlKid.nodeName, xmlKid
Set xmlKid = xmlKid.nextSibling
Loop
Set ListXMLs = lstXMLs
End Property
'Friend Function GetXMLNode() As MSXML2.IXMLDOMNode
' Dim xmlDoc As MSXML2.DOMDocument
' Dim xmlRoot As MSXML2.IXMLDOMNode
' Dim xmlNode As MSXML2.IXMLDOMNode
'
' Set xmlDoc = objMain.GetXMLDoc
'
' If objParent Is Nothing Then
' ' need root node
' If xmlDoc.childNodes.Length = 0 Then
' ' none exists yet; create it:
' Set xmlNode = xmlDoc.createNode(NODE_ELEMENT, Me.Name, "")
' xmlDoc.appendChild xmlNode
' Else
' Set xmlNode = xmlDoc.firstChild
' End If
' Else
' ' create non-root node
' Set xmlNode = xmlDoc.createNode(NODE_ELEMENT, Me.Name, "")
' Set xmlRoot = objParent.GetXMLNode
' xmlRoot.appendChild xmlNode
' End If
'
' Set GetXMLNode = xmlNode
'End Function
Public Property Get ListNodes() As Scripting.Dictionary
Dim xmlDoc As MSXML2.DOMDocument
Dim xmlRoot As MSXML2.IXMLDOMElement
Dim xmlThis As MSXML2.IXMLDOMNode
' Dim objNew As clsSettingsNodeXML
If lstNodes Is Nothing Then
' What is often happening at this point is that the XML document has been loaded, but items have not been assigned.
' So we have to set up item-nodes without knowing what objects to assign to them.
' 2006-02-12 actually, this list should never be rebuilt from the XML tree. For one thing, that tree has stuff we don't want
' to put in the nodes list. For another, this list is maintained explicitly.
' Correction: unfortunately, we need to be able to list items not explicitly added, for things like dynamic lists
Set lstNodes = New Scripting.Dictionary ' create the list object
If Not (Me.XMLNode Is Nothing) Then
If Me.XMLNode.nodeType <> NODE_TEXT Then
' text nodes get saved along with their parent
For Each xmlThis In Me.XMLNode.childNodes
With xmlThis
' Set objNew = New clsSettingsNodeXML
' objNew.InitHolder .baseName, xmlThis, Me
' lstNodes.Add .nodeName, .Text
AddHolder xmlThis
End With
Next
End If
End If
End If
Set ListNodes = lstNodes
End Property
'Public Property Get Exists(iName As String)
' Exists = Me.ListItems.Exists(iName)
'End Property
Public Sub LoadTextBox(iCtrl As TextBox, Optional iName As String = "")
Dim strName As String
If iName = "" Then
strName = iCtrl.Name
Else
strName = iName
End If
If Me.ListNodes.Exists(strName) Then
iCtrl.Text = Me.Node(strName, False).ValueStored
End If
End Sub
Public Sub SaveTextBox(iCtrl As TextBox, Optional iName As String = "")
Dim strName As String
Dim strValue As String
If iName = "" Then
strName = iCtrl.Name
Else
strName = iName
End If
strValue = iCtrl.Text
Me.Node(strName, True).ValueStored = strValue
End Sub
Public Sub LoadChkBox(iCtrl As CheckBox, Optional iName As String = "")
Dim strName As String
Dim strValue As String
If iName = "" Then
strName = iCtrl.Name
Else
strName = iName
End If
strValue = Me.Node(strName, False).ValueStored
If strValue <> "" Then
If strValue = "0" Then
iCtrl.Value = vbUnchecked
Else
iCtrl.Value = vbChecked
End If
End If
End Sub
Public Sub SaveChkBox(iCtrl As CheckBox, Optional iName As String = "")
Dim strName As String
Dim strValue As String
Dim objNode As clsSettingsNodeAPI
If iName = "" Then
strName = iCtrl.Name
Else
strName = iName
End If
Set objNode = Me.Node(strName, True)
If iCtrl.Value Then
strValue = "1"
Else
strValue = "0"
End If
objNode.ValueStored = strValue
End Sub
Public Sub LoadCombo(iCombo As ComboBox, Optional iName As String = "")
Dim strName As String
Dim strValue As String
Dim idxValue As Long
If iName = "" Then
strName = iCombo.Name
Else
strName = iName
End If
strValue = Me.Node(strName, False).ValueStored
If IsNumeric(strValue) Then
idxValue = CLng(strValue)
If idxValue < iCombo.ListCount Then
iCombo.ListIndex = CLng(strValue)
End If
End If
End Sub
Public Sub SaveCombo(iCombo As ComboBox, Optional iName As String = "")
Dim strName As String
Dim strValue As String
If iName = "" Then
strName = iCombo.Name
Else
strName = iName
End If
strValue = CStr(iCombo.ListIndex)
Me.Node(strName, True).ValueStored = strValue
End Sub
Private Function MakeXML(iName As String) As MSXML2.IXMLDOMNode
Dim objXML As MSXML2.IXMLDOMNode
If Me.ListXMLs.Exists(iName) Then
Set objXML = lstXMLs.Item(iName)
Else
Set objXML = objMain.GetXMLDoc.createNode(NODE_ELEMENT, iName, "")
Me.XMLNode.appendChild objXML
End If
Set MakeXML = objXML
End Function
Private Function clsSettingsNodeAPI_AddItem(iItem As clsSettingAPI) As clsSettingsNodeAPI
Dim objNode As clsSettingsNodeXML
Dim objXML As MSXML2.IXMLDOMNode
If Me.XMLNode Is Nothing Then Stop
If Me.ListNodes.Exists(iItem.Name) Then
Set objNode = lstNodes.Item(iItem.Name)
Set objNode.Item = iItem
Else
Set objNode = New clsSettingsNodeXML
Set objXML = MakeXML(iItem.Name)
objNode.Init iItem, objXML, Me
lstNodes.Add iItem.Name, objNode
End If
End Function
Private Property Get clsSettingsNodeAPI_ListNodes() As Scripting.IDictionary
Set clsSettingsNodeAPI_ListNodes = Me.ListNodes
End Property
Private Sub clsSettingsNodeAPI_Load()
' ACTION:
' - Load the current node's value
' - Iterate through children and call Load() for each
Dim objNode As clsSettingsNodeXML
Dim objKid As clsSettingAPI
Dim idxKid As Long
If Not (objItem Is Nothing) Then
objItem.Load Me
End If
For idxKid = 0 To Me.ListNodes.Count - 1
Set objNode = lstNodes.Items(idxKid)
Set objKid = objNode.Item
objKid.Load objNode
Next
End Sub
Private Property Get clsSettingsNodeAPI_Name() As String
clsSettingsNodeAPI_Name = Me.Name
End Property
Private Function clsSettingsNodeAPI_Node(iName As String, iCreate As Boolean) As clsSettingsNodeAPI
Set clsSettingsNodeAPI_Node = Me.Node(iName, iCreate)
End Function
Private Sub clsSettingsNodeAPI_Save()
Me.Save
End Sub
Private Property Let clsSettingsNodeAPI_ValueStored(RHS As String)
Me.ValueStored = RHS
End Property
Private Property Get clsSettingsNodeAPI_ValueStored() As String
clsSettingsNodeAPI_ValueStored = Me.ValueStored
End Property
clsSettingText
An implementation of #clsSettingAPI
Option Explicit
' CLASS: clsSettingText
' PURPOSE: generic clsSettingAPI class for storing a text value
' without needing an associated control
Implements clsSettingAPI
Private strName As String
Private strVal As String
Public Sub Init(iName As String, iVal As String)
strName = iName
strVal = iVal
End Sub
Private Sub clsSettingAPI_Load(iNode As clsSettingsNodeAPI)
strVal = iNode.ValueStored
End Sub
Private Property Let clsSettingAPI_Name(ByVal RHS As String)
strName = RHS
End Property
Private Property Get clsSettingAPI_Name() As String
clsSettingAPI_Name = strName
End Property
Private Sub clsSettingAPI_Save(iNode As clsSettingsNodeAPI)
iNode.ValueStored = strVal
End Sub
Private Property Let clsSettingAPI_Value(ByVal RHS As String)
strVal = RHS
End Property
Private Property Get clsSettingAPI_Value() As String
clsSettingAPI_Value = strVal
End Property
xtForm
Implementation of #clsSettingAPI
' CLASS: xtForm
' PURPOSE: extended form functionality
' HISTORY:
' 1999.12.06 - NJS - adapted from Forms.bas
' 2001.01.03 - NJS - modified to use clsConfigReg for storing position
' 2006-01-22 - Wzl - modified to use clsSettingsAPI for storing position
Implements clsSettingAPI
Public WithEvents Form As Form
Public doSaveSize As Boolean
Public doAutoSave As Boolean
Private strPos As String
'Private objSet As clsSettingsNodeAPI
Private strName As String
Option Explicit
Public Sub Init(iForm As Form)
Set Form = iForm
doSaveSize = (iForm.BorderStyle = vbSizable) Or (iForm.BorderStyle = vbSizableToolWindow)
' Set objSet = iSettings
' Me.LoadPos
strName = iForm.Name
End Sub
Public Property Let Pos(istr As String)
strPos = istr
If Me.Form.Visible Then Me.PlaceForm
End Property
Public Property Get Pos() As String
' ACTION: return a string specifying the given form's size and position
Dim ostr As String
Dim can_size As Boolean
With Me.Form
If .WindowState <> vbNormal Then
.WindowState = vbNormal ' so dimensions are meaningful when we read them
End If
ostr = .Left & " " & .Top
If doSaveSize Then
ostr = ostr & " " & .Width & " " & .Height
End If
End With
Pos = ostr
End Property
'Public Sub Reveal()
'' ACTION: show the given form, and make sure it's not minimized.
' With Me.Form
' .Show
' If .WindowState = vbMinimized Then
' .WindowState = vbNormal
' End If
' LoadPos ' restore saved position
' End With
'End Sub
'Public Sub LoadPos(Optional iKey As String)
' If iKey = "" Then
' iKey = Form.Name
' End If
' Me.Pos = objSet.Value(iKey)
' PlaceForm
'End Sub
'Public Sub SavePos(Optional iKey As String)
'' PURPOSE: saves the given form's position to the registry
' With Me.Form
' If iKey = "" Then
' iKey = .Name
' End If
' If .Visible And (.WindowState <> vbMinimized) Then
' objSet.Value(iKey) = Me.Pos
' End If
' End With
'End Sub
Public Sub PlaceForm()
' ACTION: set the given form's size and position from the stored string
Dim oarr As Variant
Dim can_size As Boolean
If strPos = "" Then
' use default position: center in screen
With Me.Form
.Left = (Screen.Width - .Width) / 2
.Top = (Screen.Height - .Height) / 2
End With
Else
' use stored position (and size if applicable)
oarr = Split(strPos, " ")
With Form
.Left = oarr(0)
.Top = oarr(1)
If .Left > Screen.Width Then
.Left = Screen.Width - .Width
End If
If .Top > Screen.Height Then
.Top = Screen.Height - .Height
End If
If doSaveSize And (UBound(oarr) > 1) Then
.Width = oarr(2)
.Height = oarr(3)
End If
End With
End If
End Sub
Private Sub clsSettingAPI_Load(iNode As clsSettingsNodeAPI)
strPos = iNode.ValueStored
PlaceForm
End Sub
Private Property Let clsSettingAPI_Name(ByVal RHS As String)
strName = RHS
End Property
Private Property Get clsSettingAPI_Name() As String
clsSettingAPI_Name = strName
End Property
Private Sub clsSettingAPI_Save(iNode As clsSettingsNodeAPI)
iNode.ValueStored = Me.Pos
End Sub
Private Property Let clsSettingAPI_Value(ByVal RHS As String)
strPos = RHS
PlaceForm
End Property
Private Property Get clsSettingAPI_Value() As String
clsSettingAPI_Value = Me.Pos
End Property
'Private Sub Form_Activate()
' EVENT: runs the first time the form is shown
' Me.PlaceForm
'End Sub
Private Sub Form_Deactivate()
strPos = Me.Pos ' get new position
End Sub
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
' This is redundant; xtForm also handles this
' If doAutoSave Then
' Me.SavePos
' End If
End Sub
xtListBox
Implementation of #clsSettingAPI
' CLASS: xtListBox
' PURPOSE: extended functionality for ListBox control
' REQUIRES: xtListItems.cls, xtListItem.cls, property let/get AppSetting(Name)
Option Explicit
Implements clsSettingAPI
Public Event ItemChosen(oKey As Long)
Public Event ItemChecked(oKey As Long)
'Public Config As clsSettingsNodeAPI
'local variable(s) to hold property value(s)
Private strName As String
Private WithEvents ctrlList As ListBox 'local copy
Private WithEvents ctrlForm As Form
Private lstItems As Scripting.Dictionary
Private doDataSave As Boolean
Private intNewItem As Long
Public Sub Init(iList As ListBox, Optional iDataSave As Boolean = True)
Set ctrlList = iList
Set ctrlForm = iList.Parent
' Set Config = iConfig
Set lstItems = New Scripting.Dictionary
doDataSave = iDataSave
With ctrlList
strName = .Parent.Name & "." & .Name
End With
End Sub
Public Property Get List() As Scripting.Dictionary
Set List = lstItems
End Property
Public Sub Add(iText As String, Optional iObj As Object)
' NOTES:
' 1. Add object to list
' 2. Add text to listbox, with ItemData pointer to list
With ctrlList
intNewItem = intNewItem + 1
.AddItem iText
.ItemData(.NewIndex) = intNewItem
lstItems.Add intNewItem, iObj
End With
End Sub
Public Sub Clear()
ctrlList.Clear
lstItems.RemoveAll
End Sub
Public Property Get SelectedItem() As Object
' to be written
End Property
Public Function SelectCount() As Integer
SelectCount = ctrlList.SelCount
End Function
Public Sub ClearSelection()
' PURPOSE: removes any selections from the control
Dim idxList As Long, qtyList As Long
qtyList = ctrlList.ListCount
For idxList = 1 To qtyList
ctrlList.Selected(idxList) = False
Next
End Sub
Public Sub DelSelected()
' ACTION: Delete selected items from the list
Dim idxList As Long, qtyList As Long
Dim idxDel As Long
qtyList = Me.List.Count
For idxList = qtyList - 1 To 0 Step -1
' find the item's key in the stored list
idxDel = lstItems.Keys(idxList)
If ctrlList.Selected(idxList) Then
' delete item from the stored list
lstItems.Remove idxDel
' delete item from the control
ctrlList.RemoveItem idxList
End If
Next
End Sub
Public Sub KeyDown(KeyCode As Integer, Shift As Integer)
'' PURPOSE: define additional standard behavior for non-ASCII key presses
'' ACTION: Shift-Insert copies list contents to clipboard
' If ((KeyCode = vbKeyInsert) And (Shift & &H1)) Then
' ClipCopy
' End If
End Sub
Private Sub clsSettingAPI_Load(iNode As clsSettingsNodeAPI)
' ACTION: Load current selection and, if applicable, list data
Dim qtyItem As Long
Dim idxItem As Long
Dim objNode As clsSettingsNodeAPI
Dim strVal As String
' Me.Value = iNode.ValueStored
' check for saved data list
qtyItem = iNode.ListNodes.Count
' load data, if there is any
For idxItem = 0 To qtyItem - 1
Set objNode = iNode.ListNodes.Items(idxItem)
strVal = objNode.ValueStored
If objNode.Name = "selected" Then
Me.Value = objNode.ValueStored
Else
If strVal <> "" Then
' klugefix
If Not Me.List.Exists(strVal) Then
Me.Add strVal
End If
End If
End If
Next
End Sub
Private Property Let clsSettingAPI_Name(ByVal RHS As String)
strName = RHS
End Property
Private Property Get clsSettingAPI_Name() As String
clsSettingAPI_Name = strName
End Property
Private Sub clsSettingAPI_Save(iNode As clsSettingsNodeAPI)
Dim idxItem As Long
Dim objNode As clsSettingsNodeAPI
Set objNode = iNode.Node("selected", True)
objNode.ValueStored = Me.Value
If doDataSave Then
For idxItem = 0 To Me.List.Count - 1
Set objNode = iNode.Node("line-" & CStr(idxItem), True)
objNode.ValueStored = ctrlList.List(idxItem)
Next
End If
End Sub
Public Property Let Value(iValue As String)
Dim xtsVal As xtString
Dim idxSel As Long
Dim strSel As String
Set xtsVal = New xtString
xtsVal.Value = iValue ' data is list of selected items, by index
' parse data
strSel = xtsVal.FindFirst
Do While xtsVal.IsFound
If IsNumeric(strSel) Then
idxSel = CLng(strSel)
ctrlList.Selected(idxSel) = True
End If
strSel = xtsVal.FindNext
Loop
End Property
Public Property Get Value() As String
Dim strSel As String
Dim idxItem As Long
' debugging:
' If Me.List.Count <> ctrlList.ListCount Then Stop
For idxItem = 0 To ctrlList.ListCount - 1
If ctrlList.Selected(idxItem) Then
strSel = strSel & " " & idxItem
End If
Next
Value = strSel
End Property
Private Property Let clsSettingAPI_Value(ByVal RHS As String)
Me.Value = RHS
End Property
Private Property Get clsSettingAPI_Value() As String
clsSettingAPI_Value = Me.Value
End Property
Private Sub ctrlList_Click()
Dim intLine As Integer
Dim intKey As Long
intLine = ctrlList.ListIndex
intKey = ctrlList.ItemData(intLine)
If intKey Then
RaiseEvent ItemChosen(intKey)
End If
End Sub
Private Sub ctrlList_ItemCheck(Item As Integer)
Dim intKey As Long
If Me.List.Exists(Item) Then
intKey = ctrlList.ItemData(Item)
RaiseEvent ItemChecked(intKey)
End If
End Sub