tangible T4 Editor Blog

News, Tip and Tricks from the tangible T4 Editor team.

  • Home
  • Blog
  • Blog Series: Model Driven Development with T4 templates – Part 5

Blog Series: Model Driven Development with T4 templates – Part 5

 

This is the last part of the blog series about some of tangible engineering’s contributions to a Model Driven Development process in software development. In this part we are going to create a basic generic user interface that allows users to explore and manipulate data from any domain specific model created as a Persistent Object Model.

image

The user interface is created based on the .tasl model created in Part 2 of this series and grants access to a SQL Server database built upon the EntityFramework code classes of Part 3 and 4.

 

Blog Series Overview

Part 1: Model Driven Development and Series Overview

Part 2: Create a Persistence Model using Tangible Modeling Tools

Part 3: Generating Domain Specific data objects using T4 and EntityFramework 5 CodeFirst

Part 4: Data Persistence and Versioning

Part 5: Generate a Basic UI using T4 and Windows Presentation Foundation

 

In this Part

The resulting basic User Interface

Changes to the CodeClasses

Prepare the T4 Templates

T4 Templates Content

Summary

 

Desired Result

This is what the basic user interface created in this blog post is going to look like at the end. After having finished this post we’re going to have a WPF window containing a TabPanel for each Persistent Object of the domain specific model. Inside this tab there will be a list of all instances of that Persistent Object existing in the database. Next to that list there will be a detail view displaying all properties of the selected Persistent Object instance.

 

image

 

The User will be able to edit the properties of the Persistent Objects, Add new ones and delete existing ones. And of course the user will be able to create relationships between instances.

 

Changes to the Code Classes

Prior to generating WPF markup and logic for the user interface, we need to make two slight adjustments to the T4 template that generates the EntityFramework code classes (named TaslToCodeFirst.tt in Part 3): We need the template to generate partial data classes and mark collection properties as “virtual”.

 

Generate partial classes

The code classes generated by the TaslToCodeFirst.tt should be partial classes so we can override the ToString() method in a non-generated codefile in order to get a better visual representation for the generated objects and not to lose those changes upon regeneration of the template.

Change the following line inside the T4 template

#> public class <#= persistentObject.Name #> : System.ComponentModel.INotifyPropertyChanged

to

#> public partial class <#= persistentObject.Name #>                                       

          : System.ComponentModel.INotifyPropertyChanged                                   

 

Virtual collection properties

The second change is as easily applied as the first one. Collection properties need to be marked as “virtual” so that proxy-classes generated by the EntityFramework may recognize them as navigation properties:

Change

public ObservableCollection<<#= relation.Source.Name #>> <#= relation.SourceRoleName #>

to

public virtual ObservableCollection<<#= relation.Source.Name #>>                           

               <#= relation.SourceRoleName #>                                              

 

and

 

public ObservableCollection<<#= relation.Target.Name #>> <#= relation.TargetRoleName #>

to

public virtual ObservableCollection<<#= relation.Target.Name #>>                           

               <#= relation.TargetRoleName #>                                              

 

Override ToString() methods

To be able to display persistent object nicely in the user interface, we’re going to override the ToString() methods for each generated code class. Add a new C# code file to the project and insert the following lines of code:

 

partial class Bank
{
   public override string ToString()
    {
        return this.Name;
    }
}

partial class Customer
{
    public override string ToString()
    {
        return this.LastName + ", " + this.FirstName;
    }
}

partial class Account
{
    public override string ToString()
    {
       return this.AccountNo;
    }
}

partial class CashCard
{
    public override string ToString()
    {
        return this.Id + " (exp: " + this.Expiration.ToString("MM/yyyy") + ")";
    }
}

 

Preparation for the T4 templates

For creating the user interface, we need two T4 templates. One to create the XAML markup for the WPF window and the second one to create the CodeBehind class. Additionally we need to adapt the App.xaml to use the generated WPF window as the default window.

 

Template for WPF markup

Create a new blank T4 Template in your solution and name it “TaslToWPF.tt”. Use the same header directives as in the T4 template that generated the EntityFramework code classes so that the T4 template can access the .tasl model, but change the output extension to “.xaml”.

 

<#@ template

        inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" 
        debug="true" 
        hostSpecific="true" #>
<#@ output extension=".xaml" #>

<#@ TangiblePersistentObjectDiagram

        processor="TangiblePersistentObjectDiagramDirectiveProcessor" 
        requires="fileName='BlogSeries.tasl'" 
        provides="PersistentObjectModel" #>
<#@ assembly name="tasl.Dsl.10.dll" #>
<#@ import namespace="tasl.Dsl" #>
<#@ assembly name="System.Core.dll" #>

<#@ import namespace="System" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>

 

Template for CodeBehind

Create another blank T4 Template named “TaslToWPFCodeBehind.tt” with the same header directives as shown above. Change the output extension to “.cs”.

 

