Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Prototype Driven Development (PDD)

4.89/5 (21 votes)
26 Feb 2022MIT19 min read 18.7K  
Prototype Driven Development - a new way of developing quality software fast is explained
Prototype Driven Development (PDD) is a new way of creating quality software fast. It can be applied to building any software, but is especially useful for UI development. In this article, I explain PDD, talk about its advantages and present a detailed example showing how to build and refactor a simple UI Control using PDD.

Introduction

What is Prototype Driven Software Development (PDD)

Prototype Driven Software Development (PDD) is similar to Test Driven Development (TDD), but usually used when TDD is not possible (e.g., for Visual Development) or for larger features than those covered by the Unit Tests (in such cases, both PDD and TDD can be practiced - PDD for larger and TDD for more refined features).

Prototype Driven Development is a type of development under which for each new significant feature, the developer needs to go through the following steps:

  1. Create a new Visual Studio Project or even a Solution containing the Main(...) method for running the prototype. The prototype project should reference a subset of projects and packages of your main application project.
  2. Create the prototype code within the Main Prototype project.
  3. Polish the prototype code to perfection.
  4. Refactor the code by moving its re-usable parts to the re-usable projects that it references so that the main application code will have access to them also.
  5. Use the re-usable code within the main project.

Items 3 and 4 can be practiced interchangeably - one can move some code to the re-usable project and then continue working on the prototype and then move more code to the re-usable project.

Later, the prototype project or solution can be used in order to test, debug or modify the created feature.

Here is the diagram of PDD cycle:

Image 1

Advantages of PDD

The main advantage of PDD is that it allows to prototype, experiment and debug much faster since we are only compiling and restarting a lean prototype instead of a large and heavy main application.

Here is the full list of PDD benefits:

  1. The prototype allows you to experiment with and debug a feature very fast without waiting for the whole solution/application to start.
  2. Often, it is easier to mock up and inject the parts needed for a feature prototype instead of doing the mockup for the whole application. For example, creating a mockup of the backend data to test a single control can be much easier than creating a mockup to make the whole application work. This idea will be explored in some future articles about the IoC (inversion of control) and injection.
  3. PDD helps breaking up the code into small, manageable chunks improving the separation of concerns.
  4. Under PDD, every significant feature is tested as it is developed or modified, which improves the overall quality of the code.
  5. After a feature is created, it is easier to modify or to fix it within a small prototype that compiles and runs fast rather than as part of a large unweildy and slowly starting application.
  6. Prototypes and Unit Tests are also useful for code analysis. Indeed if you have some new code to learn, the best way to understand some complex method or class from the usage point of view is to use some prototypes or tests to actually play with that method or class. If the prototypes and tests do not exist yet, you can always create them for the purpose of analyzing some new code.

PDD Shortcomings (or Lack of Thereof)

Unlike TDD, there are basically no shortcomings to PDD.

In Test Driven Development (TDD) article, I wrote about some potential problems with TDD - in particular, the team can forget that their main purpose is to fulfill the requirements coming from the clients or client proxies and writing the tests are only means to achieve a better code, not the purpose in itself. I saw people writing tests for every method including very simple ones for which writing the tests will not decrease any likelihood of a bug. I saw projects literally coming to almost a full stop because of the sheer number of the tests and especially because of the extra slow builds - I saw builds that run tests for 2 days.

Unlike TDD, PDD should be practiced only for significant features, not for automated tests but to speed up the feature development, debugging and modifications and increase the quality of the resulting code. The prototypes do not have to be compiled and run for every build. Nothing terrible if some prototype goes out of sync with the code inside the re-usable projects. The prototype can be updated to compile only next time it is used for modifying or fixing its feature.

Where PDD Came From

I came up with PDD about 15-13 years ago, was practicing it since then always individually and sometimes introduced it also for the whole team on the projects on which I was an architect.

In my experience, it is extremely useful for fast, high quality development providing great separation of concerns both on the team and individual levels.

I won't be surprised to find out that other architects and developers (especially those who deal with the desktop UI application development) might have come up with similar development processes.

As far as I know, this article is the first attempt to systematize and publicly share this development methodology.

The Code for the PDD Sample

