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
@CloudEXPO and @ExpoDX, two of the most influential technology events in the world, have hosted hundreds of sponsors and exhibitors since our launch 10 years ago. @CloudEXPO and @ExpoDX New York and Silicon Valley provide a full year of face-to-face marketing opportunities for your company. Each sponsorship and exhibit package comes with pre and post-show marketing programs. By sponsoring and exhibiting in New York and Silicon Valley, you reach a full complement of decision makers and buyers in ...
The Internet of Things is clearly many things: data collection and analytics, wearables, Smart Grids and Smart Cities, the Industrial Internet, and more. Cool platforms like Arduino, Raspberry Pi, Intel's Galileo and Edison, and a diverse world of sensors are making the IoT a great toy box for developers in all these areas. In this Power Panel at @ThingsExpo, moderated by Conference Chair Roger Strukhoff, panelists discussed what things are the most important, which will have the most profound e...
Two weeks ago (November 3-5), I attended the Cloud Expo Silicon Valley as a speaker, where I presented on the security and privacy due diligence requirements for cloud solutions. Cloud security is a topical issue for every CIO, CISO, and technology buyer. Decision-makers are always looking for insights on how to mitigate the security risks of implementing and using cloud solutions. Based on the presentation topics covered at the conference, as well as the general discussions heard between sessio...
The Jevons Paradox suggests that when technological advances increase efficiency of a resource, it results in an overall increase in consumption. Writing on the increased use of coal as a result of technological improvements, 19th-century economist William Stanley Jevons found that these improvements led to the development of new ways to utilize coal. In his session at 19th Cloud Expo, Mark Thiele, Chief Strategy Officer for Apcera, compared the Jevons Paradox to modern-day enterprise IT, examin...
While the focus and objectives of IoT initiatives are many and diverse, they all share a few common attributes, and one of those is the network. Commonly, that network includes the Internet, over which there isn't any real control for performance and availability. Or is there? The current state of the art for Big Data analytics, as applied to network telemetry, offers new opportunities for improving and assuring operational integrity. In his session at @ThingsExpo, Jim Frey, Vice President of S...
Rodrigo Coutinho is part of OutSystems' founders' team and currently the Head of Product Design. He provides a cross-functional role where he supports Product Management in defining the positioning and direction of the Agile Platform, while at the same time promoting model-based development and new techniques to deliver applications in the cloud.
In his keynote at 18th Cloud Expo, Andrew Keys, Co-Founder of ConsenSys Enterprise, provided an overview of the evolution of the Internet and the Database and the future of their combination – the Blockchain. Andrew Keys is Co-Founder of ConsenSys Enterprise. He comes to ConsenSys Enterprise with capital markets, technology and entrepreneurial experience. Previously, he worked for UBS investment bank in equities analysis. Later, he was responsible for the creation and distribution of life settl...
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.
LogRocket helps product teams develop better experiences for users by recording videos of user sessions with logs and network data. It identifies UX problems and reveals the root cause of every bug. LogRocket presents impactful errors on a website, and how to reproduce it. With LogRocket, users can replay problems.
Data Theorem is a leading provider of modern application security. Its core mission is to analyze and secure any modern application anytime, anywhere. The Data Theorem Analyzer Engine continuously scans APIs and mobile applications in search of security flaws and data privacy gaps. Data Theorem products help organizations build safer applications that maximize data security and brand protection. The company has detected more than 300 million application eavesdropping incidents and currently secu...