Adapt App.xaml

Since the “TaslToWPF.tt” Template will generate a “TaslToWPF.xaml” file containing the main window of our WPF application, we need to change the App.xaml so that it points to this window:

 

<Application x:Class="BlogSeriesApp.App"
             xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="TaslToWPF.xaml">
    <Application.Resources>
       
    </Application.Resources>
</Application>

 

Fill the T4 templates

In order to generate the full functionality for the basic user interface, this section is split into several parts: At first we’re just going to display any data from the database in a lists for each Persistent Object object inside a TabControl. Then we’re going to deal with adding, deleting and changing data and save changes to the database. After that we’re going to create a detail view for the selected item in a list and display navigation properties.

Display Data in Grids

At the very beginning, we’re going to create the scaffold of the CodeBehind class for the XAML window. Then we’ll write the code for rendering the XAML markup.

 

CodeBehind scaffold (TaslToWPFCodeBehind.tt)

We need a C# code class representing the main window of the basic user interface. When loading that window we’re going to create a new instance of the Context class that grants us access to the database, pass the proper connection information and set this context as the DataContext of the window.

Put the following code snippet after the template directives of the TaslToWPFCodeBehind.tt file:

 

<#  List<PersistentObjectBase> allPersistentObjects =

           this.PersistentObjectModel.PersistentObjects.ToList<PersistentObjectBase>();
    allPersistentObjects.Insert(0, this.PersistentObjectModel.PersistentRootObject);
#>

using System;                                                                              
using System.Windows;                                                                      
using System.Windows.Controls;                                                             
using System.Data.Entity;                                                                  
using System.Linq;                                                                         

namespace BlogSeriesApp                                                                    
{                                                                                          
   
/// <summary>                                                                          
    /// Interaction logic for MainWindow.xaml                                              
    /// </summary>                                                                         
    public partial class MainWindow : Window                                               
    {                                                                                      
        public MainWindow()                                                                
        {                                                                                  
            InitializeComponent();                                                         

            this.Loaded += MainWindow_Loaded;                                              

        }                                                                                  

                                                                                           

        private Context _DataContext;                                                      

        void MainWindow_Loaded(object sender, RoutedEventArgs e)                           
        {                                                                                  
            _DataContext = new Context(                                                    

       @"Server=(local)\SQLEXPRESS;Database=BankPersistenceModel;Trusted_Connection=True;");

<#  foreach(var persistentObject in allPersistentObjects)
    {
#>            _DataContext.<#= persistentObject.Name #>.Load();                            
<#  }
#>          this.DataContext = _DataContext;                                               
        }                                                                                  

    }                                                                                      

}                                                                                          

 

Generate lists for displaying current database data (TaslToXAML.tt)

The following code snippet creates the XAML scaffold for the main window. Inside the window we’re going to place a TabControl containing a Tab for each Persistent Object in our model. That already might suffice, but for aesthetics we’re going to display only the scalar properties inside the grids.

 

<#  List<PersistentObjectBase> allPersistentObjects =

         this.PersistentObjectModel.PersistentObjects.ToList<PersistentObjectBase>();
    allPersistentObjects.Insert(0, this.PersistentObjectModel.PersistentRootObject);
#>

<Window x:Class="BlogSeriesApp.MainWindow"                                                 
        xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"                  
        xmlns:x=
http://schemas.microsoft.com/winfx/2006/xaml                               
        Title="MainWindow" Height="600" Width="800">                                       
    <Window.Resources>                                                                     

    </Window.Resources>                                                                    