The coding sample is essentially an exercise that one has to go through in order to understand the PDD process as specified by the diagram at the top of this article.

All the code for the sample was built using Visual Studio 2022, C# 10 and .NET 6, but there is absolutely no reason one cannot use previous versions of VS, C# and .NET as long as some insignificant modifications are done to the project and solution files and perhaps also the code.

For the sample's visual development, I use Avalonia packages. Avalonia is an open source multiplatform WPF++ which I highly recommend for desktop or blazor UI development. To learn more about Avalonia, search for my articles on Avalonia on the codeproject.com starting with Multiplatform UI Coding with AvaloniaUI in Easy Samples. Part 1 - AvaloniaUI Building Blocks.

Those who understand WPF or other XAML frameworks should be able to easily understand all Avalonia code used in this article. If you do not have any XAML experience, you can still read and go through the sample explained in this article - it is ok if you do not understand parts of the sample as long as you understand the PDD process.

The code is located within NP.PrototypeDrivenDevelopmentSample github repository.

The repository contains two folders:

  • EmptyApp - containing the empty prototype application only with solution/project and folder structure - this is the solution that one can use to start working on the PDD exercise.

  • CompleteApp - containing the prototype application the way it should come out once you are done with the PDD exercise.

The structures of the two applications, EmptyApp and CompleteApp are very similar, except that EmptyApp does not have any code aside for that generated by the Visual Studio at the moment of project creation, so to give details on the initial file directory and project structure, we shall use the code inside EmptyApp.

Base folder of the app (e.g., EmptyApp) contains three subfolders:

  1. src - For the projects specific to this application, including the main application project (in our case, it only contains the main project with solution and nothing else).

  2. Core - For the re-usable projects with generic code that can be shared between different applications. For the sake of simplicity, we place there only one project NP.Visuals which (in our case) should contain any re-usable visual code. If you use Git, such re-usable projects can be placed into separate repositories and then brought under the Core folder as git sub-modules.

  3. Prototypes - A folder containing the prototype projects. Initially (within the EmptyApp folder), it contains only one prototype NP.PDD.PrototypeToClone. The NP.PDD.PrototypeToClone solution has all the needed projects and packages with required references.

One can create a project and a solution for a new Prototype from NP.PDD.PrototypeToClone simply by copying it to a new name and doing a bit of renaming. This way, all the needed projects, packages and references will be preserved and you will not have to add them manually. How to do it will be described in detail within the PDD Walkthrough section.

Here is the Solution Explorer view of the MainApp project:

Image 2

You can see that both NP.PDD.MainApp and NP.Visuals projects reference Avalonia packages.

The main project has some standard Avalonia startup files: Program.cs, App.axaml, App.axaml.cs, MainWindow.axaml and MainWindow.axaml.cs (extension ".axaml" is used in Avalonia for XAML files and double extension ".axaml.cs" for code behind).

If you try to start the NP.PDD.MainApp project, you will see an empty window.

Important Note: Many advantages of the PDD approach only show when the main application is large and slow to start and compile which is usually the case for any non-trivial UI application. In our demo, however, for the sake of simplicity, we start with an empty main application.

PDD Cycle Walkthrough Sample

Preliminary Information and Steps

What this Section is About

In this section, we shall describe in detail how to go through a full PDD cycle. The purpose of this cycle is to create a very simple Avalonia LabeledTextBox Custom Control which combines a text label with Avalonia TextBox.

If you want to understand and internalize the PDD, it is imperative that you go through the steps detailed in this section.

What is Avalonia

For those new to Avalonia - Avalonia is very similar to WPF but is more powerful, less buggy and works on three major platforms - Windows, Linux and Mac as well as within a browser via blazor. A very similar sample can be built using WPF for those who do not want to switch to Avalonia.

Here is a good introductory article on Avalonia - Multiplatform UI Coding with AvaloniaUI in Easy Samples. Part 1 - AvaloniaUI Building Blocks.

Visual Studio Setup for Avalonia

In order to be able to work with Avalonia, you need to install Visual Studio extension for Avalonia by clicking on EXTENSIONS->Manage Extensions menu item within your Visual Studio, then choose Online tab, find "Avalonia for Visual Studio ..." extension and press the "Install" button:

Image 3

PDD Cycle

