25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more...
Home   Projects   Up   Sitemap   Search   Blog   Forum+Chat   About Us   Privacy   Terms of Use   Feedback   FAQ   Images   Services   Ads   Donate   Humor

File Array: MS Visual C++ 2005 .NET class to build and store a list of disk files

A C++ class that holds a list of disk files as a vector of strings.

It can build the list from wildcards and provides functions for retrieving names from the list in sequential or random order and optionally deleting the file from the array (not the disk) upon retrieval.

It retains the previous version's functionality of being able to parse a Borland  TOpenSaveDialog::TData.FileName, which I am guessing probably has an MFC equivalent that it might work for, as well.

It can output its contents to display properly in a text edit control, with one filename to a line. Thus, you can build the file list, from wildcards if desired, then allow a user to edit the list in an edit control, and read the revised list back into the array.

It is useful for holding a list of files that were command-line arguments, or that result from drag-and-drop operations, or anywhere else you need to manage a list of files. Many of my programs use it.

Not every aspect of this Visual C++ version has been tested. Revisions will be made as I discover any problems.

This file requires my.h.

filearay.h

/*	filearay.h			6-26-2008
	MS Visual C++ Conversion. It appears to build and report the file list correctly, 
	but some of its functionality is still untested. 
	
	Copyright (C)1999-2000, 2002, 2007-08 Steven Whitney.
	Initially published by http://25yearsofprogramming.com.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	Version 3 as published by the Free Software Foundation.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

An array for holding a list of files, with handling functions,
including ability to parse the FileNameList member of a TOpenSaveDialog::TData.

------
NOTES:
--YOU CAN USE THE LIST OF FILES AS A:
  1. Queue from which filenames are deleted as they're retrieved
  2. Fixed list through which you cycle.
  You should NOT use it both ways in the same program.
--REMEMBER TO SET THE DATA MEMBERS AFTER CONSTRUCTION, IF NECESSARY.
  For an ostrstream (for use by a TEdit), setting the stream to ios::binary does
  not have the same effect as using TEditControlOutput=true.  Apparently,
  binary mode only applies to file output.  So TEditControlOutput is still necessary.
  TEditControlOutput could be eliminated if you could determine within operator << whether the
  ostream had been opened in binary mode, but you can't.  I only found 1 ostream-derived
  class (don't recall which) that even recorded its opening mode, and it is protected.
--the return of empty strings when the list is empty is only for protection.  you should always:
  if(FileNameList.GetItemsInContainer())
	filename = FileNameList.GetNext();

*/
#ifndef __FILEARAY_H
#define __FILEARAY_H

#include <direct.h>
#include "my.h"

using namespace std;

//////////////////////////////////////////////////////////////////////////////
// 	A utility class that provides easy parsing of partial or full file names
// 	and disk/directory management.
class FilePathParser
{
public:
	FilePathParser(const char* path = 0);
	FilePathParser(const string& path);

	// functions
	bool Parse(const char* path);	// reuse the parser on a new filename
	bool ChDir();          			// change default directory to the Drive+Dir it now holds

								// useful combinations you can retrieve.  they will reflect
								// any changes you've made to the basic elements
	string FileExt();			// File + Ext
	string DriveDir(); 			// Drive + Dir
	string DriveDirFile(); 		// DriveDir() + File
	string DriveDirFileExt(); 	// DriveDirFile() + Ext
	string Entire();            // DriveDirFileExt()

	// variables
	string Drive;  	// basic elements.  The contents follow the rules of fnsplit().
	string Dir;		// you can change these directly, but be sure to do it right
	string File;
	string Ext;
};
//----------------------------------------------------------------------------
// 							end class FilePathParser
//////////////////////////////////////////////////////////////////////////////
class FileArray 
{
public:
	FileArray(bool editcontroloutput = true);						// constructor
	~FileArray();                                                	// destructor

	friend ostream& operator << (ostream& os, const FileArray&);	// write
	friend istream& operator >> (istream& is, FileArray&);			// read

	// handy for: if(FileArray) or while(FileArray)
	// but if your FileArray is newed, it could cause ambiguity 
	// between if((FileArray*)FileArray != 0) and if((int)FileArray != 0)
	operator int() const { return(FileNameList.size()); }

