Wednesday, March 27, 2013

Windows 8 ChordFactory - latest screenshots

Getting better and adding features, like notation and settings persistence.



Wednesday, December 12, 2012

ChordFactory on Windows 8

Coming soon - once I get the hang of async data loading and all the visual state support required


Not exactly the height of Metro design excellence either.

Monday, November 26, 2012

LINQ Outer Joins

I found out today how to do outer joins in LINQ and thought I’d save it as an Aide Memoire post.

Given a typical Foreign-Key relationship:

class Preference
{
    public int Id { get; set; }
    public string Description { get; set; }
}

class Person
{
    public int Id { get; set; }
    public string Forename { get; set; }
    public string Surname { get; set; }
    public int? PreferenceId { get; set; }
}

 

And this sample data:

    var peopleList = new List<Person>();
    peopleList.Add(new Person {Id = 77, Forename = "Fred", Surname = "Flintstone", PreferenceId = 2 });
    peopleList.Add(new Person {Id = 154, Forename = "Barney", Surname = "Rubble", PreferenceId = 1 });
    peopleList.Add(new Person {Id = 308, Forename = "Wilma", Surname = "Flintstone" });
    peopleList.Add(new Person {Id = 462, Forename = "Betty", Surname = "Rubble", PreferenceId = 1 });
    peopleList.Add(new Person {Id = 616, Forename = "Bam Bam", Surname = "Rubble", PreferenceId = 4 });
    peopleList.Add(new Person {Id = 770, Forename = "Pebbles", Surname = "Flintstone" });

    var preferenceList = new List<Preference>();
    preferenceList.Add(new Preference {Id = 1, Description = "Coffee"});
    preferenceList.Add(new Preference {Id = 2, Description = "Tea"});
    preferenceList.Add(new Preference {Id = 3, Description = "Hot Chocolate"});
    preferenceList.Add(new Preference {Id = 4, Description = "Fruit Juice"});

 

The obvious LINQ query:

    var peopleWithPreferences = from p in peopleList
                            join pr in preferenceList
                            on p.PreferenceId equals pr.Id
                            select new  {p.Forename, p.Surname, pr.Description};

 

Produces results that contain only the inner joined pairs of people and their beverage preference:

Forename  Surname     Description
Fred      Flintstone  Tea
Barney    Rubble      Coffee
Betty     Rubble      Coffee
Bam Bam   Rubble      Fruit Juice
 

In order to retrieve the full list of people with their beverage preference if they have one, the LINQ query becomes:

    var peopleAnyPreferences = from p in peopleList
                            join pr in preferenceList
                            on p.PreferenceId equals pr.Id into joinedPreferences
                            from j in joinedPreferences.DefaultIfEmpty()
                            select new  {p.Forename, p.Surname, pref = j != null ? j.Description : string.Empty};

 

Forename  Surname     Description
Fred      Flintstone  Tea
Barney    Rubble      Coffee
Wilma     Flintstone
Betty     Rubble      Coffee
Bam Bam   Rubble      Fruit Juice
Pebbles   Flintstone

 

The differences being the intermediate results joinedPreferences and its use with the DefaultIfEmpty extension, with a null check for the nullable column.

Friday, March 23, 2012

System.Collections.Generic.List<T> ForEach method is missing in Windows 8 Runtime

It seems that the handy ForEach method has been removed from the List of T class in the WinRT. According to Wes Haggard of the .NET Framework Bas Class Library (BCL) Team:

“List<T>.ForEach has been removed in Metro style apps. While the method seems simple it has a number of potential problems when the list gets mutated by the method passed to ForEach. Instead it is recommended that you simply use a foreach loop.”

Which is a shame, as I use this method quite a bit to invoke method group calls to do one-time iterations over Lists, like this:

keywordResults.ForEach(this.searchActivity.Keywords.Add);


For situations like this, the mutation issue doesn’t arise and it would be safe to use the ForEach method if it existed. So I did an extension method implementation of the functionality by taking a look at the decompiled code of the method in earlier versions of the .NET Framework:

public void ForEach(Action<T> action)
{
if (action == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
for (int index = 0; index < this._size; ++index)
action(this._items[index]);
}



Using this as a starting point it was fairly easy to come up with an extension method version:

public static void ForEach<T>(this IEnumerable<T> list, Action<T> action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}

foreach (var t in list)
{
action(t);
}
}



