Building an OSD Front End using C#: Part 5 – Computer Name Form Design

Now that we have the basic layout of the computer name tab built but we don’t have any of the computer naming logic that’s our next step.

When it comes to the layout, at the same level as the button from our last example or right below the closing </Grid.RowDefinitions> tag we’re going to start building our computer name input form. We’ll start by adding a <DockPanel> control.

<DockPanel Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"&gt;

</DockPanel&gt;

A dock panel is a control which will allow us to dock various controls inside it and get them to stretch to the full width. In this case, I wanted to have our group box controls stretch to fill the width of the tab so we don’t have weird ratios of empty space as we move between tabs or between different group box controls on the same tab. As with the button we assign the dock panel to a row but in this case we assign to Grid.Row="0" which is the very first row in the grid. The horizontal and vertical alignments are set to stretch so the dock panel fills all the space available.

Now, let’s build our computer name section of the form.

<GroupBox DockPanel.Dock="Top" Header="Computer Name" VerticalAlignment="Top" Margin="5,5,5,5"&gt;
    <Grid HorizontalAlignment="Center"&gt;
        <Grid.RowDefinitions&gt;
            <RowDefinition Height="Auto" /&gt;
        </Grid.RowDefinitions&gt;
        <Grid.ColumnDefinitions&gt;
            <ColumnDefinition Width="Auto" /&gt;
            <ColumnDefinition Width="Auto" /&gt;
        </Grid.ColumnDefinitions&gt;
        <Label Grid.Row="0" Grid.Column="0" Content="Enter Computer Name:" HorizontalAlignment="Left" Margin="10,10,10,10" VerticalAlignment="Top" /&gt;
        <TextBox Grid.Row="0" Grid.Column="1" x:Name="TextBoxComputerName" Width="175" HorizontalAlignment="Left" Margin="10,10,10,10" VerticalAlignment="Top" CharacterCasing="Upper" TextChanged="TextBoxComputerName_TextChanged" /&gt;
    </Grid&gt;
</GroupBox&gt;

We’re using a new control called a group box. It lets us put groups of content together into a single control when they’re related. In this case it’s to contain a label and a text box which prompts for a computer name to be inputted.

You’ll notice that we again use a grid and this time we have both rows and columns defined to help us align everything and make it look neat and orderly.

There is a TextChanged event trigger that we’ve added to the text box. We can ignore that for now and we’ll get to that later on in the behind the form code section.

The next part of the computer name entry form is the table where we show any validation rules and if they pass or fail:

<GroupBox DockPanel.Dock="Top" Header="Computer Name Validation" VerticalAlignment="Top" Margin="5,5,5,5"&gt;
    <Grid HorizontalAlignment="Left" x:Name="GridComputerNameRules"&gt;
        <Grid.ColumnDefinitions&gt;
            <ColumnDefinition Width="Auto" /&gt;
            <ColumnDefinition Width="Auto" /&gt;
        </Grid.ColumnDefinitions&gt;
        <Grid.RowDefinitions&gt;
            <RowDefinition Height="Auto" /&gt;
            <RowDefinition Height="Auto" /&gt;
            <RowDefinition Height="Auto" /&gt;
            <RowDefinition Height="Auto" /&gt;
        </Grid.RowDefinitions&gt;
        <Label Grid.Column="0" Grid.Row="0" Content="REQ - Length &gt;= 5:" x:Name="LabelRuleGreaterThan" /&gt;
        <Label Grid.Column="1" Grid.Row="0" Content="False" x:Name="LabelRuleGreaterThanStatus" Foreground="{StaticResource InvalidItemBrush}" /&gt;
        <Label Grid.Column="0" Grid.Row="1" Content="REQ - Length <= 15:" x:Name="LabelRuleLessThan" /&gt;
        <Label Grid.Column="1" Grid.Row="1" Content="False" x:Name="LabelRuleLessThanStatus" Foreground="{StaticResource InvalidItemBrush}" /&gt;
        <Label Grid.Column="0" Grid.Row="2" Content="OPT - Starts with UMN:" x:Name="LabelRuleStartsWith" /&gt;
        <Label Grid.Column="1" Grid.Row="2" Content="False" x:Name="LabelRuleStartsWithStatus" Foreground="{StaticResource InvalidItemBrush}" /&gt;
        <Label Grid.Column="0" Grid.Row="3" Content="OPT - Ends with blah:" x:Name="LabelRuleEndsWith" /&gt;
        <Label Grid.Column="1" Grid.Row="3" Content="False" x:Name="LabelRuleEndsWithStatus" Foreground="{StaticResource InvalidItemBrush}" /&gt;
    </Grid&gt;
</GroupBox&gt;