Step 1 - Create the New Prototype Project by Cloning an Existing Project

We shall call the our prototype project NP.PDD.LabeledTextBoxPrototype.

In general, one should copy an older prototype to a new folder and then rename the solution, the project and the namespaces. This can be achieved much faster than creating a new prototype project and setting all the references in it.

We already have an empty prototype project NP.PDD.PrototypeToClone that I placed there specifically for the purpose of cloning.

Here are the detailed steps for creating a new prototype project NP.PDD.LabeledTextBoxPrototype out of NP.PDD.PrototypeToClone:

  1. Copy NP.PDD.PrototypeToClone folder to NP.PDD.LabeledTextPrototype folder.
  2. Rename the prototype VS solution file NP.PDD.LabeledTextBoxPrototype.sln.
  3. Open the new prototype solution in Visual Studio.
  4. Within the Solution Explorer, rename the main project of the prototype to match the solution and folder name (NP.PDD.LabeledTextBoxPrototype).

    Image 4

  5. Rename the old namespace "Sample" to "LabeledTextBoxPrototype" everywhere within the main prototype project, by going through the following steps:
    1. Open some .cs file within the main project, e.g., Program.cs.
    2. Select "Sample" part of the namespace.
    3. Use Ctrl-Shift-F to open the search window, switch over to "Replace in Files" tab inside Replace text box, put "LabeledTextBoxPrototype" string. Change "Look in" option to "Current Project", set File types to "*.axaml;*.cs" and press "ReplaceAll" button: Image 5

Now the prototype project is ready for coding.

Step 2 and 3 - Create the LabeledTextBox Control within the Main Project of the Prototype and Polish it to Perfection

Right click on the NP.PDD.LabeledTextBoxPrototype project choose Add->New Item menu item. In the opened dialog, choose "Code" tab on the left and "Class" on the right, name the class LabeledTextBox and press button "Add":

Image 6

New class LabeledTextBox will be created. Make the class public and make it inherit from Avalonia TemplatedControl class:

C#
using Avalonia.Controls.Primitives;

namespace NP.PDD.LabeledTextBoxPrototype
{
    public class LabeledTextBox : TemplatedControl
    {
    }
}  

Now add two Styled Properties of type string to the class - Text and Label.

Reminder: Avalonia Styled Properties are basically the same as WPF dependency properties. To create an Avalonia Styled Property fast, you can use the avsp snippet from Avalonia Snippets. Snippet installation instructions are available at the same URL. If you want to know more about Avalonia Attached and Styled properties, please, read Multiplatform Avalonia .NET Framework Programming Basic Concepts in Easy Samples article.

C#
using Avalonia;
using Avalonia.Controls.Primitives;

namespace NP.PDD.LabeledTextBoxPrototype
{
    public class LabeledTextBox : TemplatedControl
    {
        #region Label Styled Avalonia Property
        public string Label
        {
            get { return GetValue(LabelProperty); }
            set { SetValue(LabelProperty, value); }
        }

        public static readonly StyledProperty<string> LabelProperty =
            AvaloniaProperty.Register<LabeledTextBox, string>
            (
                nameof(Label)
            );
        #endregion Label Styled Avalonia Property

