Option Strict On
Imports System
Imports System.Diagnostics
Imports System.Collections.Generic
Imports Inventor
Public Class ContentCenterReplace
Inherits ComponentReplacer
Private m_ccFinder As ContentCenterFinder
Public Sub New(ByVal doc As Document, ByVal app As Application, Optional ByVal Language As String = "en-US")
MyBase.New(doc, app, "ContentCenterReplace")
m_ccFinder = New ContentCenterFinder(app, Language)
m_ccFinder.CustomPartDirectory = CustomPartDirectory
End Sub
Public Property CreationMode() As FileCreationMode
Get
Return m_ccFinder.CreationMode
End Get
Set(ByVal value As FileCreationMode)
m_ccFinder.CreationMode = value
End Set
End Property
Public Property KeyColumnName() As String
Get
Return m_ccFinder.KeyColumnName
End Get
Set(ByVal value As String)
m_ccFinder.KeyColumnName = value
End Set
End Property
Public Sub Replace(ByVal componentName As String, ByVal ccPath As String, ByVal partNumber As String)
Dim occurrence As ComponentOccurrence = FindComponent(componentName)
Replace(occurrence, ccPath, partNumber)
End Sub
Public Sub Replace(ByVal occurrence As ComponentOccurrence, ByVal ccPath As String, ByVal partNumber As String)
Dim newFileName As String = GetPartFullFileName(ccPath, partNumber)
ReplaceComponent(occurrence, newFileName)
End Sub
Public Sub ReplaceCustom(ByVal componentName As String, ByVal ccPath As String, ByVal partNumber As String, ByVal fileName As String, ByVal ParamArray customNamesAndValues() As Object)
Dim occurrence As ComponentOccurrence = FindComponent(componentName)
ReplaceCustom(occurrence, ccPath, partNumber, fileName, customNamesAndValues)
End Sub
Public Sub ReplaceCustom(ByVal occurrence As ComponentOccurrence, ByVal ccPath As String, ByVal partNumber As String, ByVal fileName As String, ByVal ParamArray customNamesAndValues() As Object)
Dim newFileName As String = GetCustomPartFullFileName(ccPath, partNumber, fileName, customNamesAndValues)
ReplaceComponent(occurrence, newFileName)
End Sub
Public Function GetPartFullFileName(ByVal ccPath As String, ByVal partNumber As String) As String
m_ccFinder.CustomPartDirectory = CustomPartDirectory
Dim newFileName As String = m_ccFinder.FindFileName(ccPath, partNumber, Nothing)
CheckFileName(newFileName, ccPath, partNumber)
Return newFileName
End Function
Public Function GetCustomPartFullFileName(ByVal ccPath As String, ByVal partNumber As String, ByVal fileName As String, ByVal ParamArray customNamesAndValues() As Object) As String
m_ccFinder.CustomPartDirectory = CustomPartDirectory
Dim customNv As NameValueMap = CreateCustomMap(customNamesAndValues)
Dim customInfo As New CustomInformation()
customInfo.CustomFileName = fileName
customInfo.CustomNamesAndValues = customNv
Dim newFileName As String = m_ccFinder.FindFileName(ccPath, partNumber, customInfo)
CheckFileName(newFileName, ccPath, partNumber)
Return newFileName
End Function
Private Sub CheckFileName(ByVal newFileName As String, ByVal ccPath As String, ByVal partNumber As String)
If String.IsNullOrEmpty(newFileName) Then
Throw New ArgumentException(String.Format("Content Center part not found: {0}:{1}", ccPath, partNumber))
End If
End Sub
Public Function GetValuesInColumn(ByVal familyPathName As String, ByVal columnName As String) As ArrayList
Return m_ccFinder.GetValuesInColumn(familyPathName, columnName)
End Function
Public Function GetFamilyNames(ByVal pathName As String) As ArrayList
Return m_ccFinder.GetFamilyNames(pathName)
End Function
Public Function GetMatchingFamilyNames(ByVal pathName As String, ByVal partialName As String, Optional ByVal options As FamilyMatchingOptions = FamilyMatchingOptions.Recurse) As Dictionary(Of String, String)
Return m_ccFinder.GetMatchingFamilyNames(pathName, partialName, options)
End Function
Private Sub ReplaceComponent(ByVal occurrence As ComponentOccurrence, ByVal newFileName As String)
ReplaceIfDifferent(occurrence, newFileName)
End Sub
Private Function CreateCustomMap(ByVal ParamArray customNamesAndValues() As Object) As NameValueMap
Dim customNv As NameValueMap = m_app.TransientObjects.CreateNameValueMap()
Dim currentName As String = String.Empty
Dim index As Integer
For Each obj As Object In customNamesAndValues
If (index Mod 2 = 0) Then
currentName = obj.ToString()
Else
customNv.Add(currentName, obj)
End If
index += 1
Next
Return customNv
End Function
End Class
Public Enum FileCreationMode
ByFunctionName = 0
AsCustom
AsStandard
End Enum
Public Class NonContentReplace
Inherits ComponentReplacer
Private m_sourcePartDirectory As String
Public Sub New(ByVal doc As Document, ByVal app As Application)
MyBase.New(doc, app, "NonContentReplace")
m_sourcePartDirectory = IO.Path.GetDirectoryName(doc.FullFileName)
End Sub
Public Property SourcePartDirectory() As String
Get
Return m_sourcePartDirectory
End Get
Set(ByVal value As String)
m_sourcePartDirectory = value
End Set
End Property
Public Sub ReplaceCustom(ByVal componentName As String, ByVal templateFileName As String, ByVal generatedFileName As String, ByVal ParamArray parameterNamesAndValues() As Object)
Dim occurrence As ComponentOccurrence = FindComponent(componentName)
ReplaceCustom(occurrence, templateFileName, generatedFileName, parameterNamesAndValues)
End Sub
Public Sub ReplaceCustom(ByVal occurrence As ComponentOccurrence, ByVal templateFileName As String, ByVal generatedFileName As String, ByVal ParamArray parameterNamesAndValues() As Object)
If (Not generatedFileName.EndsWith(".ipt", StringComparison.OrdinalIgnoreCase)) Then
generatedFileName += ".ipt"
End If
Dim fullPath As String = IO.Path.Combine(CustomPartDirectory, generatedFileName)
If (Not IO.File.Exists(fullPath)) Then
GeneratePart(fullPath, templateFileName, parameterNamesAndValues)
End If
ReplaceIfDifferent(occurrence, fullPath)
End Sub
Sub GeneratePart(ByVal generatedFullFileName As String, ByVal templateFileName As String, ByVal parameterNamesAndValues() As Object)
Dim templateFullFileName As String = IO.Path.Combine(m_sourcePartDirectory, templateFileName)
Dim templatePart As Document = Nothing
Try
templatePart = m_app.Documents.Open(templateFullFileName, False)
Catch ex As Exception
Throw New ArgumentException("NonContentReplace: Cannot find (or open) the part: " + templateFullFileName, ex)
End Try
Try
templatePart.SaveAs(generatedFullFileName, True)
Catch ex As Exception
Throw New ArgumentException("NonContentReplace: Cannot save the generated part: " + generatedFullFileName, ex)
Finally
templatePart.ReleaseReference()
End Try
If (parameterNamesAndValues.Length = 0) Then Return
Dim errorHit As Boolean = False
Dim parameterNotFound As String = String.Empty
Dim generatedPart As Document = m_app.Documents.Open(generatedFullFileName, False)
Try
Dim partDoc As PartDocument = DirectCast(generatedPart, PartDocument)
Dim partParams As Parameters = partDoc.ComponentDefinition.Parameters
Dim currentName As String = String.Empty
Dim index As Integer
For Each obj As Object In parameterNamesAndValues
If (index Mod 2 = 0) Then
currentName = obj.ToString()
Else
Dim foundParameter As Boolean = AssignParameter(currentName, obj, partParams)
If (Not foundParameter) Then
parameterNotFound = currentName
Exit For
End If
End If
index += 1
Next
generatedPart.Update()
generatedPart.Save()
Catch ex As Exception
errorHit = True
Finally
generatedPart.ReleaseReference()
End Try
If (errorHit) Then
Throw New ArgumentException("NonContentReplace: Error assigning parameters in the part: " + generatedFullFileName)
End If
If (Not String.IsNullOrEmpty(parameterNotFound)) Then
Throw New ArgumentException(String.Format("NonContentReplace: No parameter named {0} was not found in the part {1}", parameterNotFound, generatedFullFileName))
End If
End Sub
Function AssignParameter(ByVal currentName As String, ByVal obj As Object, ByVal partParams As Parameters) As Boolean
For Each param As Inventor.Parameter In partParams
If (param.Name = currentName) Then
If (param.Units = "Text") Then
param.Value = obj.ToString()
ElseIf (param.Units = "Boolean") Then
param.Value = CBool(obj)
Else
param.Expression = obj.ToString()
End If
Return True
End If
Next
Return False
End Function
End Class
Public MustInherit Class ComponentReplacer
Protected m_doc As Document
Protected m_app As Inventor.Application
Protected m_assemDoc As AssemblyDocument
Protected m_customPartDirectory As String
Protected m_replaceAll As Boolean
Protected m_deleteOldFiles As Boolean
Private m_derivedClassName As String
Public Sub New(ByVal doc As Document, ByVal app As Application, ByVal derivedClassName As String)
m_doc = doc
m_app = app
m_derivedClassName = derivedClassName
m_assemDoc = TryCast(m_doc, AssemblyDocument)
If (m_assemDoc Is Nothing) Then
Throw New ArgumentException(m_derivedClassName & " can only be used in an assembly.")
End If
If (Not String.IsNullOrEmpty(m_doc.FullFileName)) Then
CustomPartDirectory = IO.Path.GetDirectoryName(m_doc.FullFileName)
End If
End Sub
Public Property CustomPartDirectory() As String
Get
Return m_customPartDirectory
End Get
Set(ByVal value As String)
m_customPartDirectory = value
End Set
End Property
Public Property ReplaceAll() As Boolean
Get
Return m_replaceAll
End Get
Set(ByVal value As Boolean)
m_replaceAll = value
End Set
End Property
Public Property DeleteOldFiles() As Boolean
Get
Return m_deleteOldFiles
End Get
Set(ByVal value As Boolean)
m_deleteOldFiles = value
End Set
End Property
Friend Sub ReplaceIfDifferent(ByVal occurrence As ComponentOccurrence, ByVal newFileName As String)
Dim oldFileName As String = GetFileName(occurrence)
If (String.Equals(oldFileName, newFileName, StringComparison.OrdinalIgnoreCase)) Then
Trace.WriteLine(" Component doesn't need replacement: " & newFileName)
Return
End If
occurrence.Replace(newFileName, ReplaceAll)
If (DeleteOldFiles) Then DeleteFile(oldFileName)
End Sub
Friend Function GetFileName(ByVal occurrence As ComponentOccurrence) As String
Dim oldFileName As String = String.Empty
Try
oldFileName = occurrence.ReferencedDocumentDescriptor.FullDocumentName
Catch
End Try
Return oldFileName
End Function
Friend Function FindComponent(ByVal componentName As String) As ComponentOccurrence
Dim compDef As AssemblyComponentDefinition = m_assemDoc.ComponentDefinition
For Each compOcc As Inventor.ComponentOccurrence In compDef.Occurrences
If String.Equals(compOcc.Name, componentName, StringComparison.OrdinalIgnoreCase) Then
Return compOcc
End If
Next
Throw New ArgumentException(m_derivedClassName + ": component not found: " & componentName)
End Function
Public Sub DeleteFile(ByVal fileName As String)
If (String.IsNullOrEmpty(fileName)) Then Return
If (Not fileName.StartsWith(CustomPartDirectory, StringComparison.OrdinalIgnoreCase)) Then
Return ' Don't delete standard parts.
End If
m_app.Documents.CloseAll(UnreferencedOnly:=True)
For Each doc As Document In m_app.Documents
If (String.Equals(doc.FullFileName, fileName) AndAlso doc.Open) Then Return
If (DocumentHasReferenceTo(doc, fileName)) Then Return
Next
Trace.WriteLine("Deleting file: " & fileName)
Try
IO.File.Delete(fileName)
Catch
' We don't care if we can't delete. There is probably a good reason.
End Try
End Sub
Function DocumentHasReferenceTo(ByVal doc As Document, ByVal fileName As String) As Boolean
For Each docdesc As DocumentDescriptor In doc.ReferencedDocumentDescriptors
If (docdesc.FullDocumentName.Contains("<")) Then Continue For
' Compare only filename, not path. The path may not be resolved yet.
If (String.Equals(IO.Path.GetFileName(docdesc.FullDocumentName), IO.Path.GetFileName(fileName), StringComparison.OrdinalIgnoreCase)) Then
Return True
End If
Next
Return False
End Function
End Class
Class ContentCenterFinder
Private m_app As Application
Private m_pathName As String
Private m_pathLevels As String()
' n-1 levels are category names. Last level is family name
Private m_partNumber As String
Private m_ccDir As String
Private m_customPartDirectory As String
Private m_customInfo As CustomInformation
Private m_keyColumnName As String
Private m_creationMode As FileCreationMode
Public Sub New(ByVal app As Application, Optional ByVal Language As String = "en-US")
m_app = app
m_ccDir = System.IO.Path.Combine(m_app.DesignProjectManager.ActiveDesignProject.ContentCenterPath, Language)
m_creationMode = FileCreationMode.ByFunctionName
End Sub
Public Property CustomPartDirectory() As String
Get
Return m_customPartDirectory
End Get
Set(ByVal value As String)
m_customPartDirectory = value
End Set
End Property
Public Property KeyColumnName() As String
Get
Return m_keyColumnName
End Get
Set(ByVal value As String)
m_keyColumnName = value
End Set
End Property
Public Property CreationMode() As FileCreationMode
Get
Return m_creationMode
End Get
Set(ByVal value As FileCreationMode)
m_creationMode = value
End Set
End Property
Friend Function FindFileName(ByVal familyPathName As String, ByVal partNumber As String, ByVal customInfo As CustomInformation) As String
CompensateForComDefect()
m_partNumber = partNumber
m_customInfo = customInfo
Dim family As ContentFamily = FindFamily(familyPathName)
Return FindFileByColumn(family)
End Function
Friend Function GetValuesInColumn(ByVal familyPathName As String, ByVal columnName As String) As ArrayList
CompensateForComDefect()
Dim family As ContentFamily = FindFamily(familyPathName)
Dim values As New ArrayList
Dim col As ContentTableColumn = FindColumn(family, columnName)
If (col Is Nothing) Then
Throw New ArgumentException("ContentCenterReplace.GetValuesInColumn: Column name not found: " & columnName)
End If
For Each row As ContentTableRow In family.TableRows
Dim cell As ContentTableCell = row.Item(col.InternalName)
values.Add(cell.Value)
Next
Return values
End Function
Friend Function GetFamilyNames(ByVal pathName As String) As ArrayList
CompensateForComDefect()
ParseLevels(pathName)
Dim node As ContentTreeViewNode = FindNode(m_app.ContentCenter.TreeViewTopNode, 0)
If (node Is Nothing) Then
Throw New ArgumentException("GetFamilyNames: content center category not found: " & pathName)
End If
Dim names As New ArrayList
For Each family As ContentFamily In node.Families
names.Add(family.DisplayName)
Next
Return names
End Function
Friend Function GetMatchingFamilyNames(ByVal pathName As String, ByVal partialName As String, Optional ByVal options As FamilyMatchingOptions = FamilyMatchingOptions.Recurse) As Dictionary(Of String, String)
CompensateForComDefect()
ParseLevels(pathName)
Dim node As ContentTreeViewNode = FindNode(m_app.ContentCenter.TreeViewTopNode, 0)
If (node Is Nothing) Then
Throw New ArgumentException("GetMatchingFamilyNames: content center category not found: " & pathName)
End If
Dim names As New Dictionary(Of String, String)
AddMatchingFamilyNames(names, node, partialName, options, 0)
Return names
End Function
Private Sub AddMatchingFamilyNames(ByVal names As Dictionary(Of String, String), ByVal node As ContentTreeViewNode, ByVal partialName As String, ByVal options As FamilyMatchingOptions, ByVal level As Integer)
Dim categoryName As String = node.FullTreeViewPath
For Each family As ContentFamily In node.Families
If ((level > 0 AndAlso (options And FamilyMatchingOptions.MatchAllInSubCategories) <> 0) OrElse _
MatchesPartialName(family.DisplayName, partialName)) Then
names(family.DisplayName) = categoryName + ":" + family.DisplayName
End If
Next
If ((options And FamilyMatchingOptions.Recurse) = 0) Then Return
For Each childNode As ContentTreeViewNode In node.ChildNodes
If (MatchesPartialName(childNode.DisplayName, partialName)) Then
AddMatchingFamilyNames(names, childNode, partialName, options, level + 1)
End If
Next
'Marshal.FinalReleaseComObject(node)
End Sub
Private Function MatchesPartialName(ByVal name As String, ByVal partialName As String) As Boolean
Return name.IndexOf(partialName, StringComparison.OrdinalIgnoreCase) >= 0
End Function
Private Function FindFamily(ByVal pathName As String) As ContentFamily
ParseLevels(pathName)
Dim fam As ContentFamily = FindFamily(m_app.ContentCenter.TreeViewTopNode, 0)
If (fam Is Nothing) Then
Throw New ArgumentException("Content Center family not found: " & pathName)
End If
Return fam
End Function
Private Sub ParseLevels(ByVal pathName As String)
m_pathName = pathName
If pathName.IndexOf(":"c) > 0 Then
m_pathLevels = pathName.Split(":"c)
Else
Dim stringSeparators As String() = New String() {"->"}
' Support paths such as: "Fasteners->Pins->Cotter Pins"
m_pathLevels = pathName.Split(stringSeparators, StringSplitOptions.None)
End If
End Sub
Private Function FindFamily(ByVal parentNode As ContentTreeViewNode, ByVal level As Integer) As ContentFamily
If level = m_pathLevels.Length - 1 Then
If parentNode.Families.Count = 0 Then
Return Nothing
End If
For Each fam As ContentFamily In parentNode.Families
If fam.DisplayName = m_pathLevels(level) Then
' Debug.Print("--- ContentFamily Directory = " & fam.MemberDirectory)
Return fam
End If
Next
Return Nothing
End If
For Each cNode As ContentTreeViewNode In parentNode.ChildNodes
If cNode.DisplayName = m_pathLevels(level) Then
Return FindFamily(cNode, level + 1)
End If
Next
Return Nothing
End Function
Private Function FindNode(ByVal parentNode As ContentTreeViewNode, ByVal level As Integer) As ContentTreeViewNode
If level = m_pathLevels.Length Then
Return parentNode
End If
For Each cNode As ContentTreeViewNode In parentNode.ChildNodes
' Trace.WriteLine(" --- Content node name: " & cNode.DisplayName)
If cNode.DisplayName = m_pathLevels(level) Then
Return FindNode(cNode, level + 1)
End If
Next
Return Nothing
End Function
Private Function FindColumn(ByVal family As ContentFamily, ByVal columnName As String) As ContentTableColumn
For i As Integer = 0 To 1
For Each col As ContentTableColumn In family.TableColumns
' Search first for InternalName, then for DisplayHeading.
' If the user knows the internal name, this is safe. If he only knows the display name, this should be OK
' but there could be localization problems.
' The InternalName is displayed as a tooltip when you mouse over the column title. So the user can find it.
If ((i = 0 AndAlso String.Equals(col.InternalName, columnName, StringComparison.OrdinalIgnoreCase)) OrElse _
(i = 1 AndAlso String.Equals(col.DisplayHeading, columnName, StringComparison.OrdinalIgnoreCase))) Then
Return col
End If
Next
Next
Return Nothing
End Function
Private Function FindFileByColumn(ByVal family As ContentFamily) As String
' Return the complete filename for this part number or filename
' Ask the Content Center to create the member if required
'Debug.Print(" Family: {0}", family.DisplayName);
Dim keyColumnInternalName As String = String.Empty
If (Not String.IsNullOrEmpty(KeyColumnName)) Then
Dim keyColumn As ContentTableColumn = FindColumn(family, KeyColumnName)
If (keyColumn Is Nothing) Then
Throw New ArgumentException(String.Format("ContentCenterReplace: The column name {0} was not found in the famuly: {1}", KeyColumnName, family.DisplayName))
End If
keyColumnInternalName = keyColumn.InternalName
End If
Dim partNumberColIndex As Integer = -1
Dim fileNameColIndex As Integer = -1
Dim keyColumnIndex As Integer = -1
Dim i As Integer = 1
For Each col As ContentTableColumn In family.TableColumns
'' Debug.Print("{0}, Column: {1} ({2})", tabString, col.DisplayHeading, col.InternalName);
If col.InternalName = "PARTNUMBER" Then
partNumberColIndex = i
ElseIf col.InternalName = "FILENAME" Then
fileNameColIndex = i
ElseIf col.InternalName = keyColumnInternalName Then
keyColumnIndex = i
End If
i += 1
Next
If partNumberColIndex < 0 OrElse fileNameColIndex < 0 Then
Return String.Empty
End If
For Each row As ContentTableRow In family.TableRows
Dim found As Boolean = False
For Each cell As ContentTableCell In row
If (cell.Column = keyColumnIndex OrElse _
keyColumnIndex = -1 And (cell.Column = partNumberColIndex OrElse cell.Column = fileNameColIndex)) Then
If FilenamesAreEqual(cell.Value, m_partNumber) Then
found = True
Exit For
End If
End If
Next
If Not found Then
Continue For ' Next Row
End If
Dim fileNameCell As ContentTableCell = row.Item(fileNameColIndex)
Return GetOrCreateFilename(family, row, fileNameCell.Value)
Next
Return String.Empty
End Function
'''
''' Find the file on disk, or create it from the Content Center.
'''
'''
'''
'''
''' filename with full path.
Private Function GetOrCreateFilename(ByVal family As ContentFamily, ByVal row As ContentTableRow, ByVal fileNameCellValue As String) As String
Dim asCustom As Boolean = m_customInfo IsNot Nothing
If (m_creationMode = FileCreationMode.AsCustom) Then
asCustom = True
ElseIf (m_creationMode = FileCreationMode.AsStandard) Then
asCustom = False
End If
Dim fileName As String = String.Empty
Dim fileNameInput As String = String.Empty
Dim fileNameLastLevel As String = String.Empty
Dim customInput As NameValueMap = Nothing
If (m_customInfo Is Nothing) Then
fileNameLastLevel = MakeValidFileName(fileNameCellValue + ".ipt")
Else
fileNameLastLevel = MakeValidFileName(m_customInfo.CustomFileName + ".ipt")
End If
If (Not asCustom) Then
Dim standardPath As String = IO.Path.Combine(m_ccDir, family.MemberDirectory)
fileName = IO.Path.Combine(standardPath, fileNameLastLevel)
Else
fileName = IO.Path.Combine(CustomPartDirectory, fileNameLastLevel)
End If
If (m_customInfo IsNot Nothing) Then
customInput = m_customInfo.CustomNamesAndValues
fileNameInput = fileName
End If
If Not System.IO.File.Exists(fileName) Then
Dim failureReason As Inventor.MemberManagerErrorsEnum
Dim failureMessage As String = String.Empty
Dim refresh As Inventor.ContentMemberRefreshEnum = ContentMemberRefreshEnum.kUseDefaultRefreshSetting
Trace.WriteLine("-- ContentCenterFinder: Creating Part: " + fileName)
' string CreateMember(object Row, out Inventor.MemberManagerErrorsEnum FailureReason,
' out string FailureMessage, Inventor.ContentMemberRefreshEnum Refresh,
' bool Custom, string FileName, object CustomInput, object Options)
fileName = family.CreateMember(row, failureReason, failureMessage, refresh, asCustom, fileNameInput, _
customInput, Nothing)
End If
Return fileName
End Function
Public Shared Function MakeValidFileName(ByVal fileName As String) As String
Dim invalidChars As Char() = New Char() {"/"c, "\"c, ":"c, "<"c, ">"c, """"c}
If (fileName.IndexOfAny(invalidChars) < 0) Then
Return fileName
End If
For Each ch As Char In invalidChars
fileName = fileName.Replace(ch, "_"c)
Next
Return fileName
End Function
Public Shared Function FilenamesAreEqual(ByVal nameA As String, ByVal nameB As String) As Boolean
If (String.Equals(nameA, nameB, StringComparison.OrdinalIgnoreCase)) Then Return True
If (String.Equals(MakeValidFileName(nameA), MakeValidFileName(nameB), StringComparison.OrdinalIgnoreCase)) Then Return True
Return False
End Function
Private Sub CompensateForComDefect()
' The Content Center API will remember a previous child node, and it will not enumerate other siblings of the same parent.
' They are counting on the caller to release the reference.
System.GC.Collect()
End Sub
End Class
Class CustomInformation
Public CustomNamesAndValues As NameValueMap
Public CustomFileName As String
End Class
Public Enum FamilyMatchingOptions
None = 0
Recurse = 1
MatchAllInSubCategories = 2
End Enum