Home

Character and String Streaming

 

Introduction

As mentioned earlier, the items to save on a document are primarily considered as bits. This makes it possible to save small pieces of information such as those that would fit a byte, a char, a short, or a double value. To support the ability to write one character at a time to a medium, the System::IO namespace provides the TextWriter class. This class is abstract, meaning you can't initialize a variable with it. Instead, you can use one of its derived classes, particularly the StreamWriter class.

Stream Writing

The operation used to save a character to a stream is performed through the StreamWriter class that is based on this. Therefore, to perform this operation, you can declare a pointer to StreamWriter or to its parent the TextWriter class. The StreamWriter class is equipped with various constructors. This allows you to initialize a stream with an option of your choice. For example, to initialize it using a FileStream object, you can use the following constructor:

public: StreamWriter(Stream* stream);

This constructor expects a Stream-type of object as argument. Here is an example of how this constructor can be used:

System::Void button1_Click(System::Object *  sender, System::EventArgs *  e)
{
FileStream *stmRecords = new FileStream(S"Records.rcd", FileMode::CreateNew, FileAccess::Write);
	 StreamWriter *txtMemo = new StreamWriter(stmRecords);
}

If you don't (yet) have a stream you can use, you can directly initialize a file using its name. To do this, you can use the following constructor:

public: StreamWriter(String* path);

When using this constructor, you can pass it the name of the file or its (complete) path.

Eventually, after using a Stream-based class, always remember to release its resources by calling its Close() method. Before calling Close(), call the class' Flush() method to clear the memory areas that the object was using when performing its operations.

After creating a character stream, you can write values to it. To support this, the StreamWriter class is equipped with the Write() method. This method is overloaded with four versions. One of the versions uses the following syntax:

public: void Write(__wchar_t value);

This method expects as argument a character. This Write() method writes one character, then another immediate call to Write() writes the next character on the same line. This can be convenient in many cases. In some other cases, you may want each character to be written on its own line. To write each character on its own line, use the WriteLine() method instead. The WriteLine() method is inherited from the TextWriter class that is the parent of StreamWriter. It can be used as follows:

System::Void btnSave_Click(System::Object *  sender, System::EventArgs *  e)
{
	 FileStream *stmWeekdays = new FileStream(S"Weekdays.txt",
                                        FileMode::Create, FileAccess::Write, FileShare::None);
	 StreamWriter *stmWriter = new StreamWriter(stmWeekdays);

	 stmWriter->WriteLine(L'M');
	 stmWriter->WriteLine(L'T');
	 stmWriter->WriteLine(L'W');
	 stmWriter->WriteLine(L'T');
	 stmWriter->WriteLine(L'F');
	 stmWriter->WriteLine(L'S');
	 stmWriter->WriteLine(L'S');

	 stmWriter->Flush();
	 stmWeekdays->Flush();
	 stmWeekdays->Close();
}

Instead of writing one character at a time, you can write an array of characters to a stream. To do this, you would use the following version of the Write() method:

public: void Write(__wchar_t buffer __gc[]);

This version takes as argument an array of Char values. This version would write a series of characters of the buffer argument on the same line or the same paragraph. If you make another call to this Write() method to add another series of characters, they would be added to the same line. If you want to writer each array in its own paragraph, you can call the TextWriter::WriteLine() method that the StreamWriter class inherits from its parent. Here is an example:

System::Void btnSave_Click(System::Object *  sender, System::EventArgs *  e)
{
	 FileStream *stmQuarter = new FileStream(S"Quarter.txt",
                                       FileMode::Create, FileAccess::Write, FileShare::None);
	 StreamWriter *stmWriter = new StreamWriter(stmQuarter);

	 __wchar_t jan[] = { L'J', L'a', L'n', L'u', L'a', L'r', L'y' };
	 __wchar_t feb[] = { L'F', L'e', L'b', L'r', L'u', L'a', L'r', L'y' };
	 __wchar_t mar[] = { L'M', L'a', L'r', L'c', L'h' };

	 stmWriter->WriteLine(jan);
	 stmWriter->WriteLine(feb);
	 stmWriter->WriteLine(mar);

	 stmWriter->Flush();
	 stmQuarter->Flush();
	 stmQuarter->Close();
}

Another way you can write characters to a stream is to proceed from a string. This operation is performed through the following version of the Write() method:

public: void Write(String* value);

This version expects a String pointer as argument and writes it wholly to the stream. Here is an example from a form equipped with a Multiline TextBox named textBox1 and a Button control named btnSave. When the user clicks the btnSave button, each line, treated as a String value, of the text box is written to a stream:

