![]() |
What is better: Application.FileSearch or Dir ??
I'm seeing a couple of alternative ways of getting a list of files from
a folder: 1. The original method I'd seen uses a loop and the Dir method 2. A new way I have seen of tackling this problem is to use Application.FileSearch Which of the above is best and why? Regards Jason. |
What is better: Application.FileSearch or Dir ??
It depends on what you're trying to do. There's nothing right or wrong about either method. It just depends on coding style. Dir will give you the '.' and '..', plus folders, etc etc. and using variants to store the list of items for everything you scan using the Dir...Loop method, you are forced to write a bunch of IF- conditionals parsing out what you want / dont want. The Application.Filesearch method (which I personally prefer) allows you to set up a bunch of filter parameters before conducting the search. You can get fancy enough to allow it to go deep inside sub directories, etc. Again, it's just a matter of 'style'. Chad "WhytheQ" wrote: I'm seeing a couple of alternative ways of getting a list of files from a folder: 1. The original method I'd seen uses a loop and the Dir method 2. A new way I have seen of tackling this problem is to use Application.FileSearch Which of the above is best and why? Regards Jason. |
What is better: Application.FileSearch or Dir ??
It all depends.
If you want simplicity then maybe Application.FileSearch is best. If you want speed and power then maybe a method based on the Windows API is best. A method based on Dir comes somewhere in between. RBS "WhytheQ" wrote in message ups.com... I'm seeing a couple of alternative ways of getting a list of files from a folder: 1. The original method I'd seen uses a loop and the Dir method 2. A new way I have seen of tackling this problem is to use Application.FileSearch Which of the above is best and why? Regards Jason. |
What is better: Application.FileSearch or Dir ??
Under some combination of some versions of windows/excel, FileSearch won't
return all the files it should. I've seen it miss a few files and I've read posts where it's missed lots (all??) files. You may want to look at FileSystemObject, too. Look for posts by Bob Phillips. He's posted lots of samples. WhytheQ wrote: I'm seeing a couple of alternative ways of getting a list of files from a folder: 1. The original method I'd seen uses a loop and the Dir method 2. A new way I have seen of tackling this problem is to use Application.FileSearch Which of the above is best and why? Regards Jason. -- Dave Peterson |
What is better: Application.FileSearch or Dir ??
I've seen enough post showing flakey behavoiour from the .FileSearch to
avoid it. Apart from that, I don't see that you actually get anything great from using it in the first place, that Dir cannot reliably provide. Also, I did read (somewhere ??), that .FileSearch will be dropped in Office 2007: maybe someone with that beta could confirm ? If you need to support Unicode paths/filename (from an English version), then the API route should be used. NickHK "WhytheQ" wrote in message ups.com... I'm seeing a couple of alternative ways of getting a list of files from a folder: 1. The original method I'd seen uses a loop and the Dir method 2. A new way I have seen of tackling this problem is to use Application.FileSearch Which of the above is best and why? Regards Jason. |
What is better: Application.FileSearch or Dir ??
Do you know of any examples using the windows API?
Rgds J On Oct 23, 1:55 pm, "RB Smissaert" wrote: It all depends. If you want simplicity then maybe Application.FileSearch is best. If you want speed and power then maybe a method based on the Windows API is best. A method based on Dir comes somewhere in between. RBS "WhytheQ" wrote in oglegroups.com... I'm seeing a couple of alternative ways of getting a list of files from a folder: 1. The original method I'd seen uses a loop and the Dir method 2. A new way I have seen of tackling this problem is to use Application.FileSearch Which of the above is best and why? Regards Jason.- Hide quoted text -- Show quoted text - |
What is better: Application.FileSearch or Dir ??
Have a go with this code:
Option Explicit Public Declare Function FindWindow _ Lib "user32" Alias "FindWindowA" _ (ByVal lpClassName As String, _ ByVal lpWindowName As String) As Long Private Declare Function lstrlen Lib "kernel32" _ Alias "lstrlenW" (ByVal lpString As Long) As Long Private Declare Function SetCurrentDirectoryA _ Lib "kernel32" (ByVal lpPathName As String) As Long Private Declare Function GetOpenFileName Lib "comdlg32" _ Alias "GetOpenFileNameA" _ (pOpenfilename As OPENFILENAME) As Long Private Declare Function GetSaveFileName Lib "comdlg32" _ Alias "GetSaveFileNameA" _ (pOpenfilename As OPENFILENAME) As Long Private Const OFN_ALLOWMULTISELECT As Long = &H200 Private Const OFN_CREATEPROMPT As Long = &H2000 Private Const OFN_ENABLEHOOK As Long = &H20 Private Const OFN_ENABLETEMPLATE As Long = &H40 Private Const OFN_ENABLETEMPLATEHANDLE As Long = &H80 Private Const OFN_EXPLORER As Long = &H80000 Private Const OFN_EXTENSIONDIFFERENT As Long = &H400 Private Const OFN_FILEMUSTEXIST As Long = &H1000 Private Const OFN_HIDEREADONLY As Long = &H4 Private Const OFN_LONGNAMES As Long = &H200000 Private Const OFN_NOCHANGEDIR As Long = &H8 Private Const OFN_NODEREFERENCELINKS As Long = &H100000 Private Const OFN_NOLONGNAMES As Long = &H40000 Private Const OFN_NONETWORKBUTTON As Long = &H20000 Private Const OFN_NOREADONLYRETURN As Long = &H8000& 'see comments Private Const OFN_NOTESTFILECREATE As Long = &H10000 Private Const OFN_NOVALIDATE As Long = &H100 Private Const OFN_OVERWRITEPROMPT As Long = &H2 Private Const OFN_PATHMUSTEXIST As Long = &H800 Private Const OFN_READONLY As Long = &H1 Private Const OFN_SHAREAWARE As Long = &H4000 Private Const OFN_SHAREFALLTHROUGH As Long = 2 Private Const OFN_SHAREWARN As Long = 0 Private Const OFN_SHARENOWARN As Long = 1 Private Const OFN_SHOWHELP As Long = &H10 Private Const OFS_MAXPATHNAME As Long = 260 Public Const OFS_FILE_OPEN_FLAGS = OFN_EXPLORER _ Or OFN_LONGNAMES _ Or OFN_CREATEPROMPT _ Or OFN_NODEREFERENCELINKS Public Const OFS_FILE_SAVE_FLAGS = OFN_EXPLORER _ Or OFN_LONGNAMES _ Or OFN_OVERWRITEPROMPT _ Or OFN_HIDEREADONLY Private Type OPENFILENAME nStructSize As Long hWndOwner As Long hInstance As Long sFilter As String sCustomFilter As String nMaxCustFilter As Long nFilterIndex As Long sFile As String nMaxFile As Long sFileTitle As String nMaxTitle As Long sInitialDir As String sDialogTitle As String flags As Long nFileOffset As Integer nFileExtension As Integer sDefFileExt As String nCustData As Long fnHook As Long sTemplateName As String End Type Private OFN As OPENFILENAME Function ChDirAPI(strFolder As String) As Long 'will return 1 on success and 0 on failure 'will work with a UNC path as well '----------------------------------------- ChDirAPI = SetCurrentDirectoryA(strFolder) End Function Function PickFileFolder(Optional bGetFile As Boolean = True, _ Optional bOpen As Boolean, _ Optional strStartFolder As String, _ Optional strFileFilters As String, _ Optional lFilterIndex As Long = 1, _ Optional strFileName As String, _ Optional strTitle As String, _ Optional bStayLastFolder As Boolean, _ Optional bMultiSelect As Boolean, _ Optional lHwnd As Long, _ Optional bSaveWarning As Boolean) As String '------------------------------------------------------------ 'adapted from Randy Birch: 'http://vbnet.mvps.org/index.html?code/comdlg/fileopendlg.htm '------------------------------------------------------------ Dim strCurDir As String Dim bChDir As Boolean strCurDir = CurDir If Len(strStartFolder) = 0 Then strStartFolder = strCurDir End If 'create a string of filters for the dialog If Len(strFileFilters) = 0 Then strFileFilters = "Text files (*.txt)" & vbNullChar & "*.txt" & vbNullChar & _ "INI files (*.ini)" & vbNullChar & "*.ini" & vbNullChar & _ "XLS files (*.xls)" & vbNullChar & "*.xls" & vbNullChar & _ "Word files (*.doc)" & vbNullChar & "*.doc" & vbNullChar & _ "Report code files (*.rcf)" & vbNullChar & "*.rcf" & vbNullChar & _ "Access files (*.mdb)" & vbNullChar & "*.mdb" & vbNullChar & _ "HTML files (*.html, *htm)" & vbNullChar & "*.htm*" & vbNullChar & _ "Interbase files (*.gdb)" & vbNullChar & "*gdb" & vbNullChar & _ "All Files (*.*)" & vbNullChar & "*.*" & vbNullChar & vbNullChar End If If lHwnd = 0 Then lHwnd = FindWindow("XLMAIN", Application.Caption) End If With OFN 'size of the OFN structure .nStructSize = Len(OFN) 'window owning the dialog .hWndOwner = lHwnd 'filters (patterns) for the dropdown combo .sFilter = strFileFilters 'index to the initial filter .nFilterIndex = lFilterIndex 'default filename, plus additional padding for the user's final selection(s). 'Must be double-null terminated If bGetFile Then .sFile = strFileName & Space$(1024) & vbNullChar & vbNullChar Else .sFile = "Select a Folder" & Space$(1024) & vbNullChar & vbNullChar End If .nMaxFile = Len(.sFile) 'the size of the buffer 'default extension applied to file if it has no extention .sDefFileExt = "txt" & vbNullChar & vbNullChar 'space for the file title if a single selection made 'double-null terminated, and its size .sFileTitle = vbNullChar & Space$(512) & vbNullChar & vbNullChar .nMaxTitle = Len(OFN.sFileTitle) 'starting folder, double-null terminated .sInitialDir = strStartFolder & vbNullChar & vbNullChar 'the dialog title .sDialogTitle = strTitle 'flags '-------- If bGetFile Then If bMultiSelect Then If bStayLastFolder Then '3701252 .flags = OFN_FILEMUSTEXIST Or OFN_HIDEREADONLY Or _ OFN_PATHMUSTEXIST Or OFN_SHAREAWARE Or _ OFN_ALLOWMULTISELECT Or OFS_FILE_OPEN_FLAGS Else '3701260 .flags = OFN_FILEMUSTEXIST Or OFN_HIDEREADONLY Or _ OFN_PATHMUSTEXIST Or OFN_SHAREAWARE Or _ OFN_ALLOWMULTISELECT Or OFS_FILE_OPEN_FLAGS Or _ OFN_NOCHANGEDIR End If Else If bOpen Then If bStayLastFolder Then '3700740 .flags = OFN_FILEMUSTEXIST Or OFN_HIDEREADONLY Or _ OFN_PATHMUSTEXIST Or OFN_SHAREAWARE Or _ OFS_FILE_OPEN_FLAGS Else '3700748 .flags = OFN_FILEMUSTEXIST Or OFN_HIDEREADONLY Or _ OFN_PATHMUSTEXIST Or OFN_SHAREAWARE Or _ OFS_FILE_OPEN_FLAGS Or OFN_NOCHANGEDIR End If Else If bStayLastFolder Then If bSaveWarning Then '2643982 .flags = OFN_FILEMUSTEXIST Or OFN_HIDEREADONLY Or _ OFN_PATHMUSTEXIST Or OFN_SHAREAWARE Or _ OFN_NOCHANGEDIR Or OFS_FILE_SAVE_FLAGS Else '22540 .flags = OFN_FILEMUSTEXIST Or OFN_HIDEREADONLY Or _ OFN_PATHMUSTEXIST Or OFN_SHAREAWARE Or _ OFN_NOCHANGEDIR End If Else If bSaveWarning Then '2643974 .flags = OFN_FILEMUSTEXIST Or OFN_HIDEREADONLY Or _ OFN_PATHMUSTEXIST Or OFN_SHAREAWARE Or _ OFS_FILE_SAVE_FLAGS Else '22532 .flags = OFN_FILEMUSTEXIST Or OFN_HIDEREADONLY Or _ OFN_PATHMUSTEXIST Or OFN_SHAREAWARE End If End If End If End If Else '16384 .flags = OFN_SHAREAWARE End If End With If bGetFile Then If bOpen Then If GetOpenFileName(OFN) Then If bMultiSelect Then PickFileFolder = BuildCSVMultiString(OFN.sFile) Else PickFileFolder = TrimNull(OFN.sFile) End If bChDir = True Else PickFileFolder = "" End If Else If GetSaveFileName(OFN) Then PickFileFolder = TrimNull(OFN.sFile) bChDir = True Else PickFileFolder = "" End If End If Else If GetSaveFileName(OFN) Then PickFileFolder = TrimNull(CurDir) bChDir = True Else PickFileFolder = "" End If End If If bStayLastFolder = False Then If bChDir Then ChDirAPI TrimNull(strCurDir) End If End If End Function Function BuildCSVMultiString(strString As String) As String 'will take a string of files produced by a multiselect 'where the files are separated by vbNullChar and make into 'a comma-separated string of files 'Will also work if only one file selected '---------------------------------------------------------- Dim strFolder As String Dim i As Long Dim arr arr = Split(strString, Chr(0)) For i = 0 To UBound(arr) If i = 0 Then 'if only only one file selected the folder won't be in 'first element and folder names won't have dots '----------------------------------------------------- If InStr(1, arr(0), ".", vbBinaryCompare) 0 Then BuildCSVMultiString = arr(0) Exit Function Else strFolder = arr(0) End If Else If InStr(1, arr(i), ".", vbBinaryCompare) = 0 Then 'no dot, so not a file anymore '----------------------------- Exit Function End If If i = 1 Then BuildCSVMultiString = strFolder & "\" & arr(1) Else BuildCSVMultiString = BuildCSVMultiString & "," & _ strFolder & "\" & arr(i) End If End If Next End Function Function TrimNull(strString As String) As String TrimNull = Left$(strString, lstrlen(StrPtr(strString))) End Function Sub tester() MsgBox "|" & PickFileFolder(, True, , , 1, , , , True) & "|" End Sub RBS "WhytheQ" wrote in message ups.com... Do you know of any examples using the windows API? Rgds J On Oct 23, 1:55 pm, "RB Smissaert" wrote: It all depends. If you want simplicity then maybe Application.FileSearch is best. If you want speed and power then maybe a method based on the Windows API is best. A method based on Dir comes somewhere in between. RBS "WhytheQ" wrote in oglegroups.com... I'm seeing a couple of alternative ways of getting a list of files from a folder: 1. The original method I'd seen uses a loop and the Dir method 2. A new way I have seen of tackling this problem is to use Application.FileSearch Which of the above is best and why? Regards Jason.- Hide quoted text -- Show quoted text - |
What is better: Application.FileSearch or Dir ??
May this page is more applicable to the .FileSerach aspect :
http://vbnet.mvps.org/code/fileapi/r...es_minimal.htm NickHK "RB Smissaert" wrote in message ... Have a go with this code: Option Explicit Public Declare Function FindWindow _ Lib "user32" Alias "FindWindowA" _ (ByVal lpClassName As String, _ ByVal lpWindowName As String) As Long Private Declare Function lstrlen Lib "kernel32" _ Alias "lstrlenW" (ByVal lpString As Long) As Long Private Declare Function SetCurrentDirectoryA _ -------------------- CUT --------------------------- |
What is better: Application.FileSearch or Dir ??
Yes, you are right there, I posted the wrong code.
Will see if I can find the API searching code, but your link will probably be enough. RBS "NickHK" wrote in message ... May this page is more applicable to the .FileSerach aspect : http://vbnet.mvps.org/code/fileapi/r...es_minimal.htm NickHK "RB Smissaert" wrote in message ... Have a go with this code: Option Explicit Public Declare Function FindWindow _ Lib "user32" Alias "FindWindowA" _ (ByVal lpClassName As String, _ ByVal lpWindowName As String) As Long Private Declare Function lstrlen Lib "kernel32" _ Alias "lstrlenW" (ByVal lpString As Long) As Long Private Declare Function SetCurrentDirectoryA _ -------------------- CUT --------------------------- |
What is better: Application.FileSearch or Dir ??
This has both a method based on Dir and one on the API:
Option Explicit Private Declare Function lstrlen Lib "kernel32" _ Alias "lstrlenW" _ (ByVal lpString As Long) _ As Long Private Declare Function FindFirstFile _ Lib "kernel32" _ Alias "FindFirstFileA" _ (ByVal lpFileName As String, _ lpFindFileData As WIN32_FIND_DATA) _ As Long Private Declare Function FindNextFile _ Lib "kernel32" _ Alias "FindNextFileA" _ (ByVal hFindFile As Long, _ lpFindFileData As WIN32_FIND_DATA) _ As Long Private Declare Function GetFileAttributes _ Lib "kernel32" _ Alias "GetFileAttributesA" _ (ByVal lpFileName As String) _ As Long Private Declare Function FindClose _ Lib "kernel32" (ByVal hFindFile As Long) _ As Long Const MAX_PATH = 260 Const MAXDWORD = &HFFFF Const INVALID_HANDLE_VALUE = -1 Const FILE_ATTRIBUTE_ARCHIVE = &H20 Const FILE_ATTRIBUTE_DIRECTORY = &H10 Const FILE_ATTRIBUTE_HIDDEN = &H2 Const FILE_ATTRIBUTE_NORMAL = &H80 Const FILE_ATTRIBUTE_READONLY = &H1 Const FILE_ATTRIBUTE_SYSTEM = &H4 Const FILE_ATTRIBUTE_TEMPORARY = &H100 Private Type FILETIME dwLowDateTime As Long dwHighDateTime As Long End Type Private Type WIN32_FIND_DATA dwFileAttributes As Long ftCreationTime As FILETIME ftLastAccessTime As FILETIME ftLastWriteTime As FILETIME nFileSizeHigh As Long nFileSizeLow As Long dwReserved0 As Long dwReserved1 As Long cFileName As String * MAX_PATH cAlternate As String * 14 End Type Function TrimNull(strString As String) As String TrimNull = Left$(strString, lstrlen(StrPtr(strString))) End Function Function RecursiveFindFiles(strPath As String, _ strSearch As String, _ Optional bSubFolders As Boolean = True, _ Optional bSheet As Boolean = False, _ Optional lFileCount As Long = 0, _ Optional lDirCount As Long = 0, _ Optional lSkipCount As Long = 0) As Variant 'adapted from the MS example: 'http://support.microsoft.com/default.aspx?scid=kb;en-us;185476 '--------------------------------------------------------------- 'will list all the files in the supplied folder and it's 'subfolders that fit the strSearch criteria 'lFileCount, lDirCount and lSkipCount will always have to start as 0 '------------------------------------------------------------------- Dim strFileName As String 'Walking strFileName variable. Dim strDirName As String 'SubDirectory Name. Dim collDirNames As Collection 'Buffer for directory name entries. Dim nDir As Long 'Number of directories in this strPath. Dim i As Long 'For-loop counter. Dim n As Long Dim arrFiles Static strStartDirName As String Static strpathOld As String On Error GoTo sysFileERR If lFileCount = 0 Then Static collFiles As Collection Set collFiles = New Collection Application.Cursor = xlWait End If If Right$(strPath, 1) < "\" Then strPath = strPath & "\" End If If lFileCount = 0 And lDirCount = 0 Then strStartDirName = strPath End If 'search for subdirectories '------------------------- nDir = 0 Set collDirNames = New Collection strDirName = Dir(strPath, _ vbDirectory Or _ vbHidden Or _ vbArchive Or _ vbReadOnly Or _ vbSystem) 'Even if hidden, and so on. Do While Len(strDirName) 0 'ignore the current and encompassing directories '----------------------------------------------- If (strDirName < ".") And (strDirName < "..") Then 'check for directory with bitwise comparison '------------------------------------------- If GetAttr(strPath & strDirName) And vbDirectory Then collDirNames.Add strDirName lDirCount = lDirCount + 1 nDir = nDir + 1 DoEvents End If 'directories. sysFileERRCont1: End If strDirName = Dir() 'Get next subdirectory DoEvents Loop 'Search through this directory '----------------------------- strFileName = Dir(strPath & strSearch, _ vbNormal Or _ vbHidden Or _ vbSystem Or _ vbReadOnly Or _ vbArchive) While Len(strFileName) < 0 'dump file in sheet '------------------ 'If bSheet Then 'If lFileCount < 65536 Then 'Cells(lFileCount + 1, 1) = strPath & strFileName 'End If 'End If lFileCount = lFileCount + 1 collFiles.Add strPath & strFileName 'If strPath < strpathOld Then 'Application.StatusBar = " " & lFileCount & _ " " & strSearch & " files found. " & _ "Now searching " & strPath 'End If 'strpathOld = strPath strFileName = Dir() 'Get next file DoEvents Wend If bSubFolders Then 'If there are sub-directories.. '------------------------------ If nDir 0 Then 'Recursively walk into them '-------------------------- For i = 1 To nDir RecursiveFindFiles strPath & collDirNames(i) & "\", _ strSearch, _ bSubFolders, _ bSheet, _ lFileCount, _ lDirCount, _ lSkipCount DoEvents Next End If 'If nDir 0 'only bare main folder left, so get out '-------------------------------------- If strPath = strStartDirName Then ReDim arrFiles(1 To lFileCount) As String For n = 1 To lFileCount arrFiles(n) = collFiles(n) Next RecursiveFindFiles = arrFiles Application.Cursor = xlDefault Application.StatusBar = False End If Else 'If bSubFolders ReDim arrFiles(1 To lFileCount) As String For n = 1 To lFileCount arrFiles(n) = collFiles(n) Next RecursiveFindFiles = arrFiles Application.Cursor = xlDefault Application.StatusBar = False End If 'If bSubFolders Exit Function sysFileERR: lSkipCount = lSkipCount + 1 Resume sysFileERRCont1 End Function Sub FindFilesAPI(strPath As String, _ strSearch As String, _ bSubDirs As Boolean, _ lFileCount As Long, _ lDirCount As Long, _ collFiles As Collection) Dim i As Long Dim strFileName As String 'Walking strFileName variable... Dim strDirName As String 'SubDirectory Name 'Buffer for directory name entries Dim collDirNames As Collection Dim lDir As Long 'Number of directories in this path Dim hSearch As Long 'Search Handle Dim WFD As WIN32_FIND_DATA Dim iCont As Integer If lFileCount = 0 Then If Right$(strPath, 1) < "\" Then strPath = strPath & "\" End If End If 'Search for subdirectories lDir = 0 Set collDirNames = New Collection iCont = True hSearch = FindFirstFile(strPath & "*", WFD) If hSearch < INVALID_HANDLE_VALUE Then Do While iCont strDirName = TrimNull(WFD.cFileName) 'Ignore the current and encompassing directories If (strDirName < ".") And (strDirName < "..") Then 'Check for directory with bitwise comparison If GetFileAttributes(strPath & strDirName) And _ FILE_ATTRIBUTE_DIRECTORY Then collDirNames.Add strDirName lDir = lDir + 1 lDirCount = lDirCount + 1 End If End If 'Get next subdirectory iCont = FindNextFile(hSearch, WFD) Loop iCont = FindClose(hSearch) End If 'Walk through this directory hSearch = FindFirstFile(strPath & strSearch, WFD) iCont = True If hSearch < INVALID_HANDLE_VALUE Then While iCont strFileName = TrimNull(WFD.cFileName) If (strFileName < ".") And (strFileName < "..") And _ Len(strFileName) 0 Then '--------------------------------------------- 'maybe a dictionary or a string will be faster 'not worth it though as this only a tiny part 'of the total time '--------------------------------------------- collFiles.Add strPath & strFileName lFileCount = lFileCount + 1 End If iCont = FindNextFile(hSearch, WFD) 'Get next file Wend iCont = FindClose(hSearch) End If If bSubDirs = False Then Exit Sub End If 'If there are sub-directories... If lDir 0 Then 'Recursively walk into them... For i = 1 To lDir FindFilesAPI strPath & _ collDirNames(i) & _ "\", _ strSearch, _ bSubDirs, _ lFileCount, _ lDirCount, _ collFiles Next i End If End Sub RBS "NickHK" wrote in message ... May this page is more applicable to the .FileSerach aspect : http://vbnet.mvps.org/code/fileapi/r...es_minimal.htm NickHK "RB Smissaert" wrote in message ... Have a go with this code: Option Explicit Public Declare Function FindWindow _ Lib "user32" Alias "FindWindowA" _ (ByVal lpClassName As String, _ ByVal lpWindowName As String) As Long Private Declare Function lstrlen Lib "kernel32" _ Alias "lstrlenW" (ByVal lpString As Long) As Long Private Declare Function SetCurrentDirectoryA _ -------------------- CUT --------------------------- |
All times are GMT +1. The time now is 02:11 AM. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
ExcelBanter.com