    <TabControl x:Name="tab">                                                              

<#  // create a tab for each persisted object
    foreach(var persistentObject in allPersistentObjects)
    {
#>      <TabItem Header="<#= persistentObject.Name #>">                                    
            <Grid>                                                                         
                <Grid.ColumnDefinitions>                                                   
                    <ColumnDefinition Width="250" />                                       
                    <ColumnDefinition Width="5" />                                         
                    <ColumnDefinition Width="*" />                                         
                </Grid.ColumnDefinitions>                                                  

                <!-- This DataGrid will contain all <#= persistentObject.Name #> objects -->
                <DataGrid x:Name="grd
<#= persistentObject.Name #>"                         
                          ItemsSource="{Binding <#= persistentObject.Name #>.Local}"       
                          AutoGenerateColumns
="False">                                     
                    <DataGrid.Columns>                                                      
<#      // create a column foreach scalar property in the persistent object
        foreach(var property in persistentObject.Properties)
        {
#>                        <DataGridTextColumn IsReadOnly="<#= property.GetExtensiblePropertyValue("Identifier").ToString() #>"                           
                                            Header="
<#= property.Name #>"                  
                                            Binding="{Binding
<#= property.Name #>}" />    
<#      }
#>                  </DataGrid.Columns>                                                     
                </DataGrid>                                                                

                <!-- Detail Data here -->                                                  

            </Grid>                                                                        

        </TabItem>                                                                         

<#  }

#>  </TabControl>                                                                          

</Window>                                                                                  

 

You might want to run the application and have a look at the objects that are already stored inside the database.

image

 

Edit Objects and Save Changes

Fortunately the DataGrid element of the Windows Presentation Foundation already provides the user with the possibility to change properties of an object in its list. Adding a new object to the list is also available: The user only needs to enter information into the last (and yet empty) line. Even deleting is already possible. Select a row and hit the “Entf” button on the keyboard.

 

The only thing we need to implement at this point is to save that changes. Because the Persistent Objects are bound to the DataGrids all changes are already propagated to those objects.

We’re going to save changes when the user closes the window. So go to the “TaslToWPFCodeBehind.tt” and insert a listener to the “Closing” Event and hook it up in the constructor.

 

Add to the constructor:

            this.Closing += MainWindow_Closing;                                            

 

Add after the constructor:

    void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)         
        {                                                                                  
            _DataContext.ChangeTracker.DetectChanges();                                     
            if (_DataContext.ChangeTracker.Entries().Count() > 0)                           
            {                                                                               
                if (MessageBox.Show("Save changes?", "Question", MessageBoxButton.YesNo)   

                   == MessageBoxResult.Yes)                                                 
                {                                                                           
                    try                                                                     
                    {                                                                       
                        _DataContext.SaveChanges();                                         
                    }                                                                       
                    catch (Exception ex)                                                    
                    {                                                                       
                        MessageBox.Show("Error saving changes: " + ex.Message);             
                        e.Cancel = true;                                                    
                    }                                                                      
                }                                                                           
            }                                                                               
        }                                                                                  

 

Detail Data

Now that we have a list for each Persistent Object in our domain specific model, let’s put a detailed view next to that displays all scalar properties of the selected item in the list. Later we’re going to add navigational properties to this detail view. Add the following part to the TaslToWPF.tt where it says “Detail Data here”:

 

<!-- This border contains the details-view for <#= persistentObject.Name #> objects  -->   
                <Border BorderBrush="DarkGray" Grid.Column="2" Margin="5">                 
                    <Grid DataContext="{Binding ElementName=grd
<#= persistentObject.Name #>,

                                                Path=SelectedItem}">                       
                        <Grid.ColumnDefinitions>                                            
                            <ColumnDefinition Width
="Auto" />                              
                            <ColumnDefinition Width
="5" />                                  
                            <ColumnDefinition Width="*" />                                  
                            <ColumnDefinition Width
="5" />                                  
                            <ColumnDefinition Width
="25" />                                 
                        </Grid.ColumnDefinitions>                                          
                        <Grid.RowDefinitions>                                              
<#      // create a row definition (and a spacer) for each
        // - scalar property
        // - navigation property where this item is target
        // - navigation property where this item is source
        int propertyCount = persistentObject.Properties.Count;
        int targetRelationCount = persistentObject.Targets.TargetDomainRole.GetElementLinks(persistentObject).Count;
        int sourceRelationCount = persistentObject.Sources.TargetDomainRole.GetElementLinks(persistentObject).Count;