System::Void btnSave_Click(System::Object *  sender, System::EventArgs *  e)
{
	 FileStream *stmRecords = new FileStream(S"Conserve.txt", 
                                                 FileMode::Create, FileAccess::Write, FileShare::None);
	 StreamWriter *stmWriter = new StreamWriter(stmRecords);

	 for(int i = 0; i < this->textBox1->Lines->Length; i++)
	 {
		 stmWriter->WriteLine(this->textBox1->Lines[i]->ToString());
	 }

	 stmWriter->Flush();
	 stmWriter->Close();
}

The last version of the StreamWriter::Write() method allows you also to write an array of characters to a stream but this time, you don't have to include the whole array, just a set number of characters of the array. In other words, you can select a section of the array and write it to the stream. This method uses the following syntax:

public: void Write(__wchar_t buffer __gc[], int index, int count);

This method expects an array of __wchar_t as the first argument. When writing the characters of this argument to a stream, the second argument specifies the starting character from the array. The last argument specifies the number of characters from the starting index to the end of the array, that would be written to stream.

Stream Reading

As opposed to writing characters, you may want to read them from a stream. To perform this operation, you can use the StreamReader class, which is derived from the TextReader class. To read a character from a stream, the StreamReader class is equipped with the Read() method which is overloaded in two versions. One of the versions uses the following syntax:

public int Read();

This method reads one a character from the stream, checks the next character and returns it as an integer. If the next character has a value higher than -1, then the method reads it and advances to the next. Once, or when, the method finds out that the next character has a value of -1, then it stops. Here is an example that reads characters from a stream, one character at a time:

System::Void btnOpen_Click(System::Object *  sender, System::EventArgs *  e)
{
	 FileStream *fleReader = new FileStream(S"Conserve.txt", FileMode::Open, FileAccess::Read);
	 StreamReader *stmReader = new StreamReader(fleReader);
	 StringBuilder *lstChars = new StringBuilder();

	 int iChar = stmReader->Read();
	 lstChars->Append(static_cast<Char>(iChar));
	 while(iChar > -1)
	 {
		 iChar = stmReader->Read();
		 lstChars->Append(static_cast<Char>(iChar));
	 } 
				 
	 this->textBox1->Text = lstChars->ToString();
	 stmReader->Close();
}

As mentioned earlier, the Write() method of the StreamWriter class writes its values on the same line. This means that, when you read them back using the StreamReader::Read() method, all characters on the same line are read at once. We also mentioned that an alternative was to write each character on its own line using the TextWriter::WriteLine() method. In the same way, to read characters one line at a time, you can call the ReadLine() method. The TextReader::ReadLine() method returns a pointer to String and not a character. This returned value is actually a number that represents the Unicode number of the character, such as 87; but if you access 87 using the StreamReader::Read() method, it would consider that 87 is a string and would therefore read only 8 as the character. This means that, if you had used the StreamWriter::WriteLine() method, you should also use the StreamReader::ReadLine() method to read the number as a string. After reading the line, before using the character, remember to cast it appropriately in order to retrieve the number stored in it. Here is an example that reads the characters written on the Weekdays.txt file we saved earlier:

System::Void btnSave_Click(System::Object *  sender, System::EventArgs *  e)
{
	 FileStream *stmWeekdays = new FileStream(S"Weekdays.txt", 
                                                     FileMode::Create, FileAccess::Write, FileShare::None);
	 StreamWriter *stmWriter = new StreamWriter(stmWeekdays);

	 stmWriter->WriteLine(L'M');
	 stmWriter->WriteLine(L'T');
	 stmWriter->WriteLine(L'W');
	 stmWriter->WriteLine(L'T');
	 stmWriter->WriteLine(L'F');
	 stmWriter->WriteLine(L'S');
	 stmWriter->WriteLine(L'S');

	 stmWriter->Flush();
	 stmWeekdays->Flush();
	 stmWeekdays->Close();
}

System::Void btnOpen_Click(System::Object *  sender, System::EventArgs *  e)
{
FileStream *stmWeekdays = new FileStream(S"Weekdays.txt", FileMode::Open, FileAccess::Read);
	 StreamReader *stmReader = new StreamReader(stmWeekdays);

	 String *strDay = stmReader->ReadLine();
	 int iDay = strDay->ToInt32(0);
	 this->textBox1->Text = static_cast<__wchar_t>(iDay).ToString();
			 
	 strDay = stmReader->ReadLine();
	 iDay = strDay->ToInt32(0);
	 this->textBox2->Text = static_cast<__wchar_t>(iDay).ToString();

	 strDay = stmReader->ReadLine();
	 iDay = strDay->ToInt32(0);
	 this->textBox3->Text = static_cast<__wchar_t>(iDay).ToString();

	 strDay = stmReader->ReadLine();
	 iDay = strDay->ToInt32(0);
	 this->textBox4->Text = static_cast<__wchar_t>(iDay).ToString();

	 strDay = stmReader->ReadLine();
	 iDay = strDay->ToInt32(0);
	 this->textBox5->Text = static_cast<__wchar_t>(iDay).ToString();

	 strDay = stmReader->ReadLine();
	 iDay = strDay->ToInt32(0);
	 this->textBox6->Text = static_cast<__wchar_t>(iDay).ToString();

	 strDay = stmReader->ReadLine();
	 iDay = strDay->ToInt32(0);
	 this->textBox7->Text = static_cast<__wchar_t>(iDay).ToString();

	 stmWeekdays->Flush();
	 stmWeekdays->Close();
}

