Showing posts with label Data binding. Show all posts
Showing posts with label Data binding. Show all posts

Sunday, March 18, 2012

Using files as your app data


In Windows 8, you can use your files as data for your app. Suppose you have a bunch of pics stored on your machine or on your network, for example. You can easily display them in your Metro app :
image
Here’s the basic method behind this UI :
   1: private async Task GetFilesAsync(IStorageFolder rootFolder)
   2: {
   3:     myFiles.Clear();
   4:  
   5:     var files = await rootFolder.GetFilesAsync();
   6:  
   7:     foreach (var f in files)
   8:     {
   9:         var myFile = new MyFile { FileName = f.Name };
  10:  
  11:         myFile.Thumbnail = await (f as StorageFile).GetThumbnailAsync(
  12:             ThumbnailMode.ListView, 100, ThumbnailOptions.ResizeThumbnail);
  13:  
  14:         myFiles.Add(myFile);
  15:     }
  16: }


It uses a data model file that looks something like this :


   1: public class MyFile : BindableBase
   2: {
   3:     public string FileName { get; set; }
   4:  
   5:     string title;
   6:     public string Title 
   7:     {
   8:         get { return title; }
   9:         set { SetProperty<string>(ref title, value); }
  10:     }
  11:  
  12:     public StorageItemThumbnail Thumbnail { get; set; }
  13: }

A custom file class that stores the file name, title (set by the user on her pictures) and thumbnail to display. Note it extends BindableBase for convenience to easily implement INotifyPropertyChanged and raise UI notifications but simply calling SetMethod(), defined in BindableBase.

Also note the GetFilesAsync method asynchronously retrieves the thumbnail of each pic by calling GetThumbnailAsync. How easy is that ? Thanks winRT.

Now suppose you want to group the pics in some way. You can do it easily through the pivot API, which creates virtual folders that represent groups. Here’s another method :


   1: private async Task GetVirtualFoldersAsync(StorageFolder rootFolder)
   2: {
   3:     myVirtualFolders.Clear();
   4:  
   5:     var pivot = CommonFolderQuery.GroupByMonth;
   6:     var queryOptions = new QueryOptions(pivot);
   7:  
   8:     var queryResult = rootFolder.CreateFolderQueryWithOptions(queryOptions);
   9:     var vFolders = await queryResult.GetFoldersAsync();
  10:  
  11:     foreach (StorageFolder vfolder in vFolders)
  12:     { 
  13:         var myFolder = new MyFolder { FolderName = vfolder.Name };
  14:  
  15:         var files = await vfolder.GetFilesAsync();
  16:         myFolder.ImageCount = files.Count();
  17:  
  18:         myFolder.Thumbnail = await (vfolder as StorageFolder).GetThumbnailAsync(
  19:             ThumbnailMode.ListView, 200, ThumbnailOptions.ResizeThumbnail);
  20:  
  21:         myVirtualFolders.Add(myFolder);
  22:     }
  23: }


What this method does is generate a list of virtual folders each matching a group of pics taken during a given month. The pivot is defined using the GroupByMonth query, which will result in the files being pivoted on using this criterion. We could have picked another pivot, such as CommonFolderQuery.GroupByAuthor for example.

Next we call CreateFolderQueryWithOptions(options) to actually create the pivoted query. This method is available on the StorageFolder passed into the method, for example Windows.Storage.KnownFolders.PicturesLibrary.  Then we call GetFoldersAsync to actually dig and retrieve the files grouped by month.

Once the method completes, you can access the list of virtual folders representing the groups created. Here this is done in the foreach loop which loops over the virtual folders. My code then goes on to do app-specific stuff, such as retrieving the list of files in the group (vfolder) so as to display the count in the UI, and retrieve the vfolder’s thumbnail. Windows does that for you, it will generate a thumbnail for your folder or virtual folder, usually the thumbnail of the first pic in the folder. So you can call GetThumbnailAsync on a StorageFile OR a StorageFolder. Awesome.

Like before, I instantiate a custom class to store the vfolder data and store it in an observable collection so my UI can bind to it and get change notified. The UI then lists groups (vfolders), and I can add code so that when a Vfolder thumbnail is tapped, the list of actual pics in that vfolder gets displayed.

image

So you can see how easy it is to use files as app data and then implement simple pivots on that data. The beauty of this is the way we perform asynchronous operations, which would have previously made a hellish mess in our code, so smoothly and easily using async / await. Thanks to this new pattern the work we need to do to define and implement pivots on top of file resources in our data apps is almost fun!

Happy pivoting

Saturday, March 10, 2012