Now I can use the extension to give me the same ForEach functionality on IEnumerable objects such as List<T> as I had before.

Thursday, April 21, 2011

Five Minute Silverlight 5 Aides-Memoire #4 – Mouse Click Count

Rainbow Rectangle
  1. <Rectangle Width="154"
  2.            Height="77"
  3.            MouseLeftButtonDown="RectangleMouseLeftButtonDown">
  4.     <Rectangle.Fill>
  5.         <LinearGradientBrush x:Name="GradientFill" StartPoint="0,0" EndPoint="1,1">
  6.             <GradientStop Offset="0" Color="Red" />
  7.             <GradientStop Offset="0.1667" Color="Orange" />
  8.             <GradientStop Offset="0.334" Color="Yellow" />
  9.             <GradientStop Offset="0.5001" Color="Green" />
  10.             <GradientStop Offset="0.6668" Color="Blue" />
  11.             <GradientStop Offset="0.8335" Color="Indigo" />
  12.             <GradientStop Offset="1" Color="Violet" />
  13.         </LinearGradientBrush>
  14.     </Rectangle.Fill>
  15. </Rectangle>
  16. <TextBlock x:Name="ClickCountTextBlock"
  17.            Foreground="White"
  18.            HorizontalAlignment="Center"
  19.            VerticalAlignment="Center"
  20.            FontSize="40"
  21.            Opacity="0.4"
  22.            Text="" />

Click Count in MouseEventArgs
  1. private void RectangleMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
  2. {
  3.     this.ClickCountTextBlock.Text = e.ClickCount.ToString();
  4.     var topLeft = new Point(0, 0);
  5.     var topRight = new Point(1, 0);
  6.     var bottomLeft = new Point(0, 1);
  7.     var bottomRight = new Point(1, 1);
  8.  
  9.     switch (e.ClickCount)
  10.     {
  11.         case 2:
  12.             this.GradientFill.StartPoint = topRight;
  13.             this.GradientFill.EndPoint = bottomLeft;
  14.             break;
  15.         case 3:
  16.             this.GradientFill.StartPoint = bottomRight;
  17.             this.GradientFill.EndPoint = topLeft;
  18.             break;
  19.         case 4:
  20.             this.GradientFill.StartPoint = bottomLeft;
  21.             this.GradientFill.EndPoint = topRight;
  22.             break;
  23.         default:
  24.             this.GradientFill.StartPoint = topLeft;
  25.             this.GradientFill.EndPoint = bottomRight;
  26.             break;
  27.     }
  28. }

DoubleTripleClick

Five Minute Silverlight 5 Aides-Memoire #3 – Out-of-Browser Native Windows

Native Window creation in code
  1. private void ButtonClick(object sender, RoutedEventArgs e)
  2. {
  3.     if (!Application.Current.IsRunningOutOfBrowser)
  4.     {
  5.         return;
  6.     }
  7.  
  8.     var newGrid = new Grid
  9.     {
  10.         Background = new SolidColorBrush(Colors.White),
  11.         HorizontalAlignment = HorizontalAlignment.Stretch,
  12.         VerticalAlignment = VerticalAlignment.Stretch
  13.     };
  14.  
  15.     var newTextBlock = new TextBlock
  16.     {
  17.         HorizontalAlignment = HorizontalAlignment.Center,
  18.         VerticalAlignment = VerticalAlignment.Center,
  19.         Text = "New TextBlock in a new Window..."
  20.     };
  21.  
  22.     newGrid.Children.Add(newTextBlock);
  23.  
  24.     new Window { Content = newGrid, Visibility = Visibility.Visible, Width = 300, Height = 300 };
  25. }

NativeWindows

Wednesday, April 20, 2011

Five Minute Silverlight 5 Aides-Memoire #2 – Implicit DataTemplates