Here we see a lot of things we’ve built into the form already just in a slightly different way. You will notice we’re leveraging a {StaticResource} for InvalidItemBrush. This lets us define the color of an invalid item and then simply reference it and change it by changing our theme file. If you didn’t use the UMN theme file you may need to configure this in a xaml file.

Now we can move behind the scenes to look at how this works. However, before we move behind the form we’ll need to spend a little time on how we’re going to handle settings and configuration. In the next post we’ll go over the AppSettings.json file and how we parse the json file and use those settings in the application.

Building an OSD Front End using C#: Part 4 – Adding Controls and Going Deeper

Up until now we’ve been very focused on getting everything ready. Now we’re ready to actually start building out our form controls and some of the logic behind them.

Let’s get our MainWindow.Xaml setup to start adding tabs, for this we’ll need to add some basic framework controls where we can build everything inside (I’ve cut all the extra stuff out of the Controls:MetroWindow tag):

<Controls:MetroWindow&gt;
    <Grid&gt;
        <TabControl x:Name="TabControlMainWindow" Margin="0,0,0,0" TabStripPlacement="Left" FontFamily="Arial" FontSize="14" FontWeight="Bold" RenderTranformOrigin="0.5,0.5"&gt;

        </TabControl&gt;
    </Grid&gt;
</Controls:MetroWindow&gt;

Everything we write for the main form from here on out will be within the TabControl. Let’s go through a little of what we have here.

The first new control we’ve added is a <Grid> which is needed as the base. We don’t need to add anything to it since it will just be the primary container on our form.

Next we have the main piece of our form which is a <TabControl> control. We need to define several properties of the TabControl to make sure that it functions properly on our form:

  1. x:Name – Name that the control will be called including in code later on.
  2. Margin – We want the TabControl going all the way out to the edge of the form so we make this 0 on all sides.
  3. TabStripPlacement – Moves the tabs from the top of the form to the left side as a vertical list.
  4. FontFamily – This just changes the font used on the tab names.
  5. FontSize – Change the font size for the tab names.
  6. FontWeight – Make the text bold for the tab names.
  7. RenderTransformOrigin – Determines where the center point of the control.

The next thing we’re going to want to add is our first <TabItem> control. Here’s the code for the computer name tab and below the code we’ll discuss what we’re doing.

<TabItem x:Name="tabComputerName" Header="Computer Name"&gt;
    <Grid x:Name="gridComputerName" Margin="0,0,0,0"&gt;
        <Grid.RowDefinitions&gt;
            <RowDefinition Height="*" /&gt;
            <RowDefinition Height="Auto" /&gt;
        </Grid.RowDefinitions&gt;
        <Button Grid.Row="1" x:Name="buttonComputerNameNext" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="5,5,5,5" Content="Next &gt;&gt;" Width="125" Click="NextButtonHandler" /&gt;
    </Grid&gt;
</TabItem&gt;

Inside a <TabControl> you have <TabItem>‘s. Think of a tab item as just a container for all the controls on a given tab. I’ve found the best way for me when building a tab in the front end is to put down a <Grid> control like we had before but this time to define two rows. The first row will expand to fill as much space as it can. The second row will grow to the height it requires but no more.

Now, you’ll see I’ve added a button called buttonComputerNameNext. You’ll also notice it’s in the Grid.Row 1. This is the second row (numbering starts from 0) and we’re putting it in the bottom right hand corner. This is the next button. I’ve also added a click event handler. Below is the code you’ll want to add to the MainWindow.xaml.cs file.

private void NextButtonHandler( object sender, RoutedEventArgs e ) {
    TabControlMainWindow.SelectedIndex++;
    ( TabControlMainWindow.Items[tabControlMainWindow.SelectedIndex] as TabItem ).IsEnabled = true;
}

This simple button handler will increase the SelectedIndex property of the TabControlMainWindow. The selected index is the index of the currently selected tab. This code allows all our next buttons to share the same event handler and will also allow us to enable or disable tabs without needing special code to handle those cases. We can also re-order tabs through the designer without worrying about updating code to change tab orders on the next buttons either.

It also then enables the control which is disabled by default so it cannot be selected. To do that we need to get the item and the selected index and cast it to be a TabItem so we can use it’s properties.

Building an OSD Front End using C#: Part 3 – Styling Our WPF Form

All we have so far is a blank Xaml form without much content. Let’s get some basic styles applied to the form and make it look a little more respectable. I have created a custom accent theme for UMN WPF forms using MahApps.Metro. The code for this is below, if you’d like to use it feel free to download and add it to your project as a Xaml file.

