Building on the progress we made last time, let’s add open file functionality, textbox events, and theming support to .NETpad.
First up, opening files…
Even with our limited understanding of WPF commands, it should come as no surprise that Open—really, ApplicationCommands.Open—is one of the natively-supported commands. So we’ll implement the File > Open event handler that way (as opposed to using a Click event handler). We’ll also map the CTRL + O keyboard shortcut to call the same Executed command handler if needed.
To get started, let’s add the Command property to the Open menu item in MainWindow.xaml. When you’re done, it should look like so:
<MenuItem Command="Open" Name="OpenMenu" Header="_Open..." />
Next, we need to bind the command to an Executed event handler. We do that in the <Window.CommandBindings> section of the XAML file. In keeping with the loose style I’m using, I’m placing this line of XAML code above the Print command binding in that section (because Open appears above Print in the app’s menu):
<CommandBinding Command="Open" Executed="OpenCommand_Executed" />
Now, we need to create a blank OpenCommand_Executed in MainWindow.xaml.cs. As usual, I am placing this at the bottom of the MainWindow class definition.
private void OpenCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
}
For our first pass at this functionality, we’ll just add some code to display a standard Open File dialog and, if the user selects a text file, open the contents of that file in .NETpad. In the future, we’ll be adding some more logic here to check whether we need to save whatever text/document is potentially already there. That looks like so:
private void OpenCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog
{
Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*"
};
if (openFileDialog.ShowDialog() == true)
{
try
{
TextBox1.Text = File.ReadAllText(openFileDialog.FileName);
AppWindow.Title = openFileDialog.SafeFileName + " - " + Application.Current.MainWindow.GetType().Assembly.GetName().Name;
TextHasChanged = false;
DocumentName = openFileDialog.FileName;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
The OpenFileDialog and File objects will be flagged by Visual Studio, so you’ll need to add includes for Microsoft.Win32 and System.IO, respectively, to clean that up.

This works fine. In fact, even the CTRL + O keyboard shortcut works—and a hint to that effect appears in next to the Open item in the File menu—because Open is a natively supported command. So there’s no more work to do. (Aside from that save/save as check we’ll be adding later.)

Since we can now open text files, making it easier to get a lot of text into the text box, we should add the code needed to update the caret position text and word count in the status bar before moving on to file save and save as.
Let’s handle the caret position text first (“Ln 1, Col 1” by default). As with the Windows Forms version of this app, we’ll need to change that text in response to two events: The text in the text box changing and those instances where the user positions the caret manually by clicking arbitrarily in the text box. So we’ll make a ChangePositionText method first.
Create the following blank method below OpenCommand_Executed and inside the MainWindow class declaration in MainWindow.xaml.cs:
private void ChangePositionText()
{
}
Then, add the following code inside this method:
int Line = TextBox1.GetLineIndexFromCharacterIndex(TextBox1.CaretIndex); int Column = TextBox1.CaretIndex - TextBox1.GetCharacterIndexFromLineIndex(Line); PositionText.Text = "Ln " + (Line + 1).ToString() + ", Col " + (Column + 1).ToString();
For this to do anything, of course, we need to call it. We’ll do so from two event handlers tied to the textbox: TextBox1_SelectionChanged and TextBox1_TextChanged.
Open MainWindow.xaml and select somewhere in the definition of TextBox1. Then, in the Properties pane, switch to the Events view and locate SelectionChanged. Double-click the text box next to that to create an empty TextBox1_SelectionChanged event handler in MainWindow.xaml.cs.
Now, go back to MainWindow.xaml and select somewhere in the definition of TextBox1 again if needed. Again, in the Events view of the Properties pane, located TextChanged and double-click the text box next to that. You should see two empty new event handlers now in MainWindow.xaml.cs:
private void TextBox1_SelectionChanged(object sender, RoutedEventArgs e)
{
}
private void TextBox1_TextChanged(object sender, TextChangedEventArgs e)
{
}
TextBox1_SelectionChanged is easy. We just need to call that ChangePositionText method we created. So add the following one line of code:
ChangePositionText();
TextBox1_TextChanged requires a bit more work. In keeping with how Notepad (and our WinForms versions of .NETpad) work, we need to add an asterisk (“*”) to the beginning of the document name in the app’s title bar if the text has changed and needs to be saved. We need to check for a new word and then update the word count if necessary. And then we need to update the caret position text in the status bar too.
So that all looks like so:
if (TextHasChanged == false)
{
AppWindow.Title = "*" + AppWindow.Title;
TextHasChanged = true;
}
int Count = System.Text.RegularExpressions.Regex.Matches(TextBox1.Text, @"[\S]+").Count;
WordCountText.Text = Count.ToString() + " word";
if (Count == 0 | Count > 1)
WordCountText.Text += "s";
ChangePositionText();
Now, you can test the application again. In addition to File > Open working, you should see that the caret position text and word count in the status bar update correctly too.

Adding support for themes—really, just configuring the foreground (text) and background colors—is easy. First, let’s add the relevant menu items to the XAML via new Theme submenu under View. When you’re done, the code for the View menu should look like so:
<MenuItem Header="_View"> <MenuItem Header="_Theme"> <MenuItem Name="BlackOnWhiteMenu" Header="Black on _White (Default)" /> <MenuItem Name="BlackOnLightGrayMenu" Header="Black on _Light Gray" /> <MenuItem Name="AmberOnBlackMenu" Header="_Amber on Black" /> <MenuItem Name="GreenOnBlackMenu" Header="_Green on Black" /> <Separator /> <MenuItem Name="TextColorMenu" Header="Select _Text Color..." /> <MenuItem Name="BackgroundColorMenu" Header="Select _Background Color..." /> </MenuItem> <MenuItem Header="_Zoom"> <MenuItem Command="NavigationCommands.Zoom" CommandParameter="In" Name="ZoomInMenu" Header="Zoom _In" InputGestureText="Ctrl+Plus" /> <MenuItem Command="NavigationCommands.Zoom" CommandParameter="Out" Name="ZoomOutMenu" Header="Zoom _Out" InputGestureText="Ctrl+Minus" /> <MenuItem Command="NavigationCommands.Zoom" CommandParameter="Restore" Name="RestoreDefaultZoomMenu" Header="_Restore Default Zoom" InputGestureText="Ctrl+0" /> </MenuItem> <MenuItem Name="StatusBarMenu" Header="_Status Bar" IsChecked="True" Click="StatusBarMenu_Click"/> </MenuItem>
Next, we have to add Click event handlers for the four theme items and the text and background color items. As always, you must first select a menu item in the XAML, switch the Properties pane to the Events view, and then double-click the text box next to the Click event there. As you do so, you’ll get a blank Click event handlers in MainWindow.xaml.cs for each menu item.

Now, you just have to write the code. The top four theme items are basically identical, except for the chosen colors:
private void BlackOnWhiteMenu_Click(object sender, RoutedEventArgs e)
{
TextBox1.Foreground = Brushes.Black;
TextBox1.Background = Brushes.White;
}
private void BlackOnLightGrayMenu_Click(object sender, RoutedEventArgs e)
{
TextBox1.Foreground = Brushes.Black;
TextBox1.Background = Brushes.LightGray;
}
private void AmberOnBlackMenu_Click(object sender, RoutedEventArgs e)
{
TextBox1.Foreground = Brushes.Orange;
TextBox1.Background = Brushes.Black;
}
private void GreenOnBlackMenu_Click(object sender, RoutedEventArgs e)
{
TextBox1.Foreground = Brushes.LightGreen;
TextBox1.Background = Brushes.Black;
}
And you can test those and see that they work just fine.

But the textbox and background color configuration items are going to require a bit of work. The problem? This requires us to use the system Color dialog, which is available from Windows Forms but not from WPF. (Or, we could implement our own version of the Color dialog. As you’ll soon see, I implemented several other dialogs—Font, InputBox, and About—myself, but this one seemed too tedious to bother.)
Actually, there’s another problem: While it’s possible to access Windows Forms objects and classes from WPF, Microsoft has disabled this capability if you’re using .NET Core 3.x (as opposed to the .NET Framework). Fortunately, when I complained about this earlier, a reader, davidl, explained the simple fix in the comments to The WPF Files: Fun with Fonts. And it is simple, even though Microsoft, to my knowledge, doesn’t document this anywhere.
Double-click on the project name in the Solution Explorer pane to display a file called NotepadWPF.csproj. (Or whatever name you gave the project, with a .csproj extension.)

See that <PropertyGroup> section at the top? Inside of that, there is a line of XAML that reads:
<UseWPF>true</UseWPF>
Below that, add the following line:
<UseWindowsForms>true</UseWindowsForms>
Then, save and close the file.
Now, in MainWindow.xaml.cs, we can access the Windows Forms library and its ColorDialog class. So locate the TextColorMenu_Checked event handler and add the following code:
System.Windows.Forms.ColorDialog colorDialog = new System.Windows.Forms.ColorDialog(); if (colorDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) TextBox1.Foreground = new SolidColorBrush(Color.FromArgb(colorDialog.Color.A, colorDialog.Color.R, colorDialog.Color.G, colorDialog.Color.B)); colorDialog.Dispose();
The code for the BackgroundColorMenu_Click will be nearly identical, of course:
System.Windows.Forms.ColorDialog colorDialog = new System.Windows.Forms.ColorDialog(); if (colorDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) TextBox1.Background = new SolidColorBrush(Color.FromArgb(colorDialog.Color.A, colorDialog.Color.R, colorDialog.Color.G, colorDialog.Color.B)); colorDialog.Dispose();
That mess you see to the right of the textbox foreground/background color is required to convert between colors in WinForms and WPF. And the Dispose method is there because Visual Studio noted that the colorDialog object was never properly disposed, so we’ll just make sure that happens manually when we’re done with it.
Anyway, it works.

More soon. We need to implement Save/Save As at some point, of course, and then start tackling some custom dialogs, starting with Font.
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.