The WinForms Notepad Project: More Fit and Finish, Plus a Context Menu (Premium)

This time, we’re going to clean up some code, fix some bugs, and see whether a context menu can improve the app.

Let’s dive in.

App startup bugs

This one has been bugging (ahem) me for a while. It’s a little hard to explain, but it’s easy to see if you install Notepad WF on another PC and run it for the first time: Some of the defaults I’d like to see—like the font and status bar (which I’d prefer to be on)—are not correct.

But this is easy to fix. In both cases, I forgot to set a default value for the associated application setting. So all we need to do is open Application Settings, find the correct settings, and add default values. To do so, right-click the project name in Solution Explorer and choose Properties.

MyFont and MyWordWrap have no default values. So let’s configure both. You can choose whatever font you want, of course (I like Consolas, 11 points). And then set MyStatusBar to True, which will cause the status bar to display by default.

Bingo. All is well, and I’m not sure why I didn’t fix that earlier, sorry.

Fixing issues with the text caret position display code

In the Text Cursor (Caret) Position post, I added the same code for changing the display of the text carat position in the status bar to three different event handlers (TextBox1_Click, TextBox1_KeyUp, and TextBox1_TextChanged), and Lerch recommended that I not do that. He’s right, so we’ll fix it.

But first, we need to look at an issue Jordan_Meyer raised: He says that I should handle TextBox1_KeyDown instead of TextBox1_KeyUp to handle those instances in which the user just holds down an arrow key; “the keydown event will fire multiple times at the repeat rate while a key is held down but the key up only fires once a key is released.”

He’s right. If we handle TextBox1_KeyUp and the user holds down an arrow key, the text caret position will only update correctly when they let go of the key. If we handle TextBox1_KeyDown, the text caret position will update in real-time while the key is held down. So let’s fix that.

To do so, open Form1.vb [Design] and double-click on the text box. This will open Form1.vb to the text box’s TextChanged event handler. But it also puts TextBox1 in the middle drop-down above the editor. So use the right-most drop-down to locate the KeyDown event handler. Select that to add an empty TextBox1_KeyDown event handler.

Leave that empty for now and create a new private method called ChangePositionToolStripStatusLabel(). It should look like so:

Private Sub ChangePositionToolStripStatusLabel()
    PositionToolStripStatusLabel.Text =
        "Ln " +
        (TextBox1.GetLineFromCharIndex(TextBox1.SelectionStart) + 1).ToString() +
        ", Col " +
        (TextBox1.SelectionStart - TextBox1.GetFirstCharIndexFromLine(TextBox1.GetLineFromCharIndex(TextBox1.SelectionStart)) + 1).ToString()
End Sub

OK, now we need to call this method from the three event handlers that are currently duplicating the code. They are TextBox1_Click, TextBox1_TextChanged, and TextBox1_KeyDown (which is actually empty at the moment, since we just created it). The bodies of TextBox1_Click and TextBox1_KeyDown should now read as so:

ChangePositionToolStripStatusLabel()

And TextBox1_TextChanged should look like this:

If TextHasChanged = False Then
    Me.Text = "*" + Me.Text
    TextHasChanged = True
End If

ChangePositionToolStripStatusLabel()

Word wrap vs. the text caret position

