Microsoft Cloud Authors: Pat Romanski, Andreas Grabner, Nick Basinger, Kevin Benedict, Liz McMillan

Related Topics: Microsoft Cloud

Microsoft Cloud: Article

C# and the .NET Framework: Tying It All Together

Part 3: Adding File Operations

This is the third in a series of articles that build a text editing application using C# and the .NET Framework. Our model application is the Windows Notepad.

What We've Done and Where We're Going
In the first article (.NETDJ, Vol. 2, issue 1), we built the framework of our application: the NetpadForm class, our text editing interface (a TextBox control), and the basic menu bar options. In the second article (Vol. 2, issue 5), we expanded Netpad by adding clipboard commands, an Insert Time/Date command, and a Word Wrap command.

In this article, we'll add file operations to our application (the File menu's New, Open, Save, and Save As commands). We'll ensure these operations are intuitive and follow a logical flow. Finally, we'll attempt to make our code as generic and reusable as possible (taking advantage of the fact that many of our file operations use similar application logic).

Understanding File Operations: A Flow Chart
Take a moment to think about what goes on when you choose File/Open from a typical application. If your current document has been modified since it was last saved, or if it was never saved, you're prompted to decide if you want to save before continuing. If you choose to save the document, you're then prompted for a file name if it was never saved previously; if it was saved previously, the current filename is used. After the file is saved, you're asked to select a new file to open, in a standard dialog box. Once you select a file, it is opened in the application.

Figure 1 is a flowchart that outlines the many different logical paths that can be taken when choosing one of the four file commands we will implement: New, Open, Save, and Save As. There are two possible starting points in the flowchart: one for File New and File/Open, and another for File/Save and File/Save As. Note that, regardless of the starting point, each of these operations share some of the same functionality. For example, the File/Open operation can involve file saving, as mentioned earlier.

Developing a flowchart for different aspects of an application can be very helpful in understanding what functionality you need to code before coding. Designing before developing is a very good practice.

Outlining the Implementation
After reviewing the flowchart for our file operations, we can outline our implementation:

  • We need to create properties to track the state of the document:
    - _isNew will track whether the document is a new (never before saved) document.
    - _isModified will track whether the document has been modified since last saved, opened, or created.
  • We need to create properties to store file information:
    - _fileName is the name of the file itself (or "Untitled" for a new file). - _filePath is the path to the folder that stores the file (not including the file name).
  • We need to create generic methods for use by our File menu commands:
    - CloseDocument(): A method to close the current document. These steps are shown in blue in the flowchart.
    - ClearDocument(): A method to clear the current document (erase the current file properties and data). These steps are shown in brown in the flowchart.
    - OpenDocument(): A method to open a new document (prompt for the file name to open and read the data). These steps are shown in green in the flowchart.
    - SaveDocument(): A method to save data to a given file name (get the file name to save to - or use the existing file name - and save the data). These steps are shown in red in the flowchart.
Each command in the file menu will use one or more of those methods. Notice how the flowchart makes it easy to see which methods need to be called when:
  • File/New will call CloseDocument().
  • File/Open will call CloseDocument() then OpenDocument().
  • File/Save and File/Save As will call SaveDocument(). A Boolean parameter, saveAs, will determine whether to use existing or new file information.
Setting Up the New Properties and Menu Items
As in previous articles, we need to set up our new menu items and add event handlers to get our application to respond to user actions. Because the techniques for doing this were detailed in the previous article, I'll only highlight special items.

The four new properties will be declared and initialized as if a new, untitled document is created when our application starts.

private bool _isModified = false;
private bool _isNew = true;
private string _fileName = "Untitled";
private string _filePath = "";

Our menu items (_miFileNew, _miFileOpen, etc.) each have an event handler to handle the Clicked event (MenuFileNew_Click, MenuFileOpen_Click, etc.).