Data Classes
  1. /// <summary>
  2. /// The cartoon character.
  3. /// </summary>
  4. public class CartoonCharacter
  5. {
  6.     /// <summary>
  7.     /// Gets or sets the forename.
  8.     /// </summary>
  9.     /// <value>The forename.</value>
  10.     public string Forename { get; set; }
  11. }
  12.  
  13. /// <summary>
  14. /// The flintstone.
  15. /// </summary>
  16. public class Flintstone : CartoonCharacter { }
  17.  
  18. /// <summary>
  19. /// The griffin.
  20. /// </summary>
  21. public class Griffin : CartoonCharacter { }
  22.  
  23. /// <summary>
  24. /// The simpson.
  25. /// </summary>
  26. public class Simpson : CartoonCharacter { }

 

XAML
  1. <Grid x:Name="LayoutRoot"
  2.       Background="White">
  3.     <Grid.Resources>
  4.         <Style x:Key="SimpsonStyle"
  5.                TargetType="TextBlock">
  6.             <Setter Property="FontFamily" Value="Comic Sans MS" />
  7.             <Setter Property="FontSize" Value="16" />
  8.             <Setter Property="Foreground" Value="Blue" />
  9.             <Setter Property="FontStyle" Value="Italic" />
  10.         </Style>
  11.         <DataTemplate DataType="this:Simpson">
  12.             <Border Background="Yellow">
  13.                 <StackPanel Orientation="Horizontal">
  14.                     <TextBlock Style="{StaticResource SimpsonStyle}"
  15.                                Text="{Binding Forename}" />
  16.                     <TextBlock Margin="5,0,0,0"
  17.                                Style="{StaticResource SimpsonStyle}"
  18.                                Text="Simpson" />
  19.                 </StackPanel>
  20.             </Border>
  21.         </DataTemplate>
  22.         <DataTemplate DataType="this:Flintstone">
  23.             <Border Background="Orange">
  24.                 <Border.RenderTransform>
  25.                     <TransformGroup>
  26.                         <SkewTransform AngleX="-20" />
  27.                         <ScaleTransform ScaleX="2"
  28.                                         ScaleY="0.75" />
  29.                         <TranslateTransform X="30" />
  30.                     </TransformGroup>
  31.                 </Border.RenderTransform>
  32.                 <StackPanel Orientation="Horizontal">
  33.                     <TextBlock Text="{Binding Forename}" />
  34.                     <TextBlock Margin="5,0,0,0"
  35.                                Text="Flintstone" />
  36.                 </StackPanel>
  37.             </Border>
  38.         </DataTemplate>
  39.         <DataTemplate DataType="this:Griffin">
  40.             <Border BorderBrush="DarkSlateBlue" BorderThickness="2">
  41.                 <StackPanel Orientation="Horizontal">
  42.                     <TextBlock Text="{Binding Forename}" FontSize="18">
  43.                         <TextBlock.Effect>
  44.                             <BlurEffect />
  45.                         </TextBlock.Effect>
  46.                     </TextBlock>
  47.                     <TextBlock Margin="5,0,0,0"
  48.                                FontSize="22" CharacterSpacing="5"
  49.                                Text="Griffin">
  50.                         <TextBlock.Effect>
  51.                             <DropShadowEffect />
  52.                         </TextBlock.Effect>
  53.                     </TextBlock>
  54.                 </StackPanel>
  55.             </Border>
  56.         </DataTemplate>
  57.     </Grid.Resources>
  58.     <ListBox x:Name="CartoonCharactersListBox"
  59.              Margin="50,20"
  60.              BorderBrush="Blue"
  61.              BorderThickness="2"
  62.              ItemsSource="{Binding}" />
  63. </Grid>

 

SL5AM2

Yeugh!!

Five Minute Silverlight 5 Aides-Memoire #1 – Style Setter Binding

Code Snippet
  1. <Grid x:Name="LayoutRoot"
  2.       Background="White">
  3.     <Grid.RowDefinitions>
  4.         <RowDefinition />
  5.         <RowDefinition Height="Auto" />
  6.     </Grid.RowDefinitions>
  7.     <Grid.Resources>
  8.         <Style x:Key="VariableFontTextBlockStyle"
  9.                TargetType="TextBlock">
  10.             <Setter Property="Foreground" Value="DarkOrange" />
  11.             <Setter Property="FontWeight" Value="Bold" />
  12.             <Setter Property="FontSize" Value="{Binding ElementName=FontSizeSlider, Path=Value}" />
  13.         </Style>
  14.     </Grid.Resources>
  15.     <TextBlock x:Name="SampleTextBlock"
  16.                Grid.Row="0"
  17.                HorizontalAlignment="Center"
  18.                VerticalAlignment="Center"
  19.                Style="{StaticResource VariableFontTextBlockStyle}"
  20.                Text="No Mr Bond - I expect you to die" />
  21.     <Slider x:Name="FontSizeSlider"
  22.             Grid.Row="1"
  23.             MaxWidth="500"
  24.             Margin="100,30"
  25.             Maximum="64"
  26.             Minimum="8"
  27.             Value="12" />
  28. </Grid>