Also in the Text Cursor (Caret) Position post, lwetzel noticed that if you enable word wrap, the code I have for calculating the text caret position no longer works the same as it does in the real Notepad. He’s right, but I’m going to punt this one for now because fixing it will require a major rewrite (of the code that’s now in that new ChangePositionToolStripStatusLabel() method. But I will look at this one again in the future.

A context menu

I literally had a dream about adding a context menu to the application that would appear when you right-clicked in the text box and provide access to applicable commands. This was a great idea, I thought, because the real Notepad doesn’t provide a context menu.

Except, of course, that it does. (Dreams always warp reality, I guess.) And, oddly enough, so does our application(!). In fact, we get the same context menu as Notepad automatically; it must just be a feature of the TextBox control. Nice.

But I like the idea of context menus, and I may eventually still try to make my own custom context menu for the textbox and replace the stock version. Before even trying that, however, it occurs that there’s another good place for a context menu: The zoom label in the status bar. We could create a simple pop-up menu with just a few choices (like 250 percent, 125 percent, 100 percent, 75 percent, and 50 percent) so that the user could quickly zoom the text display to some preset values using the mouse.

To add a context menu to the project, display Form1.vb, find ContextMenuStrip in the Toolbox, and then double-click it. ContextMenuStrip1 will appear next to our menu strip, status strip, dialogs, and other components in the bottom of the designer. Select it to display its properties in the Properties pane. Then, change its name to ZoomContextMenuStrip.

When you select ZoomContextMenuStrip, you’ll all see a visual designer for the context menu appear over the form.

This works just like the visual designer for regular menus, except that you can’t add top-level menu items horizontally. Instead, you can only add menu items in a single vertical column. Add five items, with their Text properties set to “250%”, “125%”, “100%”, “75%”, and “50%” (no quotes), respectively. Also, set the Name property of each to Percent250, Percent125, and so on.

Typically, you attach a context menu to a control using the control’s ContextMenuStrip property. But ZoomToolStripStatusLabel (which I think of as a status bar field; it’s really just a label) doesn’t offer such a property. That’s OK. I was also intending to handle this label’s Click event to display the context menu that way: It’s far more likely that a user would click this control (rather than right-click it). So, double-click the label to add a blank ZoomToolStripStatusLabel_Click event handler. Then, add the following line of code inside there to display the context menu when this label is clicked:

ZoomContextMenuStrip.Show()

Simple, right? Well, not really. If you run the application and click the zoom label in the status bar, you’ll discover that the context menu does appear. But it appears in the upper-left of the screen. And not over the zoom label where it belongs. Doh.

Really, what we want here is for the menu to appear where the mouse cursor is. And that’s easy: There’s a Cursor.Position property that gives us that position as a Point. And, as it turns out, there’s a version of the Show method used by the context menu that allows us to pass it a Point. So change that one line of code to read:

ZoomContextMenuStrip.Show(Cursor.Position)

Now run it again and click on the zoom label. Much better.

By the way, if you do want to handle right-click as well, you can use the MouseUp event for ZoomToolStripStatusLabel instead of Click. This will fire whenever any mouse click (left, right, whatever) occurs on that control. (If you do handle MouseUp, you can delete ZoomToolStripStatusLabel_Click, as it will be redundant.) For whatever it’s worth, I did make this change.

Now we need to handle each of the menu items in ZoomContextMenuStrip. So, display ZoomContextMenuStrip in the visual designer and then double-click each menu item, in turn, to add blank event handlers for each to the code.

The event handler for 100 percent (which I’ve called Percent100, so the event handler is Percent100_Click) is the simplest one to handle, since we’ve already written this code: We just need to call the event handler for the View > Zoom > Restore Default Zoom menu item. So add the following line of code to Percent100_Click:

RestoreDefaultZoomToolStripMenuItem_Click(sender, e)

For the other four event handlers, all we need to do is reuse some code from our View > Zoom > Zoom Out event handler. So let’s start with Percent250_Click. It should look like so:

ZoomValue = 250
TextBox1.Font = New Font(TextBox1.Font.Name, ((MasterFont.Size * ZoomValue) / 100))
ZoomToolStripStatusLabel.Text = " " + (ZoomValue).ToString + "%"

Percent125_Click, Percent75_Click, and Percent50_Click will be basically identical, except that the ZoomValue needs to match in each (125 in Percent125_Click, and so on).

Of course, we’re reusing a lot of code here again: There are now six (!) event handlers (ZoomInToolStripMenuItem_Click , ZoomOutToolStripMenuItem_Click, Percent250_Click, Percent125_Click, Percent75_Click, and Percent50_Click) that contain the following two lines of code:

TextBox1.Font = New Font(TextBox1.Font.Name, ((MasterFont.Size * ZoomValue) / 100))
ZoomToolStripStatusLabel.Text = " " + (ZoomValue).ToString + "%"

So, yes, we should fix that in a manner similar to what we did above in the section “Fixing issues with the text caret position display.” We can create a new private method that will contain those two lines of code, and then call that method from those six event handlers.

That new method can appear at the bottom of Form1.vb (above End Class). And it looks like so:

Private Sub ChangeZoom()
    TextBox1.Font = New Font(TextBox1.Font.Name, ((MasterFont.Size * ZoomValue) / 100))
    ZoomToolStripStatusLabel.Text = " " + (ZoomValue).ToString + "%"
End Sub

Now, you just need to replace those blocks of code in each of the six event handlers to read as:

ChangeZoom()

Be sure to only change the relevant blocks of code. Each of those event handlers contains other code that needs to stay as-is. As you test it, everything should work as before (and should work properly).

Gain unlimited access to Premium articles.

With technology shaping our everyday lives, how could we not dig deeper?

Thurrott Premium delivers an honest and thorough perspective about the technologies we use and rely on everyday. Discover deeper content as a Premium member.

Tagged with

Share post

Thurrott