Tracking the Document Modified Status
The _isModified property exists to keep track of whether or not the document has been modified since it was last saved or opened. To maintain this property, we add a new event handler to the TextChanged event of our TextBox control. This event occurs whenever the text of the control changes. If the text changes, we know the document has been modified in some way, so we will set _isModified to true whenever this event is called. This may not be the most efficient method (we're calling an event handler every time a change is made to the text), but it's adequate for our needs.

In our constructor method, we add the event handler to our TextBox control:

_textBox.TextChanged += new EventHandler( TextBox_TextChanged );

The code for the actual event handler method follows:

protected void TextBox_TextChanged( System.Object sender, System.EventArgs e )
		// Set modified flag to true
		_isModified = true;

Clearing the Document
Our first method is ClearDocument(), which will be used by the CloseDocument() method. The purpose of this method is to reset our document to a blank, untitled document - to return the application to its state when first executed. We set our four new properties to their default values and clear the text of our TextBox control.

	private void ClearDocument()
		_textBox.Text = "";
		_fileName = "Untitled";
		_filePath = "";
		_isNew = true;
		_isModified = false;


Note that I ended the method with a call to another method, UpdateWindowTitle(). We'll call this method whenever we need to change the title of our window to reflect the name of the file on which we're working. Our application will need to set the window title to "Untitled" (for unsaved documents) or to the file name of the document (for saved documents), followed by a hyphen and the name of our application. A new document, therefore, would have the window title Untitled - Netpad; a document named ReadMe.txt would have the title ReadMe.txt - Netpad. The code of this method follows.

	private void UpdateWindowTitle()
		this.Text = _fileName + " - Netpad";

I could have simply entered that one line of code whenever I needed to update the window title instead of using a method call. However, a method call is preferred because it ensures that every time our application performs a function (in this case, updating the title of the window), it will be done the same way. Furthermore, if we want to change the way our window is titled, we need to change just one part of our code, not many.

Opening the Document
The first thing we need to do in OpenDocument() is prompt the user to identify which file to open. The .NET Framework makes this easy. Part of the System.IO namespace (which we also reference in using a directive at the start of our code) is a class OpenFileDialog. This sealed (uninheritable) class is defined in the MSDN Library as representing "a common dialog box that displays the control that allows the user to open a file." In other words, this class allows you to use the standard "file open" dialog box that you see in most Windows applications.

There are three properties we will set after creating our instance of OpenFileDialog.

	OpenFileDialog dlgOpen = new OpenFileDialog();
	dlgOpen.DefaultExt = "txt";
	dlgOpen.Filter = "Text Documents (*.txt)|*.txt|All Files|*.*";
	dlgOpen.FilterIndex = 1;

Setting DefaultExt sets the default file extension to use - in our case - "txt". Note that you don't provide the dot before the extension.

Filter and FilterIndex work together. In the standard OpenFileDialog, there is a drop-down menu labeled "Files of Type." This drop-down allows you to specify what filtering (if any) is used in displaying files in the dialog box. Microsoft Word, for example, has many entries in this field, for files such as Word documents (DOC), comma-separated value (CSV) files, rich text files (RTF), and others.

For Netpad, we have only one file type, the text (*.txt) file. However, we want to allow users to be able to view (and potentially open) any file, so we want the filter to also allow for all files (*.*). All filters are set through the Filter property. Each filter must include a description, followed by a vertical bar, which is then followed by the filter pattern. Additional filters are also separated by a vertical bar.

The "Files of Type" drop-down in Netpad should include two options: Text Documents (*.txt) and All Files (*.*). Our filter string is "Text Documents (*.txt)|*.txt|All Files|*.*". Note how the vertical bar separates the filter description from the filter pattern.

The FilterIndex property specifies which of the filters (in the Filter property) is the default selection. The default value (and the first filter) is 1, which corresponds to the "Text Documents" filter.

To show the dialog box, we call the ShowDialog() method. This method returns one of two possible values: DialogResult.OK, if the user specified a file and clicked the OK button, or DialogResult.Cancel, if the user clicked the cancel button. If the user specified a file, the full path to the file is set in the FileName property.

For our OpenDocument() method, if the user clicks the cancel button (ShowDialog() == DialogResult.Cancel), we exit the method (having made no changes). If the user clicks the OK button, we need to retrieve the file name to open (which we store in a string variable newfilename).

	if ( dlgOpen.ShowDialog() == DialogResult.OK )
		//user chose a file name and pressed Open (OK)
		//get the new filename
		newfilename = dlgOpen.FileName;
	else //( dlgOpen.ShowDialog() != DialogResult.OK )
		//user pressed Cancel, exit function
		return false;

Reading the document from disk will utilize the StreamReader class. In .NET, a stream is a sequence of bytes. There are many classes that allow you to read and write streams of bytes to various devices, including disks and network (IP) ports. The StreamReader class is specifically designed to read information from text files.

Most people are amazed at how simple it is to do this. We can read data from a given file, place it in the text of our document, and do all of the necessary housekeeping (i.e., close our document) in three lines of code.

	StreamReader sr = new StreamReader( newfilename );
	_textBox.Text = sr.ReadToEnd();

There are methods in the StreamReader class that allow you to read individual lines of text, individual characters, or blocks of text. For our purposes, reading the entire file from beginning to end is sufficient, and ReadToEnd() handles this for us. The method returns the read text in a string, which we put right in the _textBox.Text property.

It's important to close a stream when you're done with it. Until closed, a stream will maintain a connection to the device (disk or network), which consumes resources and could block other programs from utilizing the same device. Streams are closed using the Close() method.

The last thing to do is reset application properties. The document is no longer modified and isn't new. We need to store the file name and path to the file opened, and update the window's title.

	FileInfo fi = new FileInfo( newfilename );
	_fileName = fi.Name;
	_filePath = fi.Directory.FullName;
	_isNew = false;
	_isModified = false;

The FileInfo class allows us to access information about a given file, such as its name, directory, and size. We use this to get the file name (Name) and directory name (Directory.Full Name) of the file opened. More information on this class can be found in the MSDN library.

Saving the Document
There is one method for saving documents, SaveDocument(), that will handle all file saving functions. Note that this is an overloaded method. It has one parameter: the Boolean saveAs. This parameter identifies if the user selected File/Save As (and must be prompted for a file name). Calling SaveDocument() with no parameters implicitly calls SaveDocument(false).

You'll notice that the code to save a document is nearly identical to the code to open a document. The key differences are in the use of different classes: SaveFileDialog instead of OpenFileDialog, and StreamWriter instead of StreamReader.

The first step is to determine if we need to prompt the user for the file name to save: if the document is new, or if the user chose the File/Save As command, we need to prompt the user for a file name. If necessary, we use the SaveFileDialog class. This class works much like the OpenFileDialog class in that it "presents a common dialog box that allows the user to specify options for saving a file" (quoted from the MSDN Library). It has Filter and FilterIndex properties that work just like those for the OpenFileDialog class. The two classes are so similar that the code for each is nearly identical.

Once we have the file name for the file to save (retrieved from the SaveFileDialog class or by concatenating the _filePath and _fileName), we use the StreamWriter class to save our data to disk. StreamWriter is the "write" version of StreamReader and the syntax is almost identical - the only difference is the use of the Write() method to save our text string to disk.

	StreamWriter sw = new StreamWriter( newfilename, false );
	sw.Write( _textBox.Text );

Last, we do the same thing we did at the end of OpenDocument(): we reset our application properties and update the window's title. The code to do this in SaveDocument() is identical to the code used in OpenDocument().

Closing the Document
Closing the document involves prompting the user to save if the document has been modified, calling SaveDocument() if necessary, and resetting the application to its default (new document) status.

The goal of CloseDocument() is to get us to safely erase the current document by calling ClearDocument(). We will implement logic to ensure we prompt the user if there is a chance of data loss, giving him or her the opportunity to save his or her work or acknowledge that he or she will lose data. If the document is successfully closed, this method returns true; otherwise, it returns false.

If the document is not modified, our steps are easy: clear the document and exit the method.

if ( _isModified == false )
	//current document was not modified, clear the current document
		return true;

If the document has been modified, we need to notify the user that there may be data loss, giving him or her the option to save changes, cancel the operation, or allow the data loss. To do this, we'll create a standard dialog box using the MessageBox class.

MessageBox is a static class that is used to generate a variety of standard dialog boxes. There are many different varieties of dialog boxes that can be created using the class's Show() method. The basic dialog box is generated by passing four parameters to the Show() method: a string providing the text to display in the content region, a string providing the text to display in the title bar, a MessageBoxButtons value to specify which buttons to show, and a MessageBoxIcon value to specify which icon to show.

We need to show three buttons in our dialog box (Yes, No, and Cancel), and our dialog box should show an exclamation point as the icon. The enumerator values MessageBoxButtons.YesNoCancel and MessageBoxIcon.Exclamation will provide this. Additional enumerator values can be reviewed in the MSDN Library.

//current document was modified, show the 'save changes' message box
string alertMsg = "The text in the " + _fileName +
" file has changed.\n\nDo you want to save the changes?";
DialogResult dlgResult;
dlgResult = MessageBox.Show( alertMsg, "Netpad",
MessageBoxButtons.YesNoCancel, MessageBoxIcon.Exclamation );

The MessageBox.Show() method returns a value of type DialogResult. This value corresponds to the button clicked to close the dialog box. The possible return values in our case are DialogResult.Yes, DialogResult.No, and DialogResult.Cancel. Based on this, we'll take the appropriate action:

  • Clicking No ("don't save changes") will require us to call ClearDocument().
  • Clicking Yes ("do save changes") will require us to call SaveDocument(). If this method returns true ("save successful"), we call ClearDocument(); otherwise we do nothing.
  • In any case where the close completed successfully, we must exit the method returning the value true. If the close did not complete successfully, we return false.

	//check the results of the message box
	if ( dlgResult == DialogResult.No )
		//user chose 'no', clear the current document
		return true; //document closed successfully
	else if ( dlgResult == DialogResult.Yes )
		//user chose 'yes', save current document
		if ( SaveDocument() == true )
			//save was successful, clear the current document
			return true; //document closed successfully
	//else if user chose 'cancel' do nothing
	return false; //document not closed

File Menu Event Handlers
As described above, the event handlers for our File menu commands call one or more of the methods used earlier to take the appropriate actions on the document (closing, opening, or saving). File/New simply calls CloseDocument().

protected void MenuFileNew_Click( System.Object sender, System.EventArgs e )

File/Open calls CloseDocument(), and then OpenDocument() if the close was

protected void MenuFileOpen_Click( System.Object sender, System.EventArgs e )
		if ( CloseDocument() )

File/Save calls SaveDocument() with no parameters (the method determines if we
must prompt for a file name or not).

protected void MenuFileSave_Click( System.Object sender, System.EventArgs e )

File/Save As calls SaveDocument (true), which forces the method to prompt for a
file name.

protected void MenuFileSaveAs_Click( System.Object sender, System.EventArgs e )
		SaveDocument( true );

Putting It Together
After compiling and running the complete source code for this article, Netpad has duplicated a big chunk of the functionality Windows Notepad. There are a some significant features left to implement: printing, find/ replace, font customization, the status bar, and others. These will be in subsequent articles. Until then, start using Netpad as your replacement to Notepad and enjoy using your creation!

More Stories By Brian DeMarzo

Brian DeMarzo has over 10 of years experience as a developer and systems engineer. He is an IT manager at a law firm in New York, a freelance consultant, the developer/designer of an online baseball game (www.csfbl.com), and developer and contributor to Marzie's Toolbox (www.marzie.com).

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.

IoT & Smart Cities Stories
Moroccanoil®, the global leader in oil-infused beauty, is thrilled to announce the NEW Moroccanoil Color Depositing Masks, a collection of dual-benefit hair masks that deposit pure pigments while providing the treatment benefits of a deep conditioning mask. The collection consists of seven curated shades for commitment-free, beautifully-colored hair that looks and feels healthy.
The textured-hair category is inarguably the hottest in the haircare space today. This has been driven by the proliferation of founder brands started by curly and coily consumers and savvy consumers who increasingly want products specifically for their texture type. This trend is underscored by the latest insights from NaturallyCurly's 2018 TextureTrends report, released today. According to the 2018 TextureTrends Report, more than 80 percent of women with curly and coily hair say they purcha...
The textured-hair category is inarguably the hottest in the haircare space today. This has been driven by the proliferation of founder brands started by curly and coily consumers and savvy consumers who increasingly want products specifically for their texture type. This trend is underscored by the latest insights from NaturallyCurly's 2018 TextureTrends report, released today. According to the 2018 TextureTrends Report, more than 80 percent of women with curly and coily hair say they purcha...
We all love the many benefits of natural plant oils, used as a deap treatment before shampooing, at home or at the beach, but is there an all-in-one solution for everyday intensive nutrition and modern styling?I am passionate about the benefits of natural extracts with tried-and-tested results, which I have used to develop my own brand (lemon for its acid ph, wheat germ for its fortifying action…). I wanted a product which combined caring and styling effects, and which could be used after shampo...
The platform combines the strengths of Singtel's extensive, intelligent network capabilities with Microsoft's cloud expertise to create a unique solution that sets new standards for IoT applications," said Mr Diomedes Kastanis, Head of IoT at Singtel. "Our solution provides speed, transparency and flexibility, paving the way for a more pervasive use of IoT to accelerate enterprises' digitalisation efforts. AI-powered intelligent connectivity over Microsoft Azure will be the fastest connected pat...
There are many examples of disruption in consumer space – Uber disrupting the cab industry, Airbnb disrupting the hospitality industry and so on; but have you wondered who is disrupting support and operations? AISERA helps make businesses and customers successful by offering consumer-like user experience for support and operations. We have built the world’s first AI-driven IT / HR / Cloud / Customer Support and Operations solution.
Codete accelerates their clients growth through technological expertise and experience. Codite team works with organizations to meet the challenges that digitalization presents. Their clients include digital start-ups as well as established enterprises in the IT industry. To stay competitive in a highly innovative IT industry, strong R&D departments and bold spin-off initiatives is a must. Codete Data Science and Software Architects teams help corporate clients to stay up to date with the mod...
At CloudEXPO Silicon Valley, June 24-26, 2019, Digital Transformation (DX) is a major focus with expanded DevOpsSUMMIT and FinTechEXPO programs within the DXWorldEXPO agenda. Successful transformation requires a laser focus on being data-driven and on using all the tools available that enable transformation if they plan to survive over the long term. A total of 88% of Fortune 500 companies from a generation ago are now out of business. Only 12% still survive. Similar percentages are found throug...
Druva is the global leader in Cloud Data Protection and Management, delivering the industry's first data management-as-a-service solution that aggregates data from endpoints, servers and cloud applications and leverages the public cloud to offer a single pane of glass to enable data protection, governance and intelligence-dramatically increasing the availability and visibility of business critical information, while reducing the risk, cost and complexity of managing and protecting it. Druva's...
BMC has unmatched experience in IT management, supporting 92 of the Forbes Global 100, and earning recognition as an ITSM Gartner Magic Quadrant Leader for five years running. Our solutions offer speed, agility, and efficiency to tackle business challenges in the areas of service management, automation, operations, and the mainframe.