Component One Silvelright FlexGrid Tip 7 (ChildItemsPath with heterogeneous collection)

In one of my previous blog I have explained how to create tree view inside the grid using ChildItemsPath. There was a limitation at that time (prior to version 206), the child item must be of the same type of parent collection. In another words, both parent and child should be of same type. The best example is Person class. In a person class you can have children, but the children have to of type Person itself. In a way it was kind of limiting. With drop 206, Component One removed the limitation and now you can have children of different type. To show how it is done, lets look at the two classes that I am going to use today.

public class SalesPerson
    {
        public string Name { get; set; }
        public List<Sales> Children { get; set; }
    }
    public class Sales
    {
        public string Name { get; set; }
        public int Quantities { get; set; }
        public int Amount { get; set; }
    }

So we have two classes one is sales person and another one is sales. Each sales person have made number of sales and that I want to display in grid using ChildItemPath. Lets look at the XAML

<c1:C1FlexGrid ItemsSource="{Binding SalesPeople}" ChildItemsPath="Children" AutoGenerateColumns="False">
            <c1:C1FlexGrid.Columns>
                <c1:Column Binding="{Binding Name}"/>
                <c1:Column Binding="{Binding Quantities}"/>
                <c1:Column Binding="{Binding Amount}"/>
            </c1:C1FlexGrid.Columns>
        </c1:C1FlexGrid>

Nothing new here, everything is same as before. In the previous version of FlexGrid, when you run, you will get run time error, but with new drop 206, you will not get any error and the result should be something like

image

There you go, it is as simple as that.

Advertisements

Component One Silvelright FlexGrid Tip 6 (Group Header customatization)

Continuing our previous example of using Data Table to create grid on the fly, we will add one more column for grouping purpose to produce an output like the following

image

This also shows how to do grouping when using DataTable in Silverlight. So lets look at the code which generates the data to populate the grid and also grouping.

private void LoadData()
{
   _dataTableWithData = new DataTable();
   _dataTableWithData.Columns.Add("ID");
   _dataTableWithData.Columns.Add("Name");
   _dataTableWithData.Columns.Add("Age");
   _dataTableWithData.Columns.Add("Group");
   for (int i = 0; i < 10; i++)
   {
      int grp = ((i>3)?1:i);
      _dataTableWithData.Rows.Add(i, "Name" + i.ToString(), i, grp);
   }
   var gd = _dataTableWithData.DefaultView.GroupDescriptions;
   gd.Add(new PropertyGroupDescription("Group"));
}

As you can see, we create group row as we would create normal group row in FlexGrid. When we run the code it creates the groups properly but I do not like the default group header information in the group row. It shows

Group: value (number item)

What if I do not want to show the number of item in the parenthesis, how would I go about not showing it? That is what we are going to see today. This is easily achieved by using a custom converter for GroupHeaderConverter. Here is the custom header converter that we need to do to show only ‘Group : GroupName’

public class GroupHeaderConverter:IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
     var group = value as CollectionViewGroup;
     if (group != null)
        return string.Format("Group: {0}", group.Name);
     return null;
  }

  public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
   throw new NotImplementedException();
  }
}

As you can see it is very simple value converter. For every group, we check and see if the row is a group row and if it is, then just return “Group: ” + group.name. One thing you need to make sure to use, the first type conversion. Make sure you you get the group information of type CollectionViewGroup. With that you get all group related information. Which is very powerful if you want to handle grouping completely different. Now that we have a group header converter, we need to let flex grid know to use this converter for the group headers by

_flex.GroupHeaderConverter = new GroupHeaderConverter();
That's all about it, now if you run the program you get the group header the way we wanted it to be.

image


Not only we saw how to customize the group header, we also saw how to do grouping in Data Table. Thanks Bernardo for the pointers.

Working with DataTable in Silverlight 4

Silverlight 4 out of the box does not support DataTable as its desktop counter part, WPF. But third party controls are here for the rescue. In this blog we will look at, what it take to create a simple grid and use Data Table that we love. As you might have guessed it, I am using Component One controls. Component One provides a namespace C1.Silverlight.Data, which have DataTable implementation that we have been using. So now that we know, we can use Data table, lets create a simple Grid and populate the data.

Create a Silverlight application and add grid control in your XAML as follow;

