The UWP Notepad Project (Redux): First Steps (Premium)

After far too many struggles, I think I’ve finally gotten the Universal Windows Platform (UWP) version of .NETpad to the point where I can document how I created it. Again.

Note: Yes, you’re reading that correctly. I originally published this series of articles in late April, and then lost the originals to a backup error. So I’m republishing them now, one every day or so, with new screenshots that are based on me reimplementing this app on a different PC and ensuring that all the steps are correct. –Paul

Unfortunately, I also need to lower expectations up-front: Despite some recent improvements to this platform, UWP just wasn’t designed to create desktop applications. So this version of .NETpad performs more slowly and doesn’t provide the full feature-set that we created in the Windows Forms and Windows Presentation Foundation (WPF) versions of the app. That said, it’s still a decent rendition of a Notepad-type text editor but reimagined a bit for the unique capabilities (and limitations) of UWP.

Here’s what’s missing:

  • Line (row)/column display. The UWP version of the textbox doesn’t support the notion of lines, even with Word Wrap off.
  • New window.
  • Printing. This was a nightmare in WinForms, but it was very easy in WPF. It’s a nightmare again in UWP, and I just can’t be bothered.
  • Search with Bing. Seems a bit pointless.
  • Go To. See above about the lack of support for lines.
  • Themes. This is actually doable, but one of the many rabbit holes I went down involved theming the app itself, and it just requires too much code. But as I complete the app again while documenting it, I’ll look into (re)adding this.

Beyond that, the UWP version of .NETpad offers some nice features. For example, the UWP textbox provides an automatic context menu, allowing me to not clutter the main command bar with buttons for Cut, Copy, Paste, and so on, and it has spell-checking built-in. The UI is a bit more modern, with command bars on the top and bottom, though I can’t say I’m a fan. But I do like the custom Settings panel, which slides in from the side, and the custom Save prompt and Find and Replace dialogs quite a bit. In fact, I’m curious about trying to duplicate them in WPF now.

Which raises an interesting point. Now that I’ve tried UWP, I feel like a more modern version of the WPF version of the app would make a lot more sense than the UWP version. And that the current WPF version is, in many ways, still the superior version. I may spend a few weeks with WPF again to see whether evolving that codebase makes any sense before moving on to something else.

But before that, UWP.

New project

Before getting started, use the Visual Studio Installer application to add the “Universal Windows Platform development” workload to Visual Studio if you haven’t already. Then, launch Visual Studio and create a new “Blank App (Universal Windows)” project. Name it as “DotNETpadUWP” (no quotes) and then accept the defaults in the target/minimum version dialog that appears.

Note: If this is the first time you’ve created a UWP app, you will also be prompted to enable Developer mode in Windows 10 Settings.

XAML