In the same way, if you had written characters to a stream using arrays of characters and you want to read each line or paragraph as an entity, you can call the TextReader::ReadLine() method. This time, you may not need to cast the returned value(s). Here is an example:

System::Void btnSave_Click(System::Object *  sender, System::EventArgs *  e)
{
	 FileStream *stmQuarter = new FileStream(S"Quarter.txt",  
                                                      FileMode::Create, FileAccess::Write, FileShare::None);
	 StreamWriter *stmWriter = new StreamWriter(stmQuarter);

	 __wchar_t jan[] = { L'J', L'a', L'n', L'u', L'a', L'r', L'y' };
	 __wchar_t feb[] = { L'F', L'e', L'b', L'r', L'u', L'a', L'r', L'y' };
	 __wchar_t mar[] = { L'M', L'a', L'r', L'c', L'h' };

	 stmWriter->WriteLine(jan);
	 stmWriter->WriteLine(feb);
	 stmWriter->WriteLine(mar);

	 stmWriter->Flush();
	 stmQuarter->Flush();
	 stmQuarter->Close();
}

System::Void btnOpen_Click(System::Object *  sender, System::EventArgs *  e)
{
	 FileStream *stmQuarter = new FileStream(S"Quarter.txt", FileMode::Open, FileAccess::Read);
	 StreamReader *stmReader = new StreamReader(stmQuarter);

	 String *strMonth = stmReader->ReadLine();
	 this->textBox1->Text = strMonth;
			 
	 strMonth = stmReader->ReadLine();
	 this->textBox2->Text = strMonth;

	 strMonth = stmReader->ReadLine();
	 this->textBox3->Text = strMonth;

	 stmQuarter->Flush();
	 stmQuarter->Close();
}

After retrieving the characters from the array, instead of reading the whole line or paragraph, you may just want a selected number of characters. To read such a section of the line or paragraph, you can call the second version of the StreamReader::Read() method whose syntax is:

public: int Read([In, Out] __wchar_t buffer __gc[], int index, int count);

The first argument of this method is the variable that will return an array of characters. The second argument is the index of the first character that will be considered from the array. The third argument is the number of characters that will be considered.

String Streaming

 

Introduction

The StreamWriter and the StreamReader classes are suited for scenarios where you want to consider characters or groups of characters in file processing. If you are formally dealing with arrays, the System::IO namespace provides an alternate set of classes to save or open strings to or from a stream.

String Writing

String writing consists of saving text to a stream. Although you can perform this type of operation using the StreamWriter class, the System::IO namespace alternatively provides the StringWriter class.

The StringWriter class is equipped with four constructors. The default constructor allows you to declare an instance of a stream without initializing it.

Exception Handling in File Processing

 

__finally

In previous lessons, to handle exceptions, we were using the try, catch, and throw keywords. These allowed us to perform normal assignments in a try section and then handle an exception, if any, in a catch block. We also mentioned that, when you create a stream, the operating system must allocate resources and dedicate them to the file processing operations. Additional resources may be provided for the object that is in charge of writing to, or reading from, the stream. We also saw that, when the streaming operation was over, we should free the resources and give them back to the operating system. To do this, we called the Close() method of the variable that was using resources.

During file processing, there are many things that can go wrong. For this reason, the creation and/or management of streams should be performed in a try block to get ready to handle exceptions that would occur. When introducing the process of handling exceptions, we saw that the normal flow of the program runs in the try block. We mentioned that the catch section(s) run(s) only if an exception occurs in the try block. This implies that if nothing bad happens in the try block, the content of the catch section will never be processed. Besides actually handling exceptions, the C++ language provides a special section that always runs, whether something bad happens in the try section or not. This section is created using the __finally keyword.

The __finally keyword is used to create a section of an exception. Like catch, a __finally block cannot exist by itself. It can be created following a try section. The formula used would be:

try
{
}
__finally
{
}

