Microsoft Cloud Authors: Andreas Grabner, Nick Basinger, Kevin Benedict, Pat Romanski, 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
The graph represents a network of 1,329 Twitter users whose recent tweets contained "#DevOps", or who were replied to or mentioned in those tweets, taken from a data set limited to a maximum of 18,000 tweets. The network was obtained from Twitter on Thursday, 10 January 2019 at 23:50 UTC. The tweets in the network were tweeted over the 7-hour, 6-minute period from Thursday, 10 January 2019 at 16:29 UTC to Thursday, 10 January 2019 at 23:36 UTC. Additional tweets that were mentioned in this...
Today's workforce is trading their cubicles and corporate desktops in favor of an any-location, any-device work style. And as digital natives make up more and more of the modern workforce, the appetite for user-friendly, cloud-based services grows. The center of work is shifting to the user and to the cloud. But managing a proliferation of SaaS, web, and mobile apps running on any number of clouds and devices is unwieldy and increases security risks. Steve Wilson, Citrix Vice President of Cloud,...
Artificial intelligence, machine learning, neural networks. We're in the midst of a wave of excitement around AI such as hasn't been seen for a few decades. But those previous periods of inflated expectations led to troughs of disappointment. This time is (mostly) different. Applications of AI such as predictive analytics are already decreasing costs and improving reliability of industrial machinery. Pattern recognition can equal or exceed the ability of human experts in some domains. It's devel...
The term "digital transformation" (DX) is being used by everyone for just about any company initiative that involves technology, the web, ecommerce, software, or even customer experience. While the term has certainly turned into a buzzword with a lot of hype, the transition to a more connected, digital world is real and comes with real challenges. In his opening keynote, Four Essentials To Become DX Hero Status Now, Jonathan Hoppe, Co-Founder and CTO of Total Uptime Technologies, shared that ...
The Japan External Trade Organization (JETRO) is a non-profit organization that provides business support services to companies expanding to Japan. With the support of JETRO's dedicated staff, clients can incorporate their business; receive visa, immigration, and HR support; find dedicated office space; identify local government subsidies; get tailored market studies; and more.
As you know, enterprise IT conversation over the past year have often centered upon the open-source Kubernetes container orchestration system. In fact, Kubernetes has emerged as the key technology -- and even primary platform -- of cloud migrations for a wide variety of organizations. Kubernetes is critical to forward-looking enterprises that continue to push their IT infrastructures toward maximum functionality, scalability, and flexibility. As they do so, IT professionals are also embr...
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...
As the fourth industrial revolution continues to march forward, key questions remain related to the protection of software, cloud, AI, and automation intellectual property. Recent developments in Supreme Court and lower court case law will be reviewed to explain the intricacies of what inventions are eligible for patent protection, how copyright law may be used to protect application programming interfaces (APIs), and the extent to which trademark and trade secret law may have expanded relev...
When Enterprises started adopting Hadoop-based Big Data environments over the last ten years, they were mainly on-premise deployments. Organizations would spin up and manage large Hadoop clusters, where they would funnel exabytes or petabytes of unstructured data.However, over the last few years the economics of maintaining this enormous infrastructure compared with the elastic scalability of viable cloud options has changed this equation. The growth of cloud storage, cloud-managed big data e...
Your applications have evolved, your computing needs are changing, and your servers have become more and more dense. But your data center hasn't changed so you can't get the benefits of cheaper, better, smaller, faster... until now. Colovore is Silicon Valley's premier provider of high-density colocation solutions that are a perfect fit for companies operating modern, high-performance hardware. No other Bay Area colo provider can match our density, operating efficiency, and ease of scalability.