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 2: Adding menu bars, event handlers, and application logic

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

What We've Done
In the first article (.NETDJ, Vol. 2, issue 1), we built the framework of our application as follows:

  • We built the NetpadForm class - a Windows Form class that will ultimately encompass most of the functionality of Netpad.
  • We added a TextBox object to the form to provide our text editing interface.
  • We added a menu bar, the File menu, and the File/Exit menu item to the form, and added an event handler to the File/Exit menu item.
Where We're Going
In this article, we're going expand Netpad by adding clipboard commands (Undo, Cut, Copy, Paste, Delete, and Select All), an Insert Time/Date command, and a Word Wrap command. These commands will be added to two new top-level menus, Edit and Format.

Creating the Edit and Format Menus
Most of our commands - all the clipboard commands and the Insert Time/Date command - will be included in the Edit menu. The top-level Edit menu and each menu item below it will be instances of the MenuItem class (from the System.Windows.Forms namespace). The main menu itself, which we created in the first article (and stored in the property _mainMenu), is an instance of the MainMenu class (also from the System.Windows.Forms namespace).

To create our Edit menu, we're going to do things a little different than before. In the first article, we created a MenuItem object for the File menu, and then added it to the menu bar:

_miFile = new MenuItem( "&File" );
_mainMenu.MenuItems.Add( _miFile );

We are going to shorten these two commands into one by taking advantage of a .NET feature called method overloading. Method overloading allows a method to do different things depending on what parameters are passed to it.

Look at the code below, which does the same thing as the code above:

_miFile = _mainMenu.MenuItems.Add( "&File" );

The key difference is in the Add method:

  • In the first code block, we created the MenuItem, _miFile, and then added it to our main menu, _mainMenu, using the Add() method.
  • In the second code block, we do not create the MenuItem object directly. Rather, by passing a string - "&File" - to the Add() method, the method creates the new MenuItem object for us and adds it to the menu.
The second code block allows us to save a line of code by allowing the Add() method to do some extra work for us. Convenient? Yes. Necessary? No. Neither way is right or wrong. Some feel that using method overloading makes code more readable or less readable. Use the technique you are most comfortable with.

Here's how we create our Edit menu:

_miEdit = _mainMenu.MenuItems.Add( "&Edit" );

For the commands under the Edit menu, we will employ two more techniques. First, we need to add a shortcut key to each menu item, ensuring we can undo by pressing Ctrl-Z, cut by pressing Ctrl-X, and so forth. To set a shortcut key, we will set a value to the Shortcut property of the menu item. See how we create the Undo menu item below:

_miEditUndo = _miEdit.MenuItems.Add( "&Undo" );
_miEditUndo.Shortcut = Shortcut.CtrlZ;

Next, below the Undo option, we want a horizontal line as a spacer. This is actually another MenuItem object, created by naming your item with a single hyphen, as shown below:

_miEdit.MenuItems.Add( "-" );

The rest of the code to create our Edit menu follows. Note the use of the function key shortcut for the Time/Date menu item.

_miEditCut = _miEdit.MenuItems.Add( "Cu&t" );
_miEditCut.Shortcut = Shortcut.CtrlX;
_miEditCopy = _miEdit.MenuItems.Add( "&Copy" );
_miEditCopy.Shortcut = Shortcut.CtrlC;
_miEditPaste = _miEdit.MenuItems.Add( "&Paste" );
_miEditPaste.Shortcut = Shortcut.CtrlV;
_miEditDelete = _miEdit.MenuItems.Add( "&Delete" );
_miEditDelete.Shortcut = Shortcut.Del;
_miEdit.MenuItems.Add( "-" );
_miEditSelectAll = _miEdit.MenuItems.Add( "Select &All" );
_miEditSelectAll.Shortcut = Shortcut.CtrlA;
_miEditTimeDate = _miEdit.MenuItems.Add( "Time/&Date" );
_miEditTimeDate.Shortcut = Shortcut.F5;

The Format menu is created the same way. See the final Netpad source code for this article to see the details.

Menu Events and Event Handlers
Now that we created our menu items, we need to make them do something. To do this, we will use event handlers.

Most objects in .NET can respond to events. Some events are driven by the user interface, such as mouse clicks and keystrokes. Others are driven by application logic, such as objects being created and destroyed.