<Grid x:Name="LayoutRoot" Background="White">
        <c1:C1FlexGrid ItemsSource="{Binding DataTableWithData, Mode=TwoWay}" IsReadOnly="False" AutoGenerateColumns="True" Name="_flex"/>
    </Grid>

Nothing new here, my Flex Grid control bound a property in my view model. Before we go and code, we require following references in our project to run

c1.silvelright.dll

c1.Silvelright.data

System.Windows.Data

System.Windows.Controls.Data

Now lets look at the view model code;

private DataTable _dataTableWithData;

publicDataView DataTableWithData

{

     get

   
{

        if(_dataTableWithData == null)

            LoadData();

        return_dataTableWithData.DefaultView;

     }

}

_dataTableWithData property is our underlying data source. Which is nothing but DataTable, which is Component One data table type. If you notice we are not really binding data table to the grid control rather, we are binding DataView to the grid control. DataView implements the IEnumerable interface thus make it bindable to grid control. If you were like me trying out and not seeing the data in your grid, probabily your property type is incorrect.

Now lets look at the code which loads data to the data table

private void LoadData()
{
     _dataTableWithData = new DataTable();
     _dataTableWithData.Columns.Add("ID");
     _dataTableWithData.Columns.Add("Name");
     _dataTableWithData.Columns.Add("Age");
     for (int i = 0; i < 10; i++)
     {
          _dataTableWithData.Rows.Add(i.ToString(), "Name" + i.ToString(), i);
     }
}

There is nothing special here, creating and populating the data table as you would do in any other format. Now lets run and see the result;

image

 

That’s about it. To summarize

1. Make sure you use the references I mentioned above in your project.

2. Use Data Table as local variable to hold data but do not bind control to that property.

3. Use DataView type for binding it to the grid.

4. To bind data table to grid, you return ‘DefaultView’ of the data table.

XAP Optimization – Part III Versioning through program

In my previous blog I explained how to do manual versioning. At the end, I also mentioned I would write a ruby script to automate it so that when you have tons of third party controls, you can version them using a tool.

Initially I was going to write the script in Ruby and it turned out not many people does not know ruby and it could cause problem down the road so I stick to what everyone knows (at my place), C#. So what are we trying to do? Most of the third party controls comes with extmap files which is used for assembly caching.  By default the extmap file does not have versions. But in real life not all our applications are going to use one version in production all the time, we would end up using multiple versions in production as we release new features by using new controls while maintaining old features. Till the old features are migrated to use new controls you would need to keep both version in production. One another requirement for assembly caching is that the dll that you are going to cache need to be signed. I use component one controls and they are all signed, one less work for me.

Now lets see how are we going to do it, Lets first see the code

   1:  class Program
   2:      {
   3:          static void Main(string[] args)
   4:          {
   5:              if (args.Count() < 2)
   6:              {
   7:                  Console.WriteLine("Usage: XAPOptimizationVersioningdirectory versionnumber versionname");
   8:                  Console.WriteLine("     : Directory need to be full path.");
   9:                  Console.WriteLine("     : Version number can be anything you need, in my case it would be three digit number.");
  10:                  Console.WriteLine("     : versionname is optional. If it is available then it would consolidate all the dll into single version with this name.");
  11:                  Console.ReadKey();
  12:              }
  13:              else
  14:              {
  15:                  bool replaceFullAssemblyCacheFileName = false;
  16:                  string assemblyCacheZipFileName = string.Format(".{0}.zip", args[1]);
  17:                  if (args.Count() == 3)
  18:                  {
  19:                      assemblyCacheZipFileName = string.Format("{0}.{1}.zip", args[2], args[1]);
  20:                      replaceFullAssemblyCacheFileName = true;
  21:                  }
  22:   
  23:                  foreach (var extFileName in Directory.GetFiles(args[0]).Where(p=> p.Contains("extmap")))
  24:                  {
  25:                      Console.WriteLine("Processing File {0}", extFileName);
  26:                      XDocument doc = XDocument.Load(extFileName);
  27:                      IEnumerable<XElement> element = doc.Descendants("extension");
  28:                      string attr = element.ElementAt(0).Attribute("downloadUri").Value;
  29:                      if (replaceFullAssemblyCacheFileName)
  30:                          attr = assemblyCacheZipFileName;
  31:                      else
  32:                          attr = attr.Replace(".zip", "." + args[1] + ".zip");
  33:                      element.ElementAt(0).Attribute("downloadUri").Value = attr;
  34:                      doc.Save(extFileName);
  35:                  }
  36:                  Console.WriteLine("Versioning Complete.");
  37:                  Console.ReadKey();
  38:              }
  39:          }
  40:      }