Win8 Metro controls and data grouping


In Windows 8, the primary collection controls are ListView and GridView, which are items controls that derive from a common ListViewBase class. There’s also the FlipView, which binds to data collections as well but displays one item at a time. ListView and GridView have built-in support for data grouping, which makes a lot of very common scenarios a breeze to implement. In this post I’ll zoom in on the grouping feature in Metro list controls.
image
Suppose you have an app that displays a list of courses. You can use the Grid Application project template in Visual Studio to generate the skeleton for you. Then you need to create your own data source in place of the SampleDataSource provided in the project. In my example, I read course data from an XML file that I add to the project:
   1: <courses>
   2:   <course>
   3:     <title>Windows Phone 7 Hands-On</title>
   4:     <description>This course covers many real-world issues developers face when building Windows Phone applications</description>
   5:     <author>Yacine Khammal</author>
   6:     <level>Intermediate</level>
   7:     <duration>03:04:56</duration>
   8:     <published>04/18/2011</published>
   9:     <track>WP7</track>
  10:     <image>Data/Images/wphandson.png</image>
  11:     <modules>
  12:       <module>
  13:         <title>WP application architecture</title>
  14:         <description>A look at building an MVVM application for Windows Phone 7 application. We examine phone-specific choices and constraints faced when structuring a data backed business app for the phone.</description>
  15:         <image>Data/Images/wcfriaservices.png</image>
  16:         <clips>
  17:           <clip title="Intro" videouri="section1.wmv" />
  18:           <clip title="Overview of MVVM" videouri="section3.wmv" />
  19:           <clip title="Windows Phone 7 considerations" videouri="section4.wmv" />
  20:           <clip title="Sample requirements" videouri="section2.wmv" />
  21:           <clip title="Hands-on : application structure" videouri="section6.wmv" />
  22:           <clip title="Hands-on : viewmodel classes" videouri="section7.wmv" />
  23:           <clip title="Hands-on : the main view" videouri="section8.wmv" />
  24:           <clip title="Hands-on : view hierarchy" videouri="section9.wmv" />
  25:           <clip title="Navigation topics" videouri="section10-0.wmv" />
  26:           <clip title="Hands-on : navigation" videouri="section10.wmv" />
  27:           <clip title="Commanding" videouri="section11-0.wmv" />
  28:           <clip title="Hands-on : adding commands - 1" videouri="section11.wmv" />
  29:           <clip title="Hands-on : adding commands - 2" videouri="section12.wmv" />
  30:           <clip title="Takeaways" videouri="section13.wmv" />
  31:         </clips>
  32:       </module>
  33:       <module> ... </module>
  34:     </course>
  35:     <course> ... </course>
  36: </courses>