An event handler is a method that is called upon an event occurring. When the event occurs, the event handler method is called. The event handler method is passed two parameters that define the sender (the object which created the event) and the event arguments (information that describes what's happening in the event). The process of connecting an event handler method to a particular object's event is often called wiring.

The best way to learn this is by example. I'll show you how to write the event handler method for the clipboard menu items in the Edit menu: Undo, Cut, Copy, Paste, and Delete.

Writing the Clipboard Event Handler Methods
Look at the method below, which is the event handler for the Undo menu item. Note that we named it MenuEditUndo_Click because the name is clear and descriptive: it's a method to respond to the Click event for the Undo menu item in the Edit Menu.

protected void MenuEditUndo_Click( System.Object sender, System.EventArgs e )
	// Call the textbox's Undo method

All event handler methods accept two parameters. The first parameter (sender) is a reference to the object that sent the event. In this example, it is the MenuItem object for the Undo menu item, _miEditUndo. The second parameter is a reference to an event arguments object. This object can contain specific data about the event, and can vary greatly depending on the sender. Menu item clicks generate event data that can be much different than other events. For specific details, you will need to review the .NET Framework documentation. But for now, you don't need to be concerned with this.

The method itself does one thing: it calls the Undo() method of the _textBox object. The _textBox object is an instance of the System.Windows.Forms. TextBox class, and it provides the functionality for the "notepad" part of Netpad - the area where we type and edit our text. Part of the functionality of all TextBox objects is clipboard methods: Undo(), Cut(), Copy(), and Paste(). We'll use each of these for our Undo, Cut, Copy, and Paste menu items.

What about the Delete menu item? Consider what this menu item does: it deletes the currently selected (highlighted) text. Implementing this is easy, as the TextBox class provides a SelectedText property. To clear the selected text, just set this property to an empty string, as shown below:

__textBox.SelectedText = "";

Connecting the Clipboard Event Handler Methods
Now that we have our event handler method, we can connect it to the _miEditUndo object. Here's the code for doing this:

_miEditUndo.Click += new EventHandler( MenuEditUndo_Click );

To see how this works, read it as follows:

Add (+=) a new EventHandler object, MenuEditUndo_Click,
to the Click property of the _miEditUndo object.

That's all there is to it. The rest of the clipboard event handler methods are connected as follows:

_miEditCut.Click += new EventHandler( MenuEditCut_Click );
_miEditCopy.Click += new EventHandler( MenuEditCopy_Click );
_miEditPaste.Click += new EventHandler( MenuEditPaste_Click );
_miEditDelete.Click += new EventHandler( MenuEditDelete_Click );

As you can see, it's the same process each time. The ease of programming with events in .NET and C# is one of its best features. I won't illustrate here the code to connect the remaining event handlers, as it's the same process over and over again.

The Select All Menu Item
The Select All menu item will select all the text in the text box - the equivalent of highlighting all the text with the mouse. To do this, we need to take advantage of three properties of the TextBox class:

  1. SelectionStart identifies the starting point of selected text in the text box, with the first character being zero.
  2. SelectionLength identifies the length of the selection string, in characters.
  3. Text identifies a string that returns the current content of the entire text box.
To select all the text in the text box, we will set the SelectionStart property to zero (the beginning of the text box), and set the SelectionLength to equal the length of Text (that is, the length all the text in the text box). The code below accomplishes this, and is the code that will be placed in the event handler method for the Select All command:

_textBox.SelectionStart = 0;
_textBox.SelectionLength = _textBox.Text.Length;

The Time/Date Menu Item
The Time/Date menu item inserts the current time and date into the text box. If there's a block of text that is selected, the time/date replaces this text. Otherwise, it's inserted at the insert point. All the code that follows will become part of the Insert Time/Date event handler method.

To start, we need to identify the time and date. We do that as follows:

string timeDate;
timeDate = DateTime.Now.ToShortTimeString() + " " +

DateTime is an object that provides us with date and time functions. ToShortTimeString() and ToShortDateString() return the time and date formatted as appropriate for the localization (country) settings on his PC. In other words, in the United States the date February 12 2004 will format as 2/12/2004; in the United Kingdom, it will format as 12/02/2004.

Next, we have to determine where we insert this string - either at the insert point, or by replacing the current selected text. Using the SelectedText property discussed previously, we can tell if there is any text selected: if the length of SelectedText is zero, then we know nothing is selected:

if ( _textBox.SelectedText.Length == 0 )

If there is text selected, inserting the timeDate string is easy. Just set the SelectedText property to timeDate. The end result is that the selected text (SelectedText) is changed to be equal to timeDate:

_textBox.SelectedText = timeDate;

If there is no text selected, we need to do a bit more. We not only need to insert the timeDate string at the current insert point (the current insert point is determined by the SelectionStart property), we also need to move the current insert point so it's directly after the time and date string we insert, as if the user typed the information themselves.

To do this will take three steps:

Step 1: Store the new insert point as the current insert point plus the length of the time/date string.

int newSelectionStart = _textBox.SelectionStart + timeDate.Length;

Step 2: Add the time/date string to our TextBox control. To do this, we use the Insert() method, which inserts a given string (timeDate) at a given point (SelectionStart) in a string.

_textBox.Text = _textBox.Text.Insert( _textBox.SelectionStart, timeDate );

Step 3: Update the insert point of our TextBox control to the insert point stored in Step 1.

_textBox.SelectionStart = newSelectionStart;

The WordWrap Menu Item
Our last item is the Word Wrap command in the Format menu. Fortunately, this is a simple task, since the TextBox object has word wrap functionality built in. To turn word wrapping on or off, we will set the WordWrap property of our TextBox control to true (word wrap on) or false (word wrap off).

In addition, we need to change the appearance of the menu item for the Word Wrap command to show a check mark when the Word Wrap is turned on (and no check mark when it is turned off).

Since the TextBox control's WordWrap property is a Boolean value (true or false), to "toggle" it (effectively turning word wrapping on and off) we negate its current value:

_textBox.WordWrap = !(_textBox.WordWrap);

For the menu item, we set the Checked property (also a Boolean value) to equal the value of the WordWrap property. This ensures that when word wrap is on (true), we have the menu item checked (true).

_miFormatWordWrap.Checked = _textBox.WordWrap;

Enabling and Disabling Menu Items
One of our goals with Netpad is to make the application intuitive. If the functionality provided by a menu item is not available, it should be disabled (greyed out) so the user cannot select it.

In Netpad, the Edit menu's Cut, Copy, and Delete commands should be available only when there is text selected in the text box. (Consider that these commands manipulate the selected text; if no text is selected, they do nothing. Good design dictates that they should be disabled in these situations.)

As well, the Edit menu's Select All command should be smart: if all the text is already selected, this option should be disabled. (You can't select all if all is already selected!)