Based on this, the __finally section has a body of its own, delimited by its curly brackets. Like catch, the __finally section is created after the try section. Unlike catch, __finally never has parentheses and never takes arguments. Unlike catch, the __finally section is always executed. Because the __finally clause always gets executed, you can include any type of code in it but it is usually appropriate to use this section to free the resources that were allocated previously. Here is an example:

void StartProcessing(Object *o, EventArgs *e)
{
	String *fileName = S"Persons.spr";
	FileStream   *fstPersons = 0;
	BinaryWriter *wrtPersons = 0;

	try {
		fstPersons = new FileStream(fileName, FileMode::Create);
		wrtPersons = new BinaryWriter(fstPersons);

		wrtPersons->Write(S"James Bloch");
		wrtPersons->Write(S"Catherina Wallace");
		wrtPersons->Write(S"Bruce Lamont");
		wrtPersons->Write(S"Douglas Truth");
	}
	__finally
	{
		wrtPersons->Close();
		fstPersons->Close();
	}
}

In the same way, you can use a __finally section to free resources used when reading from a stream. Of course, since the whole block of code starts with a try section, it is used for exception handling. This means that you can add the necessary and appropriate catch section(s) but you don't have to. If you use one or more catch clauses to handle exception, they must be included after the close curly bracket of the try block and before the __finally keyword. In other words, if you use a __finally section, it must be the last in its scope.

.NET Framework Exception Handling for File Processing

One of the most important aspects of file processing is the name of the file that will be dealt with. In some cases you can provide this name to the application or document. In some other cases, you would let the user specify the name of the file and the path. Regardless of how the name of the file would be provided to the operating system, when this name is acted upon, the compiler would be asked to work on the file. If the file doesn't exist, the operation cannot be carried. Furthermore, the compiler would throw an error. There are many other exceptions that can thrown as a result of something going bad during file processing:

FileNotFoundException: The exception thrown when a file has been found is of type FileNotFoundException.

IOException: As mentioned already, during file processing, anything could go wrong. If you don't know what caused an error, you can throw the IOException exception.

 

Detailed Operations on Files

 

File Creation

Besides checking the existence of the file, the File class can be used to create a new file. To support this operation, the File class is equipped with the Create() method that is overloaded with two versions as follows:

public: static FileStream* Create(String *path);
public: static FileStream* Create(String *path, int buffersize);

In both cases, the File::Create() method returns a Stream value, in this case a FileStream value. As the File::Create() method indicates, it takes the name or path of the file as argument. If you know or want to specify the size, in bytes, of the file, you can use the second version.

To provide the same operation of creating a file, you can use the Open() method of the File class. It is overloaded in three versions as follows:

public: static FileStream* Open(String *path, FileMode mode);
public: static FileStream* Open(String *path, FileMode mode, FileAccess access);
public: static FileStream* Open(String *path, FileMode mode, FileAccess access, FileShare share );
 

Directories

 

Introduction

A directory is a section of a medium used to delimit a group of files. Because it is a "physical" area, it can handle operations not available on files. In fact, there are many fundamental differences between both:

  • A file is used to contain data. A directory doesn't contain data
  • A directory can contain one or more files and not vice-versa
  • A directory can contain other directories
  • A file can be moved from one directory to another. This operation is not possible vice-versa since a file cannot contain a directory

The similarities of both types are:

  • A directory or a file can be created. One of the restrictions is that two files cannot have the same name inside of the same directory. Two directories cannot have the same name inside of the same parent directory or drive.
  • A directory or a file can be renamed. If a directory is renamed, the "path" of its file(s) changes
  • A directory or a file can be deleted. If a directory is deleted, its files are deleted also
  • A directory or a file can be moved. If a directory moves, it "carries" all of its files to the new location
  • A directory or a file can be copied. One file can be copied from one directory to another. If a directory if copied to a new location, all of its files are also copied to the new location

Directory Operations

Before using a directory, you must first have it. You can use an existing directory if the operating system or someone else had already created one. You can also create a new directory. Directories are created and managed by various classes but the fundamental class is Directory. Additional operations are performed using the DirectoryInfo class.

Before using or creating a directory, you can first check if it exists. This is because, if a directory already exists in the location where you want to create it, you would be prevented from creating one with the same name. In the same way, if you just decide to directly use a directory that doesn't exist, the operation you want to perform may fail because the directory would not be found.

Before using or creating a directory, to check first whether it exists or not, you can call the Directory::Exists() Boolean method. Its syntax is:

public: static bool Exists(String *path);

This method receives the (complete) path of the directory. If the path is verified, the method returns true. If the directory exists, the method returns false.

To create a directory, you can call the CreateDirectory() method of the Directory class.

 

Previous Copyright © 2004-2005 FunctionX, Inc.