Once the project is created, we can build the user interface using XAML, just as we did in WPF. As you may recall, the previous two versions of .NETpad (WinForms and WPF) shared the same basic UI, with a menu bar on the top, a textbox in the middle, and a status bar on the bottom. For the UWP version, we’ll replace the menu bar with a command bar, which is basically a more modern toolbar (and push some little-used options off into a Settings panel, which is hidden by default. There will be a textbox in the middle again, however, and we’ll implement the status bar as a command bar too.

That means a three-row grid will work again. But there will be one addition, which we’ll make room for now since it’s easier: That Settings panel will require us to use a SplitView control, which I believe is what the UWP version of OneNote uses.

To get started, open MainWindow.xaml and double-click the little XAML button in the splitter so that only the XAML code is shown (as opposed to both the Design view and the code). As with WPF, Microsoft provides an empty Grid as a starting point that looks like this in XAML:

<Grid>

</Grid>

But we need to put the SplitView outside of the Grid. So add the beginning and ending SplitView tags so that it now looks like so:

<SplitView Name="SettingsPane" DisplayMode="Overlay" PanePlacement="Right">
<Grid>

</Grid>
</SplitView>

If you look at the Design view or run the app now, you won’t see anything: The SplitView is hidden by default and needs to be enabled in code so that it can appear at runtime (in response to clicking a Settings command bar button). We’ll get to that later.

Next, let’s block out the three rows in the grid. Add the following code inside of the Grid tags:

<Grid.RowDefinitions>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="1*"/>
    <RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<CommandBar Grid.Row="0" Name="MainCommandBar" DefaultLabelPosition="Right">
</CommandBar>
<TextBox Grid.Row="1" Name="TextBox1" Background="White" AcceptsReturn="True" BorderThickness="0,1,0,1" BorderBrush="Black"/>
<StackPanel Grid.Row="2" Name="StatusBar" Visibility="Visible">
    <CommandBar DefaultLabelPosition="Right" OverflowButtonVisibility="Collapsed">
    </CommandBar>
</StackPanel>

The row definitions code should look familiar: It’s almost identical to what we used for the WPF version of the app.

But the CommandBar is new to UWP. It provides a toolbar-like user interface but with support for more than just buttons, plus an overflow area to the right, accessible via a “…” (See more) button, with additional controls arranged in a menu. You can specify that controls must appear in this overflow menu by defining them as secondary commands. But the CommandBar will also place items there as needed if there isn’t enough space to display them all. Oddly, the CommandBar aligns its items to the right, rather than the left. And it supports three label positions: Right, Bottom, and Collapsed (hidden).

The textbox should be mostly familiar. The one big difference is that this control, like all built-in UWP controls, understands the Windows 10 Light and Dark “themes” and will style itself accordingly. I’m setting it to white here, but it will actually display as black if you’re using the Dark theme (or Custom with a Dark app mode) when the text box is not selected. We will fix that later. (But it may be easier to simply change Windows 10 to Light mode for now.)

We implement the status bar as a command bar wrapped inside a stack panel, which works as it does in WPF. The reason we’re using that is that we give the user the option to hide the status bar, and the command bar doesn’t support that. But the stack panel does. That’s why the stack panel is given a name, and the bottom command bar isn’t: We’ll need to access it from C# code later.

If you run the app now, you’ll see the familiar three-row design, with blank commands bars at the top and bottom. But if you right-click or type in the textbox, you’ll see its context menu and spell-checking capabilities are working.

OK, let’s fill out the main command bar. Add the following code between the opening and closing tags of MainCommandBar:

<CommandBar.PrimaryCommands>
    <AppBarButton Icon="Document" Label="New" Name="NewButton" />
    <AppBarButton Icon="OpenFile" Label="Open" Name="OpenButton" />
    <AppBarButton Icon="Save" Label="Save" Name="SaveButton" />
    <AppBarSeparator />
    <AppBarButton Icon="Find" Label="Find" Name="FindButton" />
    <AppBarButton Icon="ClearSelection" Label="Replace" />
    <AppBarSeparator />
</CommandBar.PrimaryCommands>
<CommandBar.SecondaryCommands>
    <AppBarButton Icon="Setting" Label="Settings" Name="SettingsButton" />
</CommandBar.SecondaryCommands>

Here, we’re dividing the available commands into two groups, primary commands, which are always visible unless there’s not enough space, and primary commands, which are only accessible by clicking the “…” (See more) button that will now appear automatically to the far right of the command bar. If you run the app now, you will see all of the items we’ve added (all of which happen to be app bar buttons), and you can click “…” to see the Settings item.

We could implement the bottom command bar (the “status bar”) any number of ways. I ended up just using app bar buttons for each of the items because I actually need three of them—Zoom In, Restore Default Zoom, and Zoom Out—to be selectable buttons, so it’s more easily consistent to do everything there the same way. I add an empty container at the end to space it out from right similarly to the top command bar. (Note, too, that the bottom command bar doesn’t display a “…” item because of the OverflowButtonVisibility=”Collapsed” we added earlier.) Here’s the code:

<CommandBar.PrimaryCommands>
    <AppBarSeparator />
    <AppBarButton Name="AutoSaveButton" Width="150" Content="Auto Save: Off" />
    <AppBarSeparator />
    <AppBarButton Name="WordCountButton" Width="120" Content="0 words" />
    <AppBarSeparator />
    <AppBarButton Name="ZoomOutButton" VerticalAlignment="Center" Content=" - " />
    <AppBarButton Name="ZoomButton" VerticalAlignment="Center" Content="100%" />
    <AppBarButton Name="ZoomInButton" VerticalAlignment="Center" Content=" + " />
    <AppBarSeparator />
    <AppBarElementContainer Width="50">
    </AppBarElementContainer>
</CommandBar.PrimaryCommands>

If you run the app—voila!—there’s the basic user interface.

Let’s add the Settings pane next.

As you may recall, we wrapped the main grid in a SplitView control. To construct our Settings pane, we need to now add what’s called a SplitView pane. Thanks to the magic of XAML, that can appear before the grid, like so:

</SplitView>
    <SplitView.Pane>
    </SplitView.Pane>
    <Grid>
    </Grid>
</SplitView>

Or it can appear after, like this:

</SplitView>
    <Grid>
    </Grid>
    <SplitView.Pane>
    </SplitView.Pane>
</SplitView>

I’ve done it both ways over various iterations of this app, but I prefer putting it at the bottom where it’s out of the way. (I should note, too, that I initially created this Settings pane UI as a standalone app first since the pane won’t appear in the XAML file’s Design view making it hard to test.)

In any event, locate the Grid’s closing tag (</Grid>) and open up a blank line between that and the closing SplitView tag (</SplitView>). Then, add the following XAML code:

<SplitView.Pane>
    <ScrollViewer VerticalScrollBarVisibility="Auto" BorderThickness="1,0,0,0" BorderBrush="Black">
        <StackPanel Margin="25">

        </StackPanel>
    </ScrollViewer>
</SplitView.Pane>

Basically, we’re using three controls inside each other. On the outside is that SplitView pane, which represents the Settings pane we’ll let the user display. Inside of that is a ScrollViewer control, which allows its contents to be scrolled; since the contents of the Settings pane will often be too tall to display fully, we will let the user scroll to see them all. And finally, there’s a standard StackPanel, which will contain each of the controls the user will interact with.

Before adding those, however, let’s write the C# code that will display this Settings pane.

To do so, locate SettingsButton in the XAML code, which is inside of the main command bar’s secondary commands area. Then, click inside of it to display its properties in the Properties pane. Then, click the Event handlers button (which looks like a lightning bolt) in the Properties pane to display a list of that button’s event handlers. Double-click in the textbox to the right of “Click” to create an empty Click event handler in MainPage.xaml.cs. It will look like this:

private void SettingsButton_Click(object sender, RoutedEventArgs e)
{

}

Inside of that event handler, add the following line(s) of code:

// Open the settings pane
SettingsPane.IsPaneOpen = !SettingsPane.IsPaneOpen;

Now, run the application. When you select “…” (See more) > Settings, a blank pane appears. To dismiss it, just click anywhere outside of it or type ESC.

I based the layout and style of the contents of the Settings pane on similar interfaces in UWP apps like OneNote and Mail. Add the following code in MainPage.xaml in the empty space between the opening and closing StackPanel tags inside of the ScrollViewer.

<TextBlock Text="Settings" Margin="0,0,0,15" Style="{StaticResource TitleTextBlockStyle}"/>

<TextBlock Text="Font" Style="{StaticResource BaseTextBlockStyle}" Margin="0,0,0,5" />
<ComboBox x:Name="FontsList" Height="35" Width="200" Margin="0,0,0,5" />
<TextBlock Text="Size" Style="{StaticResource BaseTextBlockStyle}" Margin="0,0,0,5" />
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Width="200" Orientation="Horizontal" Margin="0,0,0,25" >
    <ComboBox x:Name="FontSizesList" Height="35" Width="75" />
    <ToggleButton Name="BoldButton" Height="35" Width="35" Content="B" FontWeight="Bold" Margin="10,0,10,0" ToolTipService.ToolTip="Bold" />
    <ToggleButton Name="ItalicButton" Height="35" Width="35" Content="I" FontWeight="Bold" FontStyle="Italic" ToolTipService.ToolTip="Italic" />
</StackPanel>

<TextBlock Text="Word Wrap" Style="{StaticResource BaseTextBlockStyle}"/>
<ToggleSwitch Name="WordWrapSwitch" Margin="0,0,0,25" IsOn="False" OnContent="On" OffContent="Off" />

<TextBlock Text="Status Bar" Style="{StaticResource BaseTextBlockStyle}"/>
<ToggleSwitch Name="StatusBarSwitch" Margin="0,0,0,25" IsOn="True" OnContent="On" OffContent="Off" />

<TextBlock Text="Display labels in the command bar" Style="{StaticResource BaseTextBlockStyle}"/>
<ToggleSwitch Name="LabelsSwitch" Margin="0,0,0,25" IsOn="True" OnContent="On" OffContent="Off" />

<TextBlock Text="Auto Save" Style="{StaticResource BaseTextBlockStyle}"/>
<TextBlock Text="Automatically save your document every 30 seconds" Style="{StaticResource BodyTextBlockStyle}"/>
<ToggleSwitch Name="AutoSaveSwitch" Margin="0,0,0,25" IsOn="False" OnContent="On" OffContent="Off" />

<TextBlock Text="About" Margin="0,0,0,15" Style="{StaticResource TitleTextBlockStyle}"/>
<TextBlock Text=".NETpad 3.0 (UWP version)" Style="{StaticResource BaseTextBlockStyle}"/>
<TextBlock Text="Copyright © 2020 Paul Thurrott" Style="{StaticResource BodyTextBlockStyle}"/>

It’s a lot of code, but it’s a lot of simple code, and the purpose of most of it should be obvious, even for the new UWP controls like the toggle switches. The one possible exception is those Style attributes. In UWP, Microsoft prefers for developers to style text according to a type ramp that is automatically optimized for whatever display properties (size, resolution, scaling, and so on), rather than use fixed font sizes. This type ramp specifies type styles like Title, Subtitle, Base, Body, and others, so we’ll use those where appropriate in the UI. (For the textbox, we’ll use normal font sizes, since it’s a text editor.)

If you run the app now and display the Settings pane, you can see it with a few default values applied. We’ll fill this out properly with C# once we implement user/app settings. But this is a good place to stop for today; we’ll add that next time.

More soon.

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