To handle this, we will dynamically enable and disable each of these menu items when the user clicks on the Edit menu. The .NET Framework's MenuItem class has an event for this, called Popup. Whenever a user clicks on a menu item (such as our top-level Edit menu), before the menu is drawn on the screen, the Popup event occurs.

Our enabling and disabling logic will be written into a method which we'll name MenuEdit_Popup. There are three sections to this:

1.  If there's no text selected in our TextBox object, disable the Cut, Copy, and Delete menu items.

if ( _textBox.SelectionLength == 0 )
			_miEditCut.Enabled = false;
			_miEditCopy.Enabled = false;
			_miEditDelete.Enabled = false;

2.  If there is text selected in our TextBox object, enable the Cut, Copy, and Delete menu items.

else // !( _textBox.SelectionLength == 0 )
			_miEditCut.Enabled = true;
			_miEditCopy.Enabled = true;
			_miEditDelete.Enabled = true;

3.  Set the Select All menu item to be enabled if all text is not already selected. We check this by comparing the length of the selection (SelectionLength) to the length of the entire block of text (Text.Length).

_miEditSelectAll.Enabled = !( _textBox.SelectionLength == _textBox.Text.Length );

Putting It Together: Netpad, Phase 2
After compiling and running the complete source code for this article, you'll see that Netpad is much more usable. It now has clipboard support and some of the most useful functions in our model application, the Windows Notepad. Its menu items are responsive and intuitive.

However, there's still something missing that is very important: file operations. The next article in this series will outline the file opening and saving operations for Netpad.

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.