SL5AM1

Friday, April 15, 2011

Building Silverlight Chord Factory: Part 3 – The View and Windows Phone 7

Despite my good intentions, it’s been a year since I posted the second part of my set of posts on Building Silverlight Chord Factory (see Part 1 and Part 2). The third part – on the View in the Model-View-ViewModel (MVVM) triptych has been a bit delayed, mainly by work and other pressures, but also by my attention being diverted by the arrival of Windows Phone 7 (WP7).

My practice of exploring new development platforms and languages by converting my hobby project – the Openfeature Chord Factory – kicked in and the application got yet another transformation. Which worked out quite well as it illustrated some of the power and benefits of the MVVM pattern. Converting the Silverlight Chord Factory code to WP7 Silverlight code was pretty simple and the main area of change – the View – was nicely isolated from the remaining code allowing the XAML markup to be adapted to the WP7 Control Toolkit easily and the custom controls in the app to work with virtually no modifications for the new platform.

So now this belated post can be both a look at the original View and at the adaptions involved in moving the app to the WP7 platform.

Here are a few screenshots of the Silverlight application in a couple of incarnations, running in a browser:

SilverlightChordFactory

Note the unfinished musical stave controls…

OpenfeatureChordFactory

Screenshots of Silverlight ChordFactory application

And here are screenshots of the Wp7 application:

WP7ChordFactory

WP7ChordFactorySettings

sWP7ChordFactoryOptions

Screenshots of WP7 app

Leaving aside my relative lack of UI/UX design capability, you can see that basically the User Experience is the same for both versions of the ChordFactory app. And in some of the UI itself the styling is exactly the same – the keyboard keys for example.

In fact I was able to reuse large chunks of the Openfeature Silverlight music controls (Keyboard, Octave and PianoKey controls) that I developed for the browser app because they consist of generic, compatible XAML markup and C# code. The Views in either case are of course nothing more than the markup/styling with bindings to the appropriate data sources in the ViewModel.  With the Model and ViewModel classes being completely platform independent, they needed no redevelopment at all.

So I was able to get the WP7 ChordFactory app up and running in not much more than an easy afternoon of working on it. The majority of the work required lay in adding XNA code to play piano note samples added to the app so that the user can hear the selected chord or scale. Something I couldn’t do as easily with the browser version and hadn’t got around to implementing with MediaElements. Registering with the App Hub and submitting the app took a few trips back and forth to cross all the ‘Ts’ and dot all the ‘Is’, but the app is now live here:

ZuneDownload

Openfeature ChordFactory

 

Tuesday, April 13, 2010

Building Silverlight Chord Factory: Part 2 – The ViewModel

This is part 2 in my series of posts on building the latest MVVM incarnation of my ChordFactory hobby project; in Part 1 I discussed modelling musical chord and scale data in XML – the Model in the MVVM (Model-View-ViewModel) pattern. In this post I will look at the next part of the pattern – the ViewModel.

Loading the data

In the MVVM pattern, data is held in properties of the ViewModel to allow the Views to data-bind UI elements directly onto those properties. In the ChordFactory application, the mechanics of loading the data from the XML and building the Chords and Scales collections to supply the View-Model are implemented using the Repository pattern with individual repository classes, deriving from a repository base class, for the Chords and the Scales collections. These classes implement private static methods to load their respective data and then surface the collections retrieved via public methods that return ObservableCollections of Chords and Scales respectively.

REPOSITORY BASE CLASS
  1. public class RepositoryBase
  2. {
  3.     protected static Stream GetResourceStream(string resourceFile)
  4.     {
  5.         Uri uri = new Uri(resourceFile, UriKind.RelativeOrAbsolute);
  6.  
  7.         StreamResourceInfo info = Application.GetResourceStream(uri);
  8.         if (info == null || info.Stream == null)
  9.             throw new ArgumentException("Missing resource file: " + resourceFile);
  10.  
  11.         return info.Stream;
  12.     }
  13. }

