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.

37 comments:

彭志文 said...

人不可以求其備,必捨其所短,取其所長......................................................

劉WileyMares said...

pleasure to find such a good artical! please keep update!! ........................................

嘉偉 said...

辛苦了!祝你愈來愈好!........................................

DennyMa said...

很高興見到你哦!!. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

佳穎盛卿 said...

It's great!!..........................................

韋于倫成 said...

快下班囉~來幫你加油~~........................................

裕以 said...

成人卡通 視訊34c 母子亂倫聊天室 免費無碼片 台灣交友網 日本女優無碼 無名小站性感美女影片 影片試看 偷拍,影片 基隆聊天室 女性愛愛 性愛自拍 18成人網站 少女色情 洪爺成人影城 彩虹a片 情色卡通線上看 麗的色情小遊 一夜情a片 aio愛情館影片免費383 完美女人 玩美影城 性感美女 辣妹鋼管脫衣秀 人妻短片下載 熟女貼圖 a片下載 視訊聊天室 a圖看到爽 限制級寫真集 85cc免費看成人片 微風成人a片本土 嘟嘟 色情777 熊貓貼圖 援交自拍影片 情人趣味用品 免費脫衣 免費av片觀賞 洪爺影城 大奶阿尼 mofosex. 下載a圖 日本av巨乳 性愛光碟 後宮電影院18jack 限制級 live0204 免費看性愛影片 正妹走光露點

瓊慧 said...

The shortest answer is doing. ............................................................

桂竹 said...

The more haste, the less speed. ............................................................

宗伊旺博 said...

若有人問你成功時會不會記得他 試問若你失敗時他會不會記得你 ............................................................

walsha said...

Necessity is the mother of invention.............................................................

陳逸群 said...

志不立,天下無可成之事。........................................

雲亨 said...

I guess I will need a lot..................................................

林奕廷 said...

我的老天爺~內容真是太棒了.................................................................

王邦鈺 said...

pleasure to find such a good artical! please keep update!!.................................................................

亦妮亦妮 said...

河水永遠是相同的,可是每一剎那又都是新的。.................................................................

慧君慧君 said...

成熟,就是有能力適應生活中的模糊。.................................................................

吳婷婷 said...

當一個人內心能容納兩樣相互衝突的東西,這個人便開始變得有價值了。............................................................

懿綺懿綺 said...

在你一無所有的時候 是誰在陪伴你 他便是你最重要的人............................................................

香昱信張君林 said...

多謝美味的心靈雞湯......................................................................

詹莉emmaagnes莉真 said...

安安唷~~幸運的日子送給妳(你)滿滿的幸福,也祝福你天天都開心唷............................................................

dawsonfelicia張君dawsonfelicia均 said...

愛情是一位偉大的導師,教我們重新作人.................................................................

長修 said...

永遠支持你呀!!!謝謝格主............................................................

俊宏淑松 said...

雖天地之大,萬物之多,而惟吾蜩翼之知。..................................................

曾法幸 said...

好文章就值得回響,如果可以常常看到您的更新,應該是件很幸福的事情~~..................................................................

周旭威 said...

做好事,不需要給人知道,雖然只是一件微不足道的事,但我相信,這會帶給我快樂。..................................................

許志蘇昶哲宏 said...

死亡是悲哀的,但活得不快樂更悲哀。............................................................

建茂恒霖 said...

Many a little makes a mickle..................................................................

建邱勳 said...

rain before seven; fine before eleven.............................................................

宇緯陳陳宇緯陳陳 said...

Make yourself necessary to someone..................................................................

承王蓁 said...

愛情是盲目的,但婚姻恢復了它的視力。......................................................................

/798 said...

打聲招呼,祝你一切平安!............................................................

威吳隆威吳隆威吳隆威吳隆 said...

很棒的分享~祝福你............................................................

靜錢錢錢怡錢錢錢錢 said...

多謝美味的心靈雞湯......................................................

翊翊翊翊張瑜翊翊翊 said...

唯有穿鞋的人,才知道鞋的哪一處擠腳......................................................................

蕾蕾 said...

人生中最好的禮物就是屬於自己的一部份............................................................

惠NorrisBradwell041花 said...

希望能常常看到你的更新..................................................