It is as simple as 40 line code but as you can see most of the magic happen between line 23 and 35. First lets split the program in a way it make sense. First of all this is a console application where you need to pass 2 optionally 3 parameters. Lets look at the usage code;

   1:  if (args.Count() < 2)
   2:              {
   3:                  Console.WriteLine("Usage: XAPOptimizationVersioningdirectory versionnumber versionname");
   4:                  Console.WriteLine("     : Directory need to be full path.");
   5:                  Console.WriteLine("     : Version number can be anything you need, in my case it would be three digit number.");
   6:                  Console.WriteLine("     : versionname is optional. If it is available then it would consolidate all the dll into single version with this name.");
   7:                  Console.ReadKey();
   8:              }

When you run the program, you call the name of the program, full directory path to the location of dlls, version number and finally and optionally a version name. First two parameter is self explanatory. What is the third parameter? Well, by default when you do assembly caching, if you have 10 dlls, then it will generate 10 different zip files.but what if you want all of them to be in single file zip file but separated out of the solution XAP file? here is it, pass the name of the file you want all these dlls zipped into. So if you would run the program just the first two parameter

XAPOptimizationVersioning c:\temp\componentone192 192

with the above parameters, it will modify all the extmap files to add version number and thus creating versioned dlls. For example, if I have a file called C1.Silverlight.dll, its expmap would let you create C1.Silverlight.zip out of the box, but after running this tool, it will create C1.Silverlight.192.zip in the client bin.

On the other hand if you would call the program using

XAPOptimizationVersioning c:\temp\componentone192 192 ComponentOne

then anytime when the program uses any dll from c:\temp\componentone192, it will be bundle all dlls referenced in the solution and create one single dll called ComponentOne.192.zip. This will reduce number ZIP files at the server side but on flip side, if someone for their project doesn’t require one of the dll used in your project but pointing to same reference folder then it will create new ComponentOne.192.zip without your reference dll and replace existing one in the server. Since the ZIP is changed, when the user goes to your page next time, it will download new ZIP and error out since one of your required dll is missing. So use this approach with Caution. I will not recommend this solution as it is, but you can get to accomplish this with little workaround I mentioned at the bottom.

Now lets look at the meat of the code

   1:  foreach (var extFileName in Directory.GetFiles(args[0]).Where(p=> p.Contains("extmap")))
   2:                  {
   3:                      Console.WriteLine("Processing File {0}", extFileName);
   4:                      XDocument doc = XDocument.Load(extFileName);
   5:                      IEnumerable<XElement> element = doc.Descendants("extension");
   6:                      string attr = element.ElementAt(0).Attribute("downloadUri").Value;
   7:                      if (replaceFullAssemblyCacheFileName)
   8:                          attr = assemblyCacheZipFileName;
   9:                      else
  10:                          attr = attr.Replace(".zip", assemblyCacheZipFileName);
  11:                      element.ElementAt(0).Attribute("downloadUri").Value = attr;
  12:                      doc.Save(extFileName);
  13:                  }
 

Line 1: Identify all the extmap files available in the directory. This implementation does not recursively look for extmaps in sub folders. You can extend the code to do it.

Line 4: Load the extmap file as XDocument

Line 5: Identify the extension element which have the downloadUri attribute.

Line 7: Check to see if we are going to replace the full name with user supplied zip file name or append only version to the zip filename.

Line 8: Replace the default file to user supplied zip file name.

Line 10: Appends version number to zip file name.

Line 11: Replace the value with modified value.

That’s about it, but here are some thoughts on the whole ‘XAP Optimization’ worth considering.

  • By adding XAP optimization your very first page load will have same load time as the one you would do with out ‘XAP Optimization’.
  • When you modify the code and none of the ZIP changed, the file download time will be drastically reduced because it downloads only changed dll. So the benefit is in the future downloads.
  • Here is a very important catch in ‘XAP Optimization’, if you are using, say 15 or 20 reference dlls and they are zipped up into seperate ZIP files, for every page visit, regardless if you are coming to the page first time or next time, there is a round trip call the web server to see if a files is changed for all ZIP files. This time will add up so quick and users will see downloading screen every time, even though it is not downloading. So please keep this in mind. This could annoy powers users and could create a perception that, the application is downloading every time.
  • If you would like to reduce ZIP files in the server side, one option is to run the tool and create version specific complete zip file or group them in such a way you have few ZIP files. Create these ZIP files before hand manually. When you deploy, only deploy these prepackaged ZIP instead of the ones from client bin folder. This will reduce the zip files, reduce the server side look up and also no one accidently step on and remove any dlls.