CHORD REPOSITORY
  1. public class ChordRepository : RepositoryBase
  2. {
  3.     private readonly ObservableCollection<Chord> observableChords = new ObservableCollection<Chord>();
  4.  
  5.     public ChordRepository(string chordDataFile)
  6.     {
  7.         LoadChords(chordDataFile).ForEach(observableChords.Add);
  8.     }
  9.  
  10.     private static List<Chord> LoadChords(string chordDataFile)
  11.     {
  12.         using (Stream stream = GetResourceStream(chordDataFile))
  13.         using (XmlReader xmlRdr = XmlReader.Create(stream))
  14.             return (from chordElem in XDocument.Load(xmlRdr).Element("Chords").Elements("Chord")
  15.                     select
  16.                         Chord.CreateChord((string) chordElem.Element("Description"),
  17.                                           chordElem.Element("NoteList").Elements("NoteIndex").Select(
  18.                                               x => int.Parse(x.Value)).ToList())).ToList();
  19.     }
  20.  
  21.     public ObservableCollection<Chord> GetChords()
  22.     {
  23.         return observableChords;
  24.     }
  25. }

SCALE REPOSITORY
  1. public class ScaleRepository : RepositoryBase
  2. {
  3.     private readonly ObservableCollection<Scale> observableScales = new ObservableCollection<Scale>();
  4.  
  5.     public ScaleRepository(string scaleDataFile)
  6.     {
  7.         LoadScales(scaleDataFile).ForEach(observableScales.Add);
  8.     }
  9.  
  10.     private static List<Scale> LoadScales(string scaleDataFile)
  11.     {
  12.         using (Stream stream = GetResourceStream(scaleDataFile))
  13.         using (XmlReader xmlRdr = XmlReader.Create(stream))
  14.             return (from ScaleElem in XDocument.Load(xmlRdr).Element("Scales").Elements("Scale")
  15.                     select
  16.                         Scale.CreateScale((string)ScaleElem.Element("Description"),
  17.                                           ScaleElem.Element("NoteList").Elements("NoteIndex").Select(
  18.                                               x => int.Parse(x.Value)).ToList())).ToList();
  19.     }
  20.  
  21.     public ObservableCollection<Scale> GetScales()
  22.     {
  23.         return observableScales;
  24.     }
  25. }

Data in the ViewModel

The ViewModel uses the Repositories to load the data and provides it as bindable collections together with other bindable properties such as the currently selected items in the collections and implementation of change notification so that bound UI can respond to updates in the ViewModel.