Oh and by the way, in case you’re interested, for lack of better ideas, here’s the approach I’ve used to read the data from the xml :


   1: private async Task GetCoursesAsync()
   2: {
   3:     try
   4:     {
   5:         var storageFolder = await Package.Current.InstalledLocation.GetFolderAsync("Data");
   6:         var xmlFile = await storageFolder.GetFileAsync("CourseData.xml");
   7:         var stream = await xmlFile.OpenAsync(FileAccessMode.Read);
   8:  
   9:         XmlDocument xmlDoc = await XmlDocument.LoadFromFileAsync(xmlFile);
  10:         XDocument doc = XDocument.Parse(xmlDoc.GetXml());
  11:  
  12:         courseList = (from course in doc.Descendants("course")
  13:                       select new Course
  14:                       {
  15:                           Title = course.Element("title").Value,
  16:                           Description = course.Element("description").Value,
  17:                           Author = course.Element("author").Value,
  18:                           Level = course.Element("level").Value,
  19:                           Duration = TimeSpan.Parse(course.Element("duration").Value),
  20:                           Published = DateTime.Parse(course.Element("published").Value),
  21:                           Track = course.Element("track").Value,
  22:                           Image = new BitmapImage(new Uri(projectBaseUri, course.Element("image").Value)),
  23:                           Modules = (from module in course.Element("modules").Descendants("module")
  24:                                      select new Module
  25:                                      {
  26:                                          Title = module.Element("title").Value,
  27:                                          Description = module.Element("description").Value,
  28:                                          Image = new BitmapImage(new Uri(projectBaseUri, module.Element("image").Value)),
  29:                                          Clips = (from clip in module.Element("clips").Descendants("clip")
  30:                                                   select new Clip
  31:                                                   {
  32:                                                       Title = clip.Attribute("title").Value,
  33:                                                       VideoUri = new Uri(projectBaseUri, "Data/Video/" + clip.Attribute("videouri").Value),
  34:                                                       Transcript = clip.HasElements ? clip.Element("transcript").Value : ""
  35:                                                   }).ToList()
  36:                                      }).ToList()
  37:                       }).ToList();
  38:  
  39: ...
  40: }

So now I have a data source. Of course I’ve previously defined a Course class, a Module class, a Clip class, each with appropriate data properties. If your app also needs to update data as well as display it, then you should have your data classes extend the BindableBase class included in the Common directory of the VS project templates, and have your property setters call SetProperty() to raise UI-bound change notifications.

We could just go ahead and bind a list control to our data source, just like we’ve done in my previous post. However we want to first group the data, so the first level of listing is groups. So first, we define a grouping construct, which IS a list of items or EXPOSES a list of items. For example :


   1: public class CourseTrack
   2: {
   3:     public CourseTrack()
   4:     {
   5:         Courses = new ObservableCollection<Course>();
   6:     }
   7:  
   8:     public string TrackName { get; set; }
   9:     public ObservableCollection<Course> Courses { get; set; }
  10: }

There we have it, a CourseTrack grouped concept that exposes a list of courses as well as a group name called TrackName. Here’s an alternative approach :


   1: public class CourseTrack : List<Course>
   2: {
   3:     public string TrackName { get; set; }
   4:     public new IEnumerator<object> GetEnumerator()
   5:     {
   6:         return (IEnumerator<object>)base.GetEnumerator();
   7:     }
   8:  
   9:     public CourseTrack(string trackName, IEnumerable<Course> courses)
  10:     {
  11:         TrackName = trackName;
  12:         base.AddRange(courses);
  13:     }
  14: }

Here the CourseTrack IS a list of courses since it extends a collection. This approach is actually required if you want to use semantic zoom.  And here’s an example of how you actually group the data :


   1: internal void GroupCoursesByTrack()
   2: {
   3:     CoursesByTrack = new List<CourseTrack>();
   4:  
   5:     var query = from course in courseList
   6:                 orderby course.Track
   7:                 group course by course.Track into g
   8:                 select new CourseTrack(g.Key, g);
   9:  
  10:     CoursesByTrack = query.ToList();
  11:  
  12: }

You got it, CoursesByTrack is a list of groups, that is a list of CourseTracks each representing a list of groups.

Then you bind a CollectionViewSource to the list  :


   1: <CollectionViewSource
   2:     x:Name="tracksViewSource"
   3:     Source="{Binding CourseTracks}"
   4:     IsSourceGrouped="true" />

Your sharp eye probably didn’t miss the IsSourceGroup property, which as the name suggests indicates the data source is grouped, i.e. the elements in the list are groups. As a result, any ListViewBase control that binds to the CollectionViewSource will display the root elements as groups using the group-related templates you specify :


   1: <GridView ItemsSource="{Binding Source={StaticResource tracksViewSource}}"
   2:           ItemTemplate="{StaticResource TrackListItemTemplate}">
   3:     ...
   4:     <GridView.GroupStyle>
   5:         <GroupStyle>
   6:             <GroupStyle.HeaderTemplate>
   7:                 <DataTemplate>
   8:                     ...
   9:                 </DataTemplate>
  10:             </GroupStyle.HeaderTemplate>
  11:             <GroupStyle.Panel>
  12:                 ...
  13:             </GroupStyle.Panel>
  14:         </GroupStyle>
  15:     </GridView.GroupStyle>
  16: </GridView>

Within the GroupStyle property you can specify the group header through the HeaderTemplate, and the panel to use for displaying the list of groups using the Panel template.

Note that if you used approach 1 above, that is you define your group class, CourseTrack, as EXPOSING a collection of courses as a property as opposed to deriving from a collection type, then in your CollectionViewSource, in addition to setting IsGroupedSource to true, you need to indicate the path to the actual groups within the data source using the ItemsPath property :


   1: <CollectionViewSource
   2:     x:Name="tracksViewSource"
   3:     Source="{Binding CourseTracks}"
   4:     IsSourceGrouped="true"
   5:     ItemsPath="Courses"/>

If you’ve worked with the Developer Preview, you probably remember you had to have your group classes implement an IGroupInfo interface for this to work. Now all you need to do is extend or expose a collection property. So much simpler and more intuitive! Grouping your data source has really become a breeze.

Happy grouping !

Saturday, March 3, 2012

Windows 8 Consumer Preview & Visual Studio 2011

 

Windows 8 Beta is here. And it’s fantastic. I’m not just saying it because I’m a Microsoftie, or because I’m getting paid for that, or anything like that. This is (so far) my honest sincere opinion. Why, may you ask ? Just play with it for a little while and you’ll know. The Start menu is just beautiful, feels like a smartphone with no capacity restrictions. I really like the corner gestures : lower left for the start screen, upper left for last open app AND list of all open apps, upper and lower right for bringing up the charms. And I love the new ability to close an app by grabbing the top “edge” of it and literally bringing it down through the bottom of the screen. Feels so good to do that. And the crazy thing is, it all works just as well with touch AND mouse & keyboard. In short, love it.

image

Now for the serious stuff : I have quite a few apps I developed on the Developer Preview version. Bad news : so much has changed in Windows (winRT) that they just no longer run in the new version. Just try to open an devprev app with VS 11 consprev : total fail!

So, let’s roll up our sleeves and get to work, there’s a lot of stuff to catch up with to understand the breaking changes. It’s all listed in this document, have fun reading it… As far as I’m concerned, I decided to read it on an as-needed basis. So where do I start in migrating my apps ? I have this sample app I wrote using the Grid Application VS template in the old world. Tried to open it in the new vs, no way. So I followed the basic guideline that says, create a new project and then '”import” your code. I created a project using the new Grid metro style app template (C# XAML) :

image

That created a GroupedItemsPage, a GroupDetailPage, and an ItemDetailPage for me. New thing : these pages inherit from a base LayoutAwarePage which handles the layout change events associated with the visual states such as Filled vs Snap or Portrait vs Landscape. Also has some infrastructure code to facilitate data binding in our custom pages. Anyway, if you try to bring in a page from an existing devprev app, build will fail as a lot of APIs have changed. For example, classes that have the word ‘Layout’ in their names have been renamed to use ‘View’ instead, so ApplicationLayout has become ApplicationView. That’s just an example. Since the old pages contained the layout change code, it’s really a pain to try to update them. So the best approach is to recreate the page, and bring in the custom XAML that you’ve created to build the UI. Doing so, it wasn’t too bad porting the app – of course I’m talking about a simple app using the Grid template and some data binding.

Speaking of the devil, using data binding is a bit different as well. If you look at the new Grid project template, the code-behind files use an object called DefaultViewModel. If yo examine the base LayoutAwarePage, you’ll see that DefaultViewModel refers to an ObservableDictionary<string,object>. What the heck is that, I hear you ask ? It’s just a convenient way of storing a simple view model. Say you have data in the form of a list of groups of widgets, and you want the grouped page to show the groups, then the group details page to show the content of a group, that is a sub-list of widgets, and then a detail page that shows data for a specific widget.  You can store the list of groups in the dictionary under a string such as “WidgetGroups”. Then in your XAML you can define a CollectionViewSource pointing to your list like this :

   1: <CollectionViewSource
   2:     Source="{Binding WidgetGroups}"
   3:     IsSourceGrouped="true"
   4:     ItemsPath="Widgets"/>




‘Widgets’ is the name of the list within a group, so WidgetGroups is a list of groups that each contains a list of Widgets. Now where does that all get set, you scream ? It all starts in app.xaml in the OnLaunched event, where this code is called :



   1: rootFrame.Navigate(typeof(GroupedItemsPage),myWidgetGroups);

As you guessed, this will bring up the GroupedItemsPage – notice the Frame.Navigate() method has changed, it now accepts a type as the first param – and pass to it the group list. Now, in the GroupedItemsPage itself, in the OnNavigatedTo() method you do something like this :



   1: protected override void OnNavigatedTo(NavigationEventArgs e)
   2: {
   3:     this.DefaultViewModel["WidgetGroups"] = e.Parameter;            
   4: }



Where e.Parameter is the myWidgetGroups object you set in the Navigate call. It’s now stored in the DefaultViewModel dictionary under the “WidgetGroups” key, and the dictionary is the datacontext for the page, so you’re all set!


You do the same to pass data to your other pages : when something is selected for example, you call Navigate(typeof(myotherpagetype), theSelectedStuff) and in the other page, you retrieve it in the OnNavigatedTo() method and add it to the DefaultViewModel dictionary under a different key. So what’s the purpose of this new Dictionary ? Being an ObservableDictionary, it has the capability of notifying the UI when its contents are updated. So there you have it, a new INotifyCollectionChanged copycat, and a super convenient one too.


So this was a quick example of how to port a metro app written in XAML under devprev to the new consprev version. I’ll be doing quite a bit of that in the days to come, as I’m refreshing a couple of new courses on win8 Metro, so I’ll keep you posted!