Use it properly to fit your need. Hope this helps someone.

Unit Testing in Silverlight – Continuing the path

In my previous blog I talked about few starter pointers for Silverlight Unit Testing. Hopefully you have converted all your Silverlight application fully testable by now. Now comes the next question, by default when you run the Silverlight test, it launches the web page and perform the test. It is all good and well as long as you are in your development environment, what if you want to run your tests as a part of your Continuous Integration (CI). There is a solution for that.

Fortunately I follow Jeremy Likness blog and some time back he wrote a blog about a open source project called Statlight. So today’s tip, if you want to run your Silverlight test as a part of CI, download and run Statlight. Please read Jeremy’s blog, he explains how to run Statight, so I am not going to duplicate that effort.

Unit testing in Silverlight – Starting points.

For all the starters who are venturing into Silverlight testing, I would like to pass on couple of pointers that might help in your testing endeavor.

1. Make sure you create test project using ‘Silverlight Unit testing project’ template to test your Silverlight application. Do not use ‘Test project’ template, it will add incorrect dlls and you will end up spend time trying to find out why in the world, there is version conflicts?

2. Most of all you might have Test Driven.Net installed in your Visual Studio, do not try to test a method by right clicking the method. Rather make the test project as start up project and run the project. When Silverlight test project runs, it gives an option run a specific test using Tags of the tests. Make sure you have a break point set in your test method and run only the test you want to run, it will take you back to your project for debugging.

3. You will run into a situation where you might want to read a data file sooner or later. This can be achieved in two ways.

One is embedding the items in request as resource at the client side.

The second approach is to put the required files in web site which is hosting your Silverlight test application and use ‘HttpWebRequest’ to load the files asynchronously. Loading data asynchronously is little tricky, but there are solution on the web. When you created the test project, if you choose to use dynamic hosting then the second approach is not going to work. If you decide to go with this approach make sure when you create the Silverlight Test Project to enable the check box to create web page to host the application. You can find the solution for loading files in Silverlight Forum. One thing to remember, in this approach your data files lives in web project.

I like the idea embedding the required files as long as they are not very huge. Also when you are using second option, your will end up writing your test methods in delegates. On the other hand, if you would use embedded resources, all you test will be in a single method.

Working with Grid SelectedItems without any line of code in code behind

This is another blog related to creating clean separation of View and ViewModel. When you use Grid and have to act on a single selected item, just pass ‘SelectedItem’ in the command parameter. What happens when you need to act with multiple rows, it is also as simple as using SelectedItems in the command parameter. With this you do not need to write anything in the code behind, especially navigate through the collection of selected items to publish the result to ViewModel. I do not like this idea of coding in code behind in this scenario, especially you are exposing the model to the view when you are boxing individual elements. If you would expose the Model to View then you are breaking the fundamental MVVM pattern. Fortunately we can achieve this without any line of code in the code behind.

XAML

<Button Content="Selected Items" Command="{Binding SelectedItemsCmd}" CommandParameter="{Binding ElementName=grid, Path=SelectedItems}" Grid.Column="1" />
 <c1:C1FlexGrid Name="grid" ItemsSource="{Binding Rows}" AutoGenerateColumns="True" Grid.ColumnSpan="2" Grid.Row="1" />

In XAML binding, we say we are going to execute ‘SelectedItemsCmd’ which takes all Selected Items from the grid as the parameter.

ViewModel

 

public IActionCommand<object> SelectedItemsCmd { get; set; }

        public void SelectedItemsTest(object parameter)
        {
            List<string> temp = new List<string>();
            foreach(var item  in (IList)parameter)
            {
                temp.Add(item.ToString());
            }
        }

 

I am using Jounce as my MVVM framework and Component One for my grid control. Component One returns IList of objects. So in my case, the data binding happens to be List<string> so I walk through the returned object collection and cast each object the I need to box and I use it in my View Model. Thanks to Bernardo @ Component One for help clarify IList for me.