        #region Text Styled Avalonia Property
        public string Text
        {
            get { return GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        public static readonly StyledProperty<string> TextProperty =
            AvaloniaProperty.Register<LabeledTextBox, string>
            (
                nameof(Text)
            );
        #endregion Text Styled Avalonia Property
    }
}

Do not be scared by the number of lines to define only two Styled Properties - most of this code was generated by avsp snippet and using this snippet, one can create a Styled Property within a second or two.

We are done with the C# file. Now let us create the ControlTemplate in order to provide a visual representation for this Custom Control. We shall do it first within MainWindow.axaml file of the project and later move both the control and its template to the generic project NP.Visuals.

Open almost empty file MainWindow.axaml. Here is how it looks in the beginning (after copying it from NP.PDD.ProjectToClone project):

XAML
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Width="600"
        Height="400"
        x:Class="NP.PDD.LabeledTextBoxPrototype.MainWindow"
        Title="NP.PDD.LabeledTextBoxPrototype">

</Window>  

First of all, let us add an XML namespace that points to the NP.PDD.LabeledTextBoxPrototype C# namespace within the main prototype project - add the following line to the <Window ... tag:

XAML
xmlns:local="clr-namespace:NP.PDD.LabeledTextBoxPrototype"

so that the XAML code becomes:

XAML
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:NP.PDD.LabeledTextBoxPrototype"
        Width="600"
        Height="400"
        x:Class="NP.PDD.LabeledTextBoxPrototype.MainWindow"
        Title="NP.PDD.LabeledTextBoxPrototype">
</Window>  

(added line is in bold).

Now add the new control to the center of the Main Window:

XAML
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:NP.PDD.LabeledTextBoxPrototype"
        ...>
    <local:LabeledTextBox Label="The Text"
                          Text="Hello World!"
                          HorizontalAlignment="Center"
                          VerticalAlignment="Center"/>
</Window>  

Try running the application - the window will still be empty because our control has no control template and correspondingly does not result in a visual tree.

Now (ain't it exciting), let us build the template for the control inline - within the <local:LabeledTextBox.Template> tag:

XAML
<local:LabeledTextBox Label="The Text"
                      Text="Hello World!"
                      HorizontalAlignment="Center"
                      VerticalAlignment="Center">
    <local:LabeledTextBox.Template>
        <ControlTemplate TargetType="local:LabeledTextBox">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Label, 
                           RelativeSource={RelativeSource TemplatedParent}}"
                           VerticalAlignment="Center"/>
                <TextBlock Text=": "
                           VerticalAlignment="Center"/>
                <TextBox Text="{Binding Text, Mode=TwoWay, 
                         RelativeSource={RelativeSource TemplatedParent}}"
                         VerticalAlignment="Center"
                         Width="200"/>
            </StackPanel>
        </ControlTemplate>
    </local:LabeledTextBox.Template>
</local:LabeledTextBox>  

Here is the code for only control template:

XAML
<ControlTemplate TargetType="local:LabeledTextBox">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding Label, RelativeSource={RelativeSource TemplatedParent}}"
                   VerticalAlignment="Center"/>
        <TextBlock Text=": "
                   VerticalAlignment="Center"/>
        <TextBox Text="{Binding Text, Mode=TwoWay, 
                 RelativeSource={RelativeSource TemplatedParent}}"
                 VerticalAlignment="Center"
                 Width="200"/>
    </StackPanel>
</ControlTemplate>  

Specifying the TargetType of the control template allows to use Binding within the template to bind to the properties defined within the class of the TemplatedParent control (in our case, such class is LabeledTextBox).

RelativeSource={RelativeSource TemplatedParent} part of the binding means that we are binding to a property defined on the TemplatedParent (meaning the object that uses the current ControlTemplate as its template).

There is a horizontally oriented StackPanel within the template which horizontally arranges the label containing TextBlock followed by a colon ":" followed by the TextBox. The text of the TextBlock is bound to the Label Styled Property of the LabeledTextBox control containing the template and the text of the TextBox is bound to the Text Styled Property of the same control.

Now start the application and lo and behold, you'll see the label "The Text" followed by a TextBox containing "Hello World!" string:

Image 7

If you know how to use Avalonia snoop-like tool, you can even check that when you change the text within the TextBox, the Text property on the LabeledTextBox also changes (because of the two way binding).

Reminder: To start the Avalonia snoop-like tool - click on the main window of the application and press F12 key. Here is where you can learn more about Avalonia Tool.

Note: The custom control we created is only for the PDD demo - real life custom controls should expose and bind more properties, e.g., it should be possible to change the style of the label and the style of the text within the TextBox independently, etc.

Step 2 is completed - we have a working prototype with the code located in the main project of the prototype solution and we tested that it works.

Since our application is very simple, we also assumed that we completed Step 3 - polishing the prototype code to perfection and from here, we go straight to step 4 - Refactor the code by moving its re-usable parts to the generic project(s).

Step 4 - Refactor the Code by Moving its re-usable Parts to the Generic Project(s)

In our case, we are going to move our LabeledTextBox control implementation and control template over to NP.Visuals project.