There are some good guides on the web page for MahApps.Metro around how to build a theme file like I’ve included. You’ll notice I basically took their theme template and adapted it to our color scheme.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                    xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"
                    mc:Ignorable="options"&gt;
    <Color x:Key="HighlightColor"&gt;#FFFFCC33</Color&gt;
    <Color x:Key="AccentBaseColor"&gt;#FF7A0019</Color&gt;
    <!-- 80% --&gt;
    <Color x:Key="AccentColor"&gt;#FF7A0019</Color&gt;
    <!-- 60% --&gt;
    <Color x:Key="AccentColor2"&gt;#997A0019</Color&gt;
    <!-- 40% --&gt;
    <Color x:Key="AccentColor3"&gt;#667A0019</Color&gt;
    <!-- 20% --&gt;
    <Color x:Key="AccentColor4"&gt;#337A0019</Color&gt;

    <Color x:Key="ValidItem"&gt;#FF27BC1A</Color&gt;
    <Color x:Key="InvalidItem"&gt;#FFBC1A1A</Color&gt;

    <!-- re-set brushes too --&gt;
    <SolidColorBrush x:Key="HighlightBrush" Color="{StaticResource HighlightColor}" options:Freeze="True" /&gt;
    <SolidColorBrush x:Key="AccentBaseColorBrush" Color="{StaticResource AccentBaseColor}" options:Freeze="True" /&gt;
    <SolidColorBrush x:Key="AccentColorBrush" Color="{StaticResource AccentColor}" options:Freeze="True" /&gt;
    <SolidColorBrush x:Key="AccentColorBrush2" Color="{StaticResource AccentColor2}" options:Freeze="True" /&gt;
    <SolidColorBrush x:Key="AccentColorBrush3" Color="{StaticResource AccentColor3}" options:Freeze="True" /&gt;
    <SolidColorBrush x:Key="AccentColorBrush4" Color="{StaticResource AccentColor4}" options:Freeze="True" /&gt;
    <SolidColorBrush x:Key="ValidItemBrush" Color="{StaticResource ValidItem}" options:Freeze="True" /&gt;
    <SolidColorBrush x:Key="InvalidItemBrush" Color="{StaticResource InvalidItem}" options:Freeze="True" /&gt;

    <SolidColorBrush x:Key="WindowTitleColorBrush" Color="{StaticResource AccentColor}" options:Freeze="True" /&gt;

    <LinearGradientBrush x:Key="ProgressBrush" StartPoint="1.002,0.5" EndPoint="0.001,0.5" options:Freeze="True"&gt;
        <GradientStop Offset="0" Color="{StaticResource HighlightColor}" /&gt;
        <GradientStop Offset="1" Color="{StaticResource AccentColor3}" /&gt;
    </LinearGradientBrush&gt;

    <SolidColorBrush x:Key="CheckmarkFill" Color="{StaticResource AccentColor}" options:Freeze="True" /&gt;
    <SolidColorBrush x:Key="RightArrowFill" Color="{StaticResource AccentColor}" options:Freeze="True" /&gt;

    <Color x:Key="IdealForegroundColor"&gt;White</Color&gt;
    <SolidColorBrush x:Key="IdealForegroundColorBrush" Color="{StaticResource IdealForegroundColor}" options:Freeze="True" /&gt;
    <SolidColorBrush x:Key="IdealForegroundDisabledBrush" Opacity="0.4" Color="{StaticResource IdealForegroundColor}" options:Freeze="True" /&gt;
    <SolidColorBrush x:Key="AccentSelectedColorBrush" Color="{StaticResource IdealForegroundColor}" options:Freeze="True" /&gt;

    <!--  DataGrid brushes  --&gt;
    <SolidColorBrush x:Key="MetroDataGrid.HighlightBrush" Color="{StaticResource AccentColor}" options:Freeze="True" /&gt;
    <SolidColorBrush x:Key="MetroDataGrid.HighlightTextBrush" Color="{StaticResource IdealForegroundColor}" options:Freeze="True" /&gt;
    <SolidColorBrush x:Key="MetroDataGrid.MouseOverHighlightBrush" Color="{StaticResource AccentColor3}" options:Freeze="True" /&gt;
    <SolidColorBrush x:Key="MetroDataGrid.FocusBorderBrush" Color="{StaticResource AccentColor}" options:Freeze="True" /&gt;
    <SolidColorBrush x:Key="MetroDataGrid.InactiveSelectionHighlightBrush" Color="{StaticResource AccentColor2}" options:Freeze="True" /&gt;
    <SolidColorBrush x:Key="MetroDataGrid.InactiveSelectionHighlightTextBrush" Color="{StaticResource IdealForegroundColor}" options:Freeze="True" /&gt;

    <SolidColorBrush x:Key="MahApps.Metro.Brushes.ToggleSwitchButton.OnSwitchBrush.Win10" Color="{StaticResource AccentColor}" options:Freeze="True" /&gt;
    <SolidColorBrush x:Key="MahApps.Metro.Brushes.ToggleSwitchButton.OnSwitchMouseOverBrush.Win10" Color="{StaticResource AccentColor2}" options:Freeze="True" /&gt;
    <SolidColorBrush x:Key="MahApps.Metro.Brushes.ToggleSwitchButton.ThumbIndicatorCheckedBrush.Win10" Color="{StaticResource IdealForegroundColor}" options:Freeze="True" /&gt;