	// functions
	int AddFile(const string& s);	// add 1 file OR all matching files if s contains wildcards
	int AddDialogList(char *s);		// from TOpenFileDialog.TData.FileName
	void Flush()
		{ FileNameList.clear(); current = -1; }
	uint GetItemsInContainer() const	// for backwards compatibility with old Borland code
		{ return(FileNameList.size()); }
	uint size() 
		{ return(FileNameList.size()); }
	// Other vector functions could be duplicated here to allow treating this class as 
	// though it were derived from vector<string>.
	
	// FOR SEQUENTIAL USE OF THE FILE NAMES:
	string GetNext(bool DeleteEntry = true);

	// FOR CYCLING THROUGH THE FILE NAMES:
	string Next();
	string Prev();
	string Current();
	string Random();				// selects one at random

	// variables
	// In the Borland version, this class was derived from TArrayAsVector<string>.
	// In this version, the vector is a member.
	vector<string> FileNameList;
	
	// if this is true, operator << uses \r\n instead of endl for line ends, 
	// so the lines display properly in a text edit control.
	bool TEditControlOutput;	

protected:
	int current;							// index into the array, for cycling
};
//----------------------------------------------------------------------------
// 						end class FileArray
//////////////////////////////////////////////////////////////////////////////
#endif			// __FILEARAY_H

filearay.cpp