        for (int i = 0; i < propertyCount + targetRelationCount + sourceRelationCount; i++)
        {
#>                          <RowDefinition Height="Auto" />                                 
                            <RowDefinition Height
="5" />                                   
<#      }
#>                      </Grid.RowDefinitions>                                             

<#      // iterate all scalar properties
        // (not in a foreach loop, because this way we can easier decide in

        // which row the ui elements go)
        for(int i = 0; i < propertyCount; i++)
        {
            var property = persistentObject.Properties.ElementAt(i);
#>                      <TextBlock Text="<#= property.Name #>:"                            
                                   Grid.Row="
<#= (2 * i).ToString() #>"                     
                                   Grid.Column="0"                                       
  
                                   Margin
="0,3,5,0"                                         
                                   VerticalAlignment
="Top" />                               
                        <TextBox Text="{Binding
<#= property.Name #>}"                      
                                 Grid.Row="
<#= (2 * i).ToString() #>"                      
                                 Grid.Column
="2" />                                        
<#    }

      // Navigation Properties here

#>    </Grid>                                                                              

  </Border>                                                                                

 

This is the result so far including the Detail view:

image

 

 

Navigation Properties

Now that we can display and edit the scalar properties of a selected object, we also want to manipulate navigation properties and the related objects. For example, if we’re displaying a bank, we want to see all customers of this bank and even want to add new customers to that bank or remove some of them.

 

Again this can be done inside the XAML part. Like we did in the T4 Template that generated the C# code classes, we need to take care of relations where the current object is either the source or the target of the relation. So add the following code where the “TaslToWPF.tt” says “Navigation Properties here.”

 

 

        // iterate all navigation properties where this item is target
        var targetRelations = persistentObject.Targets.TargetDomainRole

                                                      .GetElementLinks(persistentObject);
        for(int i = propertyCount; i < propertyCount + targetRelationCount; i++)
        { 
            var relation = targetRelations.ElementAt(i - propertyCount)

                           as tasl.Dsl.PersistentRelation;
            if (relation.Target.Name == persistentObject.Name)
            {
#>                        <TextBlock Text="<#= relation.SourceRoleName #>:"                
                                   Grid.Row="
<#= (2 * i).ToString() #>"                    
                                   Grid.Column="0"                                          
                                   Margin="0,3,5,0"                                         
                                   VerticalAlignment="Top" />                              
<#              // if its a scalar property only display its value
                // otherwise create a grid showing all child objects

                // participating in this relation
                if (relation.SourceMultiplicity == tasl.Dsl.Multiplicity.ZeroMany)
                {
#>                      <DataGrid x:Name="grd<#= relation.Source.Name #>In<#= relation.Target.Name #>As<#= relation.SourceRoleName #>"                                   
                                  Grid.Row="
<#= (2 * i).ToString() #>"                     
                                  Grid.Column="2"                                          
                                  AutoGenerateColumns="False"                               
                                  Height="150"                                              
                                  ItemsSource="{Binding
<#= relation.SourceRoleName #>}">  
                            <DataGrid.Columns>                                             
<#                  foreach(var property in relation.Source.Properties)
                    {
#>                              <DataGridTextColumn IsReadOnly="<#= property.GetExtensiblePropertyValue("Identifier").ToString() #>"                           
                                                    Header="
<#= property.Name #>"          
                                                    Binding="{Binding
<#= property.Name #>}" />                                                                                    
<#                  }
#>                            </DataGrid.Columns>                                           
                        </DataGrid>                                                         
<#              }
                else
                {
#>                      <TextBlock Text="{Binding <#= relation.SourceRoleName #>}"         
                                   Grid.Row="
<#= (2 * i).ToString() #>"                     
                                   Grid.Column="2" />                                      

<#              }
            }
        }

        // iterate all navigation properties where this item is source
        var sourceRelations = persistentObject.Sources.TargetDomainRole

                                                      .GetElementLinks(persistentObject);
        for(int i = propertyCount + targetRelationCount; i < propertyCount + targetRelationCount + sourceRelationCount; i++)
        { 
            var relation = sourceRelations.ElementAt(i - propertyCount - targetRelationCount) as tasl.Dsl.PersistentRelation;
            if (relation.Source.Name == persistentObject.Name)
            {
#>                      <TextBlock Text="<#= relation.TargetRoleName #>:"                   
                                   Grid.Row="
<#= (2 * i).ToString() #>"                    
                                   Grid.Column="0"                                          
                                   Margin="0,3,5,0"                                         
                                   VerticalAlignment="Top" />                               
<#              // if its a scalar property only display its value
                // otherwise create a grid showing all child objects

                // participating in this relation
                if (relation.TargetMultiplicity == tasl.Dsl.Multiplicity.ZeroMany)
                {
#>                      <DataGrid x:Name="grd<#= relation.Target.Name #>In<#= relation.Source.Name #>As<#= relation.TargetRoleName #>"                                   
                                  Grid.Row="
<#= (2 * i).ToString() #>"                     
                                  Grid.Column="2"                                           
                                  AutoGenerateColumns="False"                               
                                  Height="150"                                             
                                  ItemsSource="{Binding
<#= relation.TargetRoleName #>}">   
                            <DataGrid.Columns>                                              

<#                  foreach(var property in relation.Target.Properties)
                    {
#>                              <DataGridTextColumn IsReadOnly="<#= property.GetExtensiblePropertyValue("Identifier").ToString() #>"                           
                                                    Header="
<#= property.Name #>"          
                                                    Binding="{Binding
<#= property.Name #>}" />                                                                                     
<#                  }
#>                            </DataGrid.Columns>                                          
                        </DataGrid>                                                        

<#              }
                else
                {
#>                      <TextBlock Text="{Binding <#= relation.TargetRoleName #>}"         
                                   Grid.Row="
<#= (2 * i).ToString() #>"                     
                                   Grid.Column="2" />                                       
<#              }
            }

 

 

Summary

In this part of the blog series we created two T4 templates that generate a WPF window and a matching code behind class based on a domain specific Persistent Object Model. We used these templates to generate a basic user interface for the .tasl Model we created in Part 2 of this series. The user interface uses also the Entity Framework code classes generated in Part 3 to display data from a database persistence layer introduced in Part 4 of this blog series.

 

Requirements

If you have read this blog series and are curios to get your hands on parts it, here is what you need:

Twitter Updates