CHORDS VIEW-MODEL
  1. public class ChordsViewModel : INotifyPropertyChanged
  2. {
  3.     private readonly ObservableCollection<Chord> chords;
  4.     private readonly ObservableCollection<Scale> scales;
  5.     private List<int> selectedChord;
  6.     private List<int> selectedScale;
  7.     private RootNotes rootNote;
  8.     private Inversion inversion;
  9.  
  10.     public event PropertyChangedEventHandler PropertyChanged;
  11.  
  12.     private readonly List<Inversion> inversions = new List<Inversion>
  13.                                                       {
  14.                                                           Inversion.Basic,
  15.                                                           Inversion.First,
  16.                                                           Inversion.Second,
  17.                                                           Inversion.Third,
  18.                                                           Inversion.Fouth
  19.                                                       };
  20.  
  21.     
  22.     public ChordsViewModel()
  23.     {
  24.         chords = new ChordRepository("/Openfeature.ChordFactory;component/Data/chords.xml").GetChords();
  25.         scales = new ScaleRepository("/Openfeature.ChordFactory;component/Data/scales.xml").GetScales();
  26.     }
  27.  
  28.     public ObservableCollection<Chord> Chords
  29.     {
  30.         get { return chords; }
  31.     }
  32.  
  33.     public ObservableCollection<Scale> Scales
  34.     {
  35.         get { return scales; }
  36.     }
  37.  
  38.     public List<Inversion> Inversions { get { return inversions; } }
  39.  
  40.     public List<int> SelectedChord
  41.     {
  42.         get { return selectedChord; }
  43.         private set
  44.         {
  45.             selectedChord = value;
  46.             OnPropertyChanged("SelectedChord");
  47.         }
  48.     }
  49.  
  50.     public List<int> SelectedScale
  51.     {
  52.         get { return selectedScale; }
  53.         private set
  54.         {
  55.             selectedScale = value;
  56.             OnPropertyChanged("SelectedScale");
  57.         }
  58.     }
  59.  
  60.     public Inversion Inversion
  61.     {
  62.         get { return inversion; }
  63.         set
  64.         {
  65.             inversion = value;
  66.             OnPropertyChanged("Inversion");
  67.         }
  68.     }
  69.  
  70.     public RootNotes RootNote
  71.     {
  72.         get { return rootNote; }
  73.         set
  74.         {
  75.             rootNote = value;
  76.             OnPropertyChanged("RootNote");
  77.         }
  78.     }
  79.  
  80.     public void ChordSelectionChanged(object sender, SelectionChangedEventArgs e)
  81.     {
  82.         SelectedChord = ((Chord)e.AddedItems[0]).Notes;
  83.     }
  84.  
  85.     public void InversionSelectionChanged(object sender, SelectionChangedEventArgs e)
  86.     {
  87.         Inversion = (Inversion)e.AddedItems[0];
  88.     }
  89.  
  90.     public void RootNoteChanged(object sender, SelectionChangedEventArgs e)
  91.     {
  92.         RootNote = (RootNotes)e.AddedItems[0];
  93.     }
  94.  
  95.     public void ScaleSelectionChanged(object sender, SelectionChangedEventArgs e)
  96.     {
  97.         SelectedScale = ((Scale)e.AddedItems[0]).Notes;
  98.     }
  99.  
  100.     private void OnPropertyChanged(string propertyName)
  101.     {
  102.         if (PropertyChanged != null)
  103.         {
  104.             PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  105.         }
  106.     }
  107. }

The Chords and Scales collection properties of the ViewModel are bound to UI elements, (initially ComboBoxes – UI/UX enhancement will have to come later), and their SelectionChanged events wired back to the ViewModel using the CallDataMethod behaviour from the excellent Expression Blend Samples on Codeplex:

COLLECTION PROPERTIES BINDINGS
  1. <StackPanel x:Name="BoundData" Orientation="Horizontal" Margin="10,10,10,20" >
  2.     <ComboBox x:Name="ChordsList" ItemsSource="{Binding Chords}" DisplayMemberPath="Description" Margin="10,0">
  3.         <i:Interaction.Triggers>
  4.             <i:EventTrigger EventName="SelectionChanged">
  5.                 <si:CallDataMethod Method="ChordSelectionChanged" />
  6.             </i:EventTrigger>
  7.         </i:Interaction.Triggers>
  8.     </ComboBox>
  9.     <ComboBox x:Name="ScalesList" ItemsSource="{Binding Scales}" DisplayMemberPath="Description" Margin="10,0">
  10.         <i:Interaction.Triggers>
  11.             <i:EventTrigger EventName="SelectionChanged">
  12.                 <si:CallDataMethod Method="ScaleSelectionChanged" />
  13.             </i:EventTrigger>
  14.         </i:Interaction.Triggers>
  15.     </ComboBox>
  16.     <ComboBox x:Name="InversionsList" Margin="10,0" ItemsSource="{Binding Inversions}">
  17.         <i:Interaction.Triggers>
  18.             <i:EventTrigger EventName="SelectionChanged">
  19.                 <si:CallDataMethod Method="InversionSelectionChanged" />
  20.             </i:EventTrigger>
  21.         </i:Interaction.Triggers>
  22.     </ComboBox>
  23. </StackPanel>

The ViewModel handles selection changes and sets its SelectedChord and SelectedScale properties appropriately. The piano keyboard which displays the notes from the selected chord and scale is written as a Silverlight Control and it too has SelectedChord and SelectedScale properties; these bind to the properties on the ViewModel with the same name. The keyboard control also responds to left-mouse clicks in order to allow the selection of the root note of the chord or scale.

So now I have my data loaded and in a bindable ViewModel, creating a user interface in XAML to represent it to the user of the Silverlight ChordFactory is next, together with some stuff about testing. That’s for part 3.