Start by dragging the file LabeledTextBox.cs over from NP.PDD.LabeledTextBoxPrototype project to NP.Visuals projects within the Solution Explorer. After that, remove the LabeledTextBox.cs file from the prototype's main project.

Change the namespace of the LabeledTextBox class now defined within NP.Visuals project to NP.Visuals:

C#
namespace NP.Visuals
{
    public class LabeledTextBox : TemplatedControl
    {
        ...
    }
}  

Just to re-iterate, LabeledTextBox.cs file should no longer exist under the prototype's main project NP.PDD.LabeledTextBoxPrototype and should exist under NP.Visuals and its namespace should be changed to NP.Visuals.

Now update the references to our LabeledTextBox class within MainWindow.axaml file. First of all, we need to create a new XML namespace that points to NP.Visuals namespace within NP.Visuals assembly. We add the following line to <Window ... tag:

XAML
xmlns:visuals="clr-namespace:NP.Visuals;assembly=NP.Visuals"

so that the Window tag now looks like:

XAML
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:visuals="clr-namespace:NP.Visuals;assembly=NP.Visuals"
        xmlns:local="clr-namespace:NP.PDD.LabeledTextBoxPrototype"
        ...>  

Added namespace line is in bold.

Now we change the prefix local: to prefix visuals: everywhere within the MainWindow.axaml file. Here is what we get:

XAML
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:visuals="clr-namespace:NP.Visuals;assembly=NP.Visuals"
        ...>
    <visuals:LabeledTextBox Label="The Text"
                            Text="Hello World!"
                            HorizontalAlignment="Center"
                            VerticalAlignment="Center">
        <visuals:LabeledTextBox.Template>
            <!-- TargetType allows to use Binding to bind to the 
                 Styled Properties defined on the control of the templated parent class
                 -->
            <ControlTemplate TargetType="visuals:LabeledTextBox">
                <!-- this StackPanel arranges the items contained within it horizontally -->
                <StackPanel Orientation="Horizontal">
                    <!-- TextBlock whose text is bound to the Label Styled Property
                         of its template parent control -->
                    <TextBlock Text="{Binding Label, 
                               RelativeSource={RelativeSource TemplatedParent}}"
                               VerticalAlignment="Center"/>
					
                    <!-- TextBlock displaying the colon followed by a space - ": "-->
                    <TextBlock Text=": "
                               VerticalAlignment="Center"/>

                    <!-- TextBox whose Text is two-way bound to the Text Styled Property
                         of its template parent control -->
                    <TextBox Text="{Binding Text, Mode=TwoWay, 
                             RelativeSource={RelativeSource TemplatedParent}}"
                             VerticalAlignment="Center"
                             Width="200"/>
                </StackPanel>
            </ControlTemplate>
        </visuals:LabeledTextBox.Template>
    </visuals:LabeledTextBox>
</Window>  

The added prefix is in bold. You can see that I added some documentation to the MainWindow.axaml file, but otherwise, the code is exactly the same as before - the only difference is that now we refer to LabeledTextBox class defined within a different assembly and under a different namespace.

Try running the application - it should produce exactly the same result as before.

As you remember, we defined our control template inline for convenience. We want to move it over to a XAML file within the NP.Visuals project. As a first sub-step, move it out of our <visuals:LabeledTextBox... tag and turn it into an XAML resource, but still keep it within the same MainWindow.axaml file.

We create <Window.Resources section in XAML code and place our template there also adding an x:Key="LabeledTextBoxTemplate" attribute to it. This x:Key value we specify within StaticResource markup extension to refer to the template within our LabeledTextBox control instance:

XAML
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:visuals="clr-namespace:NP.Visuals;assembly=NP.Visuals"
        ...>
    <Window.Resources>
        <ControlTemplate x:Key="LabeledTextBoxTemplate"
                         TargetType="visuals:LabeledTextBox">
            <!-- this StackPanel arranges the items contained within it horizontally -->
            <StackPanel Orientation="Horizontal">
                <!-- TextBlock whose text is bound to the Label Styled Property
                     of its template parent control -->
                <TextBlock Text="{Binding Label, 
                           RelativeSource={RelativeSource TemplatedParent}}"
                           VerticalAlignment="Center"/>

                <!-- TextBlock displaying the colon followed by a space - ": "-->
                <TextBlock Text=": "
                           VerticalAlignment="Center"/>

                <!-- TextBox whose Text is two-way bound to the Text Styled Property
                     of its template parent control -->
                <TextBox Text="{Binding Text, Mode=TwoWayTemplate, 
                         RelativeSource={RelativeSource TemplatedParent}}"
                         VerticalAlignment="Center"
                         Width="200"/>
            </StackPanel>
        </ControlTemplate>
    </Window.Resources>
    <visuals:LabeledTextBox Label="The Text"
                            Text="Hello World!"
                            Template="{StaticResource LabeledTextBoxTemplate}"
                            HorizontalAlignment="Center"
                            VerticalAlignment="Center"/>