</ResourceDictionary&gt;

Now we just need to setup our App.Xaml file to include a <Application.Resources> section as seen below:

<Application.Resources&gt;
    <ResourceDictionary&gt;
        <ResourceDictionary.MergedDictionaries&gt;
            <!-- MahApps.Metro resource dictionaries. --&gt;
            <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" /&gt;
            <ResourceDictionary Source="pack://application:,,,/UMN-OSDFrontend;component/Resources/Icons.xaml" /&gt;
            <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" /&gt;
            <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Colors.xaml" /&gt;
            <ResourceDictionary Source="pack://application:,,,/UMN-OSDFrontend;component/Styles/CustomAccentUMN.xaml" /&gt;
            <!-- Accent and AppTheme Settings --&gt;
            <!-- Un-comment the following line if you're not using the UMN theme --&gt;
            <!--<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/Blue.xaml" /&gt;--&gt;
            <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/BaseDark.xaml" /&gt;
        </ResourceDictionary.MergedDictionaries&gt;
    </ResourceDictionary&gt;
</Application.Resources&gt;

Now we have a themed window. While we don’t have anything on it yet we’re now fully ready to build on top of this foundation. I encourage you to play around a little with the theme’s and colors while you’re building your front end to get a better idea of what looks good and what works for your purposes.

Next post we’re going to dive deep into the meat of building the form and talk about the first two tabs for computer naming and pre-flight checks.

Building an OSD Front End using C#: Part 1 – Getting Started

This is a blog series walking through how to build your own OSD Front End using C#.  It walks through everything I went through creating the UMN-OSDFrontEnd project which is now open source and available on GitHub.

I’m using Visual Studio.  If you are starting out it’s the easiest (in my opinion) way to get up and running with C#.  There are other ways to build C# projects but I’m going to assume for the purposes of this guide that you have access to Visual Studio.

You’ll want to create a new WPF App (.NET Framework) project.  A key thing is to ensure you change your project’s target framework from the latest version to .NET Framework 4.5.  This will ensure you’re building to be compatible with WinPE.

OSDFrontEndProjectCreation

Next we will need to get all the NuGet packages we’re going to use throughout the project by going to Project -> Manage NuGet Packages then search for these under the browse section:

  • ControlzEx
    • The only reason this needs to be installed is that it’s a requirement of the MahApps.Metro package.
  • MahApps.Metro
    • This gives you access to a metro styled app using WPF.  It makes it way easier to customize the application and make it look good.
  • MahApps.Metro.Resources
    • Requires MahApps.Metro
    • This contains a bunch of useful resources such as icons/images.
  • Mono.Options
    • Gives us an easy way to parse parameters passed to the executable.
  • Newtonsoft.Json
    • Makes it easier to handle json data.

Building an OSD Front End using C#: Part 2 – Initial Form Configuration

Now that we have our development environment sorted and our basic project created we need to lay out the basic framework for our form.  Since we’re using the MahApps.Metro resources we’ll need to change a few things to get everything looking right.

  1. Open the MainWindow.Xaml file from the right hand menu and update the <Window tag to be <Controls:MetroWindow
  2. Now we need to make some changes to the MetroWindow tag, see the following code block and compare to your project. This will add in the namespaces we’ll be using as well as adding in some info about the overall form design.
<Controls:MetroWindow x:Class="UMN_OSDFrontEnd.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
        xmlns:local="clr-namespace:UMN_OSDFrontEnd"
        mc:Ignorable="d"
        Closing="MetroWindow_Closing"
        Loaded="MetroWindow_Loaded"
        Title="UMN OSD FrontEnd" Height="500" Width="1000" ResizeMode="NoResize"&gt;
  1. Open the MainWindow.Xaml.cs file and change the MainWindow class to be MainWindow : MetroWindow
using MahApps.Metro.Controls;

namespace UMN_OSDFrontEnd {
    public partial class MainWindow : MetroWindow {

We should now be able to use F5 to build the application and have a basic Xaml form without any info on it.