/*	filearay.cpp		6-26-2008
	MS Visual C++ Version.
	Copyright (C)1999-2000, 2002, 2007, 2008 Steven Whitney.
	Initially published by http://25yearsofprogramming.com.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	Version 3 as published by the Free Software Foundation.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/
#include "filearay.h"

using namespace std;
using namespace System;

//////////////////////////////////////////////////////////////////////////////
// class FilePathParser code
//----------------------------------------------------------------------------
// constructor
FilePathParser::FilePathParser(const char* path)
{
string s;
if(path)
	s = path;
else					// if NOTHING provided, get and use currently logged disk & dir
{
	char temp[_MAX_PATH] = {0};
	if(_getcwd(temp, _MAX_PATH) == NULL)// if error,
		temp[0] = 0;					// make it a null string
	s = temp;
	if(s.length()) 		// must add terminal \ to identify it as a path
		s += "\\";
}
Parse(s.c_str());
}                       	//constructor
//----------------------------------------------------------------------------
// constructor
FilePathParser::FilePathParser(const string& path) { Parse(path.c_str()); }
//----------------------------------------------------------------------------
// the various non-inline functions.
string FilePathParser::FileExt()			{ return File + Ext; }
string FilePathParser::DriveDir() 			{ return Drive + Dir; }
string FilePathParser::DriveDirFile() 		{ return DriveDir() + File; }
string FilePathParser::DriveDirFileExt() 	{ return DriveDirFile() + Ext; }
string FilePathParser::Entire()            	{ return DriveDirFileExt(); }
//----------------------------------------------------------------------------
// call this to reuse the parser on a new filename. (it resets everything).
// if path is ONLY a path, it must end with a "\", or the last directory will be parsed
// as a file name.
// 5-17-07 This fn required SIGNIFICANT revision to convert from Borland to VCPP.
bool FilePathParser::Parse(const char* path)
{
Drive.clear(); Dir.clear(); File.clear(); Ext.clear();

if(!path || !strlen(path))
	return false;

char drive[_MAX_DRIVE] = {0}, dir[_MAX_DIR] = {0}, file[_MAX_FNAME] = {0}, ext[_MAX_EXT] = {0};

_splitpath(path, drive, dir, file, ext);

if(strlen(drive))					// drive was specified
	Drive = drive;					// Drive has the :
else								// drive NOT specified: use current default
{
	drive[0] = (uchar)(_getdrive() - 1 + 'A');	//A=1, B=2, etc.
	drive[1] = 0;
	Drive = string(drive) + ":";
}

if(strlen(dir))						// dir was successfully parsed
	Dir = dir;						// dir has leading and trailing backslash
else                                // else get default dir for the drive we've just determined
{
	int newdrive = toupper(Drive[0]) - 'A' + 1;		// (default=0), A=1, B=2, etc.
	// if specified drive was illegal, an empty Dir is your flag of the error.
	if(_getdcwd(newdrive, dir, _MAX_DIR) == NULL)	// NULL is the error return
		Dir = "\\";									// set it to root as default
	else
	{
		Dir = string(dir).substr(2);	// must strip the leading C: that _getdcwd inserted.
		if(Dir.length() && (Dir[Dir.length() - 1] != '\\'))	// if it's root, it ends with a backslash
			Dir += "\\";									// other than root, it doesn't, so add it.
	}
}

if(strlen(file))
	File = file;
else
	File = "";
		
if(strlen(ext))
	Ext = ext;
else
	Ext = "";
		
return true;
}                    			//Parse
//----------------------------------------------------------------------------
// change the current default dir to the one it currently holds, changing disk if necessary.
bool FilePathParser::ChDir()
{
if(Drive.length() && Dir.length())
{
	if(_chdrive(toupper(Drive[0]) - 'A' + 1) == 0)	// VCPP A=1
	{
		string s(Dir);
		int i = s.length() - 1;
		if(s[i] == '\\')			// remove trailing backslash, if any
			s.erase(i);
		if(chdir(s.c_str()) == 0)
			return true;
	}
}
return false;
}                      			//ChDir
//----------------------------------------------------------------------------
// 						end class FilePathParser
//////////////////////////////////////////////////////////////////////////////
// class FileArray code
//----------------------------------------------------------------------------
// constructor
FileArray::FileArray(bool editcontroloutput) 
{
current = -1;								// -1 when array is empty so ++ makes it 0
TEditControlOutput = editcontroloutput;		// see operator <<
}                       	// constructor
//----------------------------------------------------------------------------
// destructor
FileArray::~FileArray()
{
FileNameList.clear();
}                       	// destructor
//----------------------------------------------------------------------------
// output the current list of files, one to a line
// for output to a string, remember to add the << ends after returning.
// A TEdit control is good for editing the list (see wanimate.cpp),
// and requires \r\n instead of endl.
ostream& operator << (ostream& os, const FileArray& a)
{
int N = a.GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
{
	os << a.FileNameList[i];           		// output the string
	if(a.TEditControlOutput)	// choose a terminator
		os << "\r\n";
	else
		os << endl;
}
return(os);
}
//----------------------------------------------------------------------------
// add to file list from a stream
istream& operator >> (istream& is, FileArray& a)
{
string s;
while(is >> s)
	a.AddFile(s);
return(is);
}
//----------------------------------------------------------------------------
// add 1 file name, OR
// if the given name contains wildcards, find FILES that match the pattern, and add them all.
// could test for legality, but complicated, and a bad name will simply fail on open.
// returns number of filenames added.
// 5-17-07 This fn was essentially REWRITTEN to convert from Borland to VCPP.
int FileArray::AddFile(const string& filename)
{
int count = 0;

// builds a fully qualified name from scratch using either provided values or defaults
FilePathParser f(filename);
if(!f.Dir.length())       	// if an explicit or default Disk+Dir couldn't be determined,
	return(0);				// quit to avoid lots of problems

// You don't HAVE to test for wildcards and avoid searching if there aren't any:
// An unambiguous filename would just be found once by findfirst.
// But the original purpose of this function was to create a list of STRINGS.
// findfirst is for dealing with EXISTING FILES.  If user specified a SINGLE file,
// and it doesn't exist, findfirst will fail, and the name won't go into the list.
// That's the effect you'd want, but there's no provision HERE for warning the user that it happened.
// It is better to unconditionally add the STRING now and let the open fail later,
// where user can be informed that the named file doesn't exist.
// But if there ARE wildcards in the user's string, you MUST expand them now to whatever files DO exist,
// which the user expects you to do.
string s = f.Entire();
string path = f.DriveDir();
if((s.find("?") != string::npos) || (s.find("*") != string::npos))// if there are wildcards...
{
	String^ t = gcnew String(f.FileExt().c_str());
	String^ p = gcnew String(path.c_str());
	string u;

	// Being new to .NET, this was difficult to figure out, but it sure is nice.
	array<String^>^ fa = System::IO::Directory::GetFiles(p, t);
	System::Collections::IEnumerator^ e = fa->GetEnumerator();
	while(e->MoveNext() != false)
	{
		SystemStringToBasicString(e->Current->ToString(), u);
		FileNameList.push_back(u);
	}
	delete t;
	delete p;
	delete e;	// don't know if this is required
	delete fa;	// or this
	
	// This method would have been a more direct descendant of the Borland methods,
	// but it crashes with an access violation. _findnext() appears to have one or more bugs.
	// keep for reference because it SHOULD have worked.
	// 9/14/07: Maybe try newing fileblock instead of creating it on the stack.
	//struct _finddata_t fileblock;
	//intptr_t searchhandle = _findfirst(s.c_str(), &fileblock);
	//while(searchhandle != -1L)
	//{
	//	// retrieved name never has path, so must prepend to each file.
	//	// if the caller wants upper or lower case upon use, it can do it itself.
	//	FileNameList.push_back(path + fileblock.name);
	//	count++;
	//	searchhandle = _findnext(searchhandle, &fileblock);
	//}
	//_findclose(searchhandle);
	
}
else
{
	// in this case, you don't know if the file exists or not, but add it to the list anyway.
	FileNameList.push_back(s);   	// just 1 file
	count++;
}
current = -1;			// so that either Current() or Next() will restart at 0
return(count);
}                       	// AddFile
//----------------------------------------------------------------------------
// add files from a TOpenSaveDialog::TData.FileName,
// which contains either 1 legal filename OR multiple entries separated by spaces,
// where the first is a pathname, followed by all the filenames.
// Help|WinAPI says list might contain relative pathnames, but OWL seems to prohibit this.
// remember that multi-selected files are automatically alphabetized.
// If you want a particular order, you must select and add them one at a time.
// returns number of filenames ADDED.
int FileArray::AddDialogList(char* FileName)
{
int filecount = 0;					// number of files selected
string pathname;                   	// path to the files (or a single filename)
string filename;					// for copying file names into
istrstream is(FileName);			// input stream
is >> pathname;						// the first entry is a path OR a full file name
is >> filename;                     // try to read next filename, if any
if(filename.length())               // If there are multiple files,
{
	do                              // prepend pathname to each filename
	{
		filecount += AddFile(pathname + "\\" + filename);	// add them to FileNameList
	}
	while(is >> filename);			// while there are more filenames to copy
}
else                                // else just add the one file,
{
	if(pathname.length())     		// if there was one.
	{
		filecount += AddFile(pathname);
	}
}
current = -1;				// so either Current() or Next() will restart at 0
return(filecount);
}                       	// AddDialogList
//----------------------------------------------------------------------------
// for SEQUENTIAL use of the filenames:
//----------------------------------------------------------------------------
// return name of the next file (at index 0), and optionally (usually) delete it.
//
// this could also be bool GetNext(string& destination, bool delete), that
// fills the string and returns true if there was anything to get; otherwise
// leaves the string unchanged and returns false.  Unsure which is more useful.
//
string FileArray::GetNext(bool DeleteEntry)
{
string s;
if(GetItemsInContainer())
{
	s = FileNameList[0];
	if(DeleteEntry)
		FileNameList.erase(FileNameList.begin());
}
current = -1;			// EITHER Current() or Next() will start at 0
return(s);
}                		// GetNext
//----------------------------------------------------------------------------
// for CYCLING use of the filenames:
//----------------------------------------------------------------------------
// advances to the next filename, then returns it.
string FileArray::Next()
{
string s;
int N = GetItemsInContainer();
if(N)
{
	if(++current >= N)
		current = 0;
	s = FileNameList[current];
}
return(s);
}                   	// Next
//----------------------------------------------------------------------------
// goes back to previous filename, then returns it.
// maybe useful for cycling backwards or back and forth
string FileArray::Prev()
{
string s;
int N = GetItemsInContainer();
if(N)
{
	if(--current < 0)
		current = N - 1;
	s = FileNameList[current];
}
return(s);
}                    	// Prev
//----------------------------------------------------------------------------
// returns name of the filename currently pointed at.
string FileArray::Current()
{
string s;
int N = GetItemsInContainer();
if(N)
{
	current = range(current, 0, N - 1);
	s = FileNameList[current];
}
return(s);
}                    	// Current
//----------------------------------------------------------------------------
// returns a filename randomly selected from the list. (used sometimes for .MAP color files)
string FileArray::Random()
{
string s;
int N = GetItemsInContainer();
if(N)
	s = FileNameList[random(N)];
return(s);
}                    	// Current
//----------------------------------------------------------------------------
// 						end class FileArray
//////////////////////////////////////////////////////////////////////////////

 

 

Valid HTML 4.01 Transitional Valid CSS
View content labeling at ICRA.
Copyright ©2008 Steven Whitney. Last modified 06/26/2008.