</Window>

The new lines are as always in bold.

The application should still produce exactly the same UI with exactly the same behavior.

Next sub-step is to put our ControlTemplate within a Style (styles are better to use for creating the visuals than templates, because using styles you can also define the default values for properties on the visual control that uses the style itself while using Templates you can only define what is inside the visual control).

For now, we shall keep the Style in our MainWindow.axaml file.

In order to define the style, we need to create a <Window.Styles section for styles (this is a difference from WPF where styles are simply XAML resources). Here is how the new code looks:

XAML
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:visuals="clr-namespace:NP.Visuals;assembly=NP.Visuals"
        ...>
    <Window.Styles>
        <Style Selector="visuals|LabeledTextBox.HorizontalLabeledTextBox">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="visuals:LabeledTextBox">
                        <!-- this StackPanel arranges the items contained 
                             within it horizontally -->
                        <StackPanel Orientation="Horizontal">
                            <!-- TextBlock whose text is bound to the Label Styled Property
                         of its template parent control -->
                            <TextBlock Text="{Binding Label, 
                            RelativeSource={RelativeSource TemplatedParent}}"
                                       VerticalAlignment="Center"/>

                            <!-- TextBlock displaying the colon followed by a space - ": "-->
                            <TextBlock Text=": "
                                       VerticalAlignment="Center"/>

                            <!-- TextBox whose Text is 
                                 two-way bound to the Text Styled Property
                         of its template parent control -->
                            <TextBox Text="{Binding Text, Mode=TwoWay, 
                            RelativeSource={RelativeSource TemplatedParent}}"
                                     VerticalAlignment="Center"
                                     Width="200"/>
                        </StackPanel>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Styles>
    <visuals:LabeledTextBox Label="The Text"
                            Text="Hello World!"
                            Classes="HorizontalLabeledTextBox"
                            HorizontalAlignment="Center"
                            VerticalAlignment="Center"/>
</Window>  

Some explanation about Avalonia Styles: Important part of the Avalonia style is the Selector which is a string that defines where the style applies. Note that the XML namespace prefix within the selector is separated by a pipe character '|', not a colon. This is because the colon can be used within a selector for some different purposes (which are beyond this article).

Selector visuals|LabeledTextBox.HorizontalLabeledTextBox means that the style will be applied to all LabeledTextBox controls of the class HorizontalLabeledTextBox. Now, within the control itself, we can specify its Classes property (in our case, it is Classes="HorizontalLabeledTextBox"). In general, more than one class can be specified and then, unlike in WPF, we can apply multiple styles at once to the same control.

Rerun the application, it should still run exactly the same.

Next sub-step is to create a project folder Styles under NP.Visuals project and create an Avalonia LabeledTextBoxStyles.axaml Styles file within it. In order to create the Avalonia Styles, file right click on the newly created folder Styles within the Solution Explorer, choose Add->New Items menu item, choose "Avalonia" tab on the left, select "Styles (Avalonia)" option in the middle, change the name of the file to "LabeledTextBoxStyles.axaml" and press the button "Add".

Image 8

Add the namespace Visuals to the newly created LabeledTextBoxStyles.axaml file to point to NP.Visuals namespace within the same project and move the style from MainWindow.axaml file into the same LabeledTextBoxStiles.axaml file. This is how the LabeledTextBoxStyles.axaml should look now:

XAML
<Styles xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:visuals="clr-namespace:NP.Visuals">
    ...

    <Style Selector="visuals|LabeledTextBox.HorizontalLabeledTextBox">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="visuals:LabeledTextBox">
                    <!-- this StackPanel arranges the 
                         items contained within it horizontally -->
                    <StackPanel Orientation="Horizontal">
                        <!-- TextBlock whose text is bound to the Label Styled Property
                         of its template parent control -->
                        <TextBlock Text="{Binding Label, 
                        RelativeSource={RelativeSource TemplatedParent}}"
                                   VerticalAlignment="Center"/>

                        <!-- TextBlock displaying the colon followed by a space - ": "-->
                        <TextBlock Text=": "
                                   VerticalAlignment="Center"/>

                        <!-- TextBox whose Text is two-way bound to the Text Styled Property
                         of its template parent control -->
                        <TextBox Text="{Binding Text, Mode=TwoWay, 
                        RelativeSource={RelativeSource TemplatedParent}}"
                                 VerticalAlignment="Center"
                                 Width="200"/>
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Styles>  

We also need to add a reference to the new LabeledTextBoxStyles.axaml within our MainWindow.axaml file:

XAML
<StyleInclude Source="avares://NP.Visuals/Styles/LabeledTextBoxStyles.axaml"/>

avares is a magic word - short for Avalonia Resources is followed by the assembly name (NP.Visuals) followed by the path to the file within the assembly (/Styles/LabeledTextBoxStyles.axaml).

Here is how our MainWindow.axaml file should look now:

XAML
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:visuals="clr-namespace:NP.Visuals;assembly=NP.Visuals"
        ...>
    <Window.Styles>
        <StyleInclude Source="avares://NP.Visuals/Styles/LabeledTextBoxStyles.axaml"/>
    </Window.Styles>
    <visuals:LabeledTextBox Label="The Text"
                            Text="Hello World!"
                            Classes="HorizontalLabeledTextBox"
                            HorizontalAlignment="Center"
                            VerticalAlignment="Center"/>
</Window>

Run the prototype again and the application still looks and behaves exactly the same.

We are basically done with the prototype. I advise you to keep it for possible modifications and fixes to the LabeledTextBox control and its style in the future.

Step 5 - Add the Calls to the Re-Usable Code to the Main Application wherever they are needed

Go back to the main application and open the solution file /src/NP.PDD.MainApp.sln.

The solution already has NP.Visuals project in it, so, within the project, you should be able to see the newly added LabeledTextBox.cs and Styles/LabeledTextBoxStyles.axaml files.

The main project already references NP.Visuals re-usable project.

For our demo, it is sufficient to add just one re-usable LabeledTextBox control in order to prove that it works also in the main application. We can add it in the center of the MainWindow by copying the code from the MainWindow.axaml file of our prototype. We can also copy the code for adding a reference to the Avalonia Styles file.

The resulting MainWindow.axaml file of the main application now looks very similar to that of the prototype, with only difference being in the C# namepace to the application:

XAML
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:visuals="clr-namespace:NP.Visuals;assembly=NP.Visuals"
        Width="600"
        Height="400"
        x:Class="NP.PDD.MainApp.MainWindow"
        Title="NP.PDD.MainApp">
    <Window.Styles>
        <StyleInclude Source="avares://NP.Visuals/Styles/LabeledTextBoxStyles.axaml"/>
    </Window.Styles>
    <visuals:LabeledTextBox Label="The Text"
                            Text="Hello World!"
                            Classes="HorizontalLabeledTextBox"
                            HorizontalAlignment="Center"
                            VerticalAlignment="Center"/>
</Window> 

Start the main application - it should look and behave exactly the same as our prototype.

Conclusion

This article describes Prototype Driven Development (PDD) - a new way of developing quality software (especially the UI software) fast which I used extensively and successfully for the past 13+ years.

PDD can be practiced by any individual developer and by the whole teams as well.

The main advantage of PDD is that it allows fast recompilation, restarting and debugging a small light weight prototype instead of a large and heavy main application.

This article also shows the steps for creating a custom control in Avalonia and refactoring the code so that it will become part of re-usable project(s) and could be used by other projects.

The main emphasis, however, is on PDD, so do not worry if you do not understand everything related to Avalonia.

History

  • 3rd February, 2022: Initial version

License

This article, along with any associated source code and files, is licensed under The MIT License