C# Best practices – String

Few things I want to point out when using string in C#.

In C# so as Java, string is immutable. what does it mean by immutable? immutable is that once an object have a value it never changes. For example, when you have a value ‘unni’ assigned to ‘name’ variable, and later if you decide to change the name ‘unni’ to say ‘nair’, then when you assign the value to ‘name’ variable, it creates new memory and put the value ‘nair’ in it and point to that location. The important point in here is that, the value ‘unni’ never got overwritten. That memory location now becomes garbage for garbage collector to pick it up. So you have be very careful when using string, always mindful of its immutability so that you will not create too many garbage memory so fast. You can think of immutability like ‘const’ or ‘readonly’ keyword in C#. One of the main benefit most of developers do not know is that immutability is that it naturally lend it self to concurrent programming.

Now that we know what is immutability, where could this cause problems for the novice programmers; Lets take the following code.

            string concatinatedString = string.Empty;
            for (int i = 0; i < 10; i++)
                concatinatedString += i.ToString();

This code does not look harmful. But if we go back to the example with my name what really happening is, the look is going to create 10 instance of the string variable each one with its previous string + current value.So when the program runs, you can see, it created 10 memory objects even though expected task was to append all numbers to the same variable.

image 

Now the question is, but I want to create a concatenated string so how do I go about doing it? The answer is very simple, use StringBuilder. By definition, this is mutable string of characters. So if any point you want to create mutable strings, try to use string builder. So the above code if we would use StringBuilder, it will be like the following

            StringBuilder concatinatedString = new StringBuilder();
            for (int i = 0; i < 10; i++)
                concatinatedString.Append(i.ToString());

Even though we have lot more memory to work with compared to old days, we should take every possible step to write an efficient code.

Advertisements

What makes XAP to download?

Every one knows when ever there is a new version of XAP file, Silverlight ActiveX control downloads the new version properly and all works. If you would Bing you will find out, people have trouble getting latest version of XAP file and there are lot of solution to it. So I am not going into that part. What I want to explain here is a scenario that we ran into, which is straight opposite. In our case XAP gets downloaded every time even when there is no changes the XAP file. It was a very interesting problem and I got great help from some of my colleagues to isolate and solve the problem. I thought I share it here for anyone looking for solution.

Let me explain the exact problem. In our QA environment when we open a brand new instance of IE, that is no other instance of IE is open and go to a Silverlight page for the first time, you can see that, XAP will be downloaded. Now navigate between the pages in the application and come back to the same page and you will notice, XAP will not be downloaded. So far is good. Now close all IE and launch IE again, go to Silverlight page, it will download XAP file again even though XAP file was downloaded a minute ago and we did not release any new XAP file either. Now, we though we could reproduce the problem in our development environment, but we couldn’t. In Development environment once you go the page, XAP get downloaded and close all IE and come back to the same page, it does not download XAP file again. It was a big puzzle.

I did some research on the internet and found out that, If I would set “HTTP Response header expiration’ for ClientBin folder in IIS to a specific time period, XAP will not download till the time period expires. Well it did not solve my real problem since I do not want to set the expiration time to too long. We knew there is still a problem that I am not finding. So I asked around and one of my colleague recommended me to check the HTTP header using Fiddler and he said it might give you an idea. He asked me to check for specifically

If-Modified-Since
Expires
ETag

Here is link in wiki which really helped understand the cache. With that information and I knew what I am after, I ran the same test as explained before against our QA servers with Fiddler running. First attempt I got http response 200 for the XAP file as I expected.

image

Now with in the same application on the same instance of IE, move between pages and come back to the same page and I get 304 as expected and it will not download the file.

image

I closed all the IE instances and open Silverlight page again I did get 200 and only difference in this case was my ETag was different. I was curious why ETag is different, it turned out, in QA we have multiple servers which are running under load balancer so this time, load balancer send me to another server. Since the ETag value is different from the previous server and even though the last modified date and time is same, it ended up downloading the file. So the culprit in this case is ETag. Different QA servers have different ETag set. One of my friend send me a mail with issue with ETag and its solution recommended in Microsoft site. 

There is two solution, one recommended in the Microsoft web site to bring all your QA servers to same ETag base number. The other is just add the ETag to ClientBin foler as empty. In our case, we added ETag as empty and performed the test, now it no longer downloads the XAP file.

Hopefully it helps someone.

ForEach in IEnumerable

Recently one of my friend asked me, is it possible to do foreach on an IEnumerable. I said ‘yes’ based on my knowledge on List. But it turned out I was wrong. You can not do ForEach on an IEnumerable right out of the box. It doesn’t mean, you can’t do it, it was not implemented. So I asked the question, can’t you use foreach? The answer was, sure. It was the person’s curiosity that led me to see why it was not implemented to start with. I did couple of Google search and got the following link, which explained very well why it was not implemented and if you want how you would implement it.

It is a very good read for anyone interested.

http://blogs.msdn.com/b/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx

Also there was a little debate in StackOverflow on the same subject.

http://stackoverflow.com/questions/200574/linq-equivalent-of-foreach-for-ienumerablet

http://stackoverflow.com/questions/858978/lambda-expression-using-foreach-clause

In my opinion, I would stick with ‘foreach’ instead of ‘ForEach’.

If I would need to get fancy and show off, I would write the extension rather than ‘.ToList()’.ForEach’. When you perform, ToList, it creates the copy of the object before send it to ForEach.

Out of curiosity, I wrote a small unit test code to test the performance of the three flavors. It turned out, traditional ‘foreach’ have a very thin lead in the process. Here is what the program does, I have a person model as follows; what we are trying to do is remove all the children from all the parent which have any children in them. For simplicity, we assume there is only two levels.

public class Person
{
   public string Name { get; set; }
   public int Age { get; set; }
   public List<Person> Children { get; set; }
   public Person()
   {
       Children = new List<Person>();
   }
}

I have a class library which does all three different flavor of for each

public class LinqTesting
{
   public List<Person> SourceList;
   public List<Person> TargetList;
   public void RemoveChildrenWithLinq()
   {            
       SourceList.Where(p => p.Children.Count() > 0).ToList().ForEach(p => p.Children.Clear());
   }
   public void RemoveChildrenWithForEach()
   {
       foreach (var item in SourceList.Where(p=>p.Children.Count() > 0))
       {
           item.Children.Clear();
       }
   }
   public void RemoveChildrenWithLinqExtension()
   {
       SourceList.Where(p => p.Children.Count() > 0).ForEach(p => p.Children.Clear());
   }
}

We load the data as follows

private List<Person> LoadSourceData()
{
     List<Person> ppl = new List<Person>();
     for (int i = 0; i < 1000; i++)
     {
         Person singlePerson = new Person() { Name = string.Format("Parent {0}", i), Age = i + 10 };
         if (i % 3 == 0)
         {
             for (int j = 0; j < 5000; j++)
                 singlePerson.Children.Add(new Person() { Name = string.Format("Child {0}", j), Age = j + 10 });
         }
         ppl.Add(singlePerson);
     }
     return ppl;
 }

The tree test methods are

[TestMethod]
public void TestMethodWithLinq()
{
    start = DateTime.Now;
    tester.RemoveChildrenWithLinq();
    end = DateTime.Now;
    PerformCommonAssert();
}

[TestMethod]
public void TestwithForEach()
{
   start = DateTime.Now;
   tester.RemoveChildrenWithForEach();
   end = DateTime.Now;
   PerformCommonAssert();
}

[TestMethod]
public void TestwithExtensions()
{
   start = DateTime.Now;
   tester.RemoveChildrenWithLinqExtension();
    end = DateTime.Now;
   PerformCommonAssert();
}

And the result is

0:0:4 (m:s:ms)

0:0:3 (m:s:ms)

0:0:4 (m:s:ms)

3 passed, 0 failed, 0 skipped, took 7.44 seconds (MSTest 10.0).

With the above set of input, both Linq and Linq extenstion method took 4ms, while the traditional for each only took 3ms. With 1ms better, traditional ‘foreach’ is the better way to do it. Not only that, the code is very easy to understand and easy to maintain.

If anyone have different opinion or suggestions, please let me know.

Observable Collection, second look.

Observable Collection by definition provide notification when an item is added, removed or refreshed. It is pretty cool nifty item to have. So if you want a type of collection that automatically notifies the UI to update when underlying data changes, it has to be Observable Collection. Less code, more feature out of the box. But I want to point out two things when using Observable Collection.

Most of the people forget the very import thing, if you are using Observable collection then for every change, Observable Collection fires an event. If it is very small numbers then you will not notice the performance issue. But if for some reason you are adding and/or removing 100s or 1000s of rows then you will see visible performance degradation. If your data is flat or if it does not have a collection inside the collection then there should not be any performance hit either. I wrote a sample code to time it so that I  can document the performance issue if any.

Before we go into the example, I would like to point out the second issue of Memory Leak when using Observable Collection. Once you initialize a bound observable collection, do not new up again, rather, clear the collection and then add new items to the same collection. You can read about it in here and also here.

Now lets look at the performance timings.

Scenario 1: Simple observable collection class.

My backend data model is PERSON class

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Now the XAML

<Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="80"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid HorizontalAlignment="Center">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <Button Name="Add" Content="Add" Click="Add_Click" Grid.Column="0" HorizontalAlignment="Center" Margin="4,4,4,4"/>
            <Button Name="Clear" Content="Clear" Click="Clear_Click" Grid.Column="1" HorizontalAlignment="Center" Margin="4,4,4,4"/>
            <TextBox Name="Numbers" Grid.Column="2" Width="100"/>
        </Grid>
        <c1:C1FlexGrid Name="_grid" Grid.Row="1" />
    </Grid>

The top part has two buttons, one to add rows and another one to remove rows from the collection. The third is a textbox, where you can enter how many rows you want to add or remove. What we are currently interested in is, adding rows to the grid on the fly.

private void Add_Click(object sender, RoutedEventArgs e)
{
     DateTime dt = DateTime.Now;
     for(int i=0;i<int.Parse(Numbers.Text);i++)
        _people.Add(new Person() { Age = i + 10, Name = "Name" + i.ToString() });
     DateTime dt1 = DateTime.Now;
     TimeSpan dt2 = dt1.Subtract(dt);
      Debug.WriteLine(string.Format("{0}:{1}:{2}:{3}", dt2.Hours, dt2.Minutes, dt2.Seconds, dt2.Milliseconds));
}

Armed with above code and XAML if you would run, the time it takes to add 1000 new object to the collection is 107ms. Not bad, no performance hit.

Scenario 2: Hierarchical Collection using Observable Collection.

We modify the Person Class as follows to include a children of same type.

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public ObservableCollection<Person> Children { get; set; }
    public Person()
    {
       Children = new ObservableCollection<Person>();
    }
}

With this change, if you would run the same code, it takes 10.263s. You can see why the time jumped by 10 seconds, if I would remove the constructor from the code, it still comes down to 9.623ms. So the constructor is not the problem here.Lets move on to the third scenario.

Scenario 3: Hierarchical Collection using Custom Class derived off of Observable collection.

This is an interesting scenario that I did not think about it till I ran into a performance issue with grid. The idea behind this scenario is that, since by definition, Observable Collection fires an event notification for any change to the data, in our case, when we are adding a bunch of data, we do not want to fire event for each and individual change rather, fire an event at the end of all the changes are made. So the solution is to derive a new class off of Observable Collection and make sure you turn off the notification before adding range and at the end turn the notification on. I followed the Smart Collection explained in Daamir blog. Just in case, I added the class definition right here as well.

    public class SmartCollection<T> : ObservableCollection<T>
    {
        public SmartCollection()
            : base()
        {
            _suspendCollectionChangeNotification = false;
        }


        public SmartCollection(List<T> list) : base(list) { }


        bool _suspendCollectionChangeNotification;

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (!_suspendCollectionChangeNotification)
            {
                base.OnCollectionChanged(e);
            }
        }

        public void SuspendCollectionChangeNotification()
        {
            _suspendCollectionChangeNotification = true;
        }

        public void ResumeCollectionChangeNotification()
        {
            _suspendCollectionChangeNotification = false;
        }


        public void AddRange(IEnumerable<T> items)
        {
            this.SuspendCollectionChangeNotification();
            int index = base.Count;
            try
            {
                foreach (var i in items)
                {
                    base.InsertItem(base.Count, i);
                }
            }
            finally
            {
                this.ResumeCollectionChangeNotification();
                var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
                this.OnCollectionChanged(arg);
            }
        }

        public void Repopulate(IEnumerable<T> items)
        {
            this.Clear();
            this.AddRange(items);
        }
    }

If you look at the code, three piece of information interesting to current article. SuspendCollectionChangeNotification, which is to turn off the notification and ResumeCollectionChangeNotification to turn on the notification. These two methods need to be invoked if you are adding one row at a time through your code. On the other hand, if you are adding a collection to the Observable Collection, then call AddRange method, which internally will take care of turning on and off the notification. Now lets look at the code change

public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public SmartCollection<Person> Children { get; set; }
        public Person()
        {
            Children = new SmartCollection<Person>();
        }
    }

Modify the code bind and change all Observable Collection Reference to SmartCollection. Also change the for loop where we add individual items to SmartCollection to List as follows

private void Add_Click(object sender, RoutedEventArgs e)
        {
            DateTime dt = DateTime.Now;
            Debug.WriteLine(string.Format("{0}:{1}:{2}:{3}", dt.Hour, dt.Minute, dt.Second, dt.Millisecond));

            List<Person> ppl = new List<Person>();
            for(int i=0;i<int.Parse(Numbers.Text);i++)
                ppl.Add(new Person() { Age = i + 10, Name = "Name" + i.ToString() });
            _people.AddRange(ppl);

            DateTime dt1 = DateTime.Now;
            TimeSpan dt2 = dt1.Subtract(dt);
            Debug.WriteLine(string.Format("{0}:{1}:{2}:{3}", dt2.Hours, dt2.Minutes, dt2.Seconds, dt2.Milliseconds));
        }

With these changes if you would run the program, you will see the 1000 row insertion only took 31ms.  Now the question is, why can’t we do the step as creating temporary collection and then assign it back to observable collection? Wouldn’t it work the same way? One approach would be

var concatList = _people.Concat(pp);

_people = new ObservableCollection(concatList)

in my opinion, newing Observable collection had some memory problem in Silverlight. The recommendation was always new up only once and from there on, if you want to add items to it, you add item to it or clear and then add item to it. That is the only reason I did not try it out.

So the bottom line is that, when we use Observable Collection take care not to fire event when you are working on too large of data. As I mentioned earlier, if your data is flat then you will not incur any performance problems but if you have hierarchical data then I would recommend you to switch to a model to turn on and off the notification model.

If any of you have good way to do it, please feel free to drop me a note. I am very much interested in learning and understand new ways of doing things.

Creating TreeView using Grid in Silverlight (Component One – Cell Factory) – III

[Component one already have a new attribute in C1FlexGrid ‘ ChildItemsPath=”Children” which will create the tree view look and feel without any of this custom cell factory. I did it anyway as a means to learn]

In the previous blog post, we took our first step in creating tree view look and feel using C1FlexGrid. With some basic changes we were able to get to the result close enough.

image

Few things missing for the grid to look like tree view, they are

1. When parent do not have a child, the expand and collapse icon should not appear.

2. We are missing check box along with parent id.

We can resolve both the problems using Cell Factory.  What does cell factory do? Cell Factory is used by C1FlexGrid to draw cells. It has bunch of methods that you can override so that you can implement your own drawing mechanism. In our case, when it create the cell content, I need to check if the content is a group row, then add my own cell creating logic put check box else let the grid do its work. So we create our custom class off of CellFactory. All the code you are going to see below is stripped down version of Component One’s iTune sample code.

public class MyCellFactory:CellFactory
{
}

The method we need to override to achieve custom cell content create is ‘CreateCellContent’ method. In this method, we will check, if the cell belongs to a group row and if it is first column then create me the custom cell.

static Thickness _emptyThickness = new Thickness(0);

//bind a cell to a range
 public override void CreateCellContent(C1FlexGrid grid, Border bdr, CellRange range)
{
     var row = grid.Rows[range.Row];
     var col = grid.Columns[range.Column];
      var gr = row as GroupRow;

      if (gr != null)
          bdr.BorderThickness = _emptyThickness;

      if (gr != null && range.Column == 0)
     {
          BindGroupRowCell(grid, bdr, range);
          return;
      }
      base.CreateCellContent(grid, bdr, range);
}


One point of interest in the above method call is ‘BindGroupRowCell’. This method will create the custom cell for us. This has two important parts in it, first one is to make sure the group row has a databinding source and also create custom cell and add it to its cell content.

private void BindGroupRowCell(C1FlexGrid grid, Border bdr, CellRange range)
{
     var row = grid.Rows[range.Row];
     var gr = row as GroupRow;

     if (range.Column == 0)
     {
          if (gr.DataItem == null)
         {
              gr.DataItem = BuildGroupDataItem(gr);
         }

        Type cellType = typeof(ParentCell);
        bdr.Child = (ImageCell)new ParentCell(row);

     }
}

In our grid, every row has an associated data row except the group row. Since group row does not have data item, we will, create an temporary data row, that we can use to bind it to the grid. When we group the rows to generate the grouping in data grid, the group knows about all the children of the group. We conveniently pick the first row and use the data to generate the dummy row that we use to bind it to group row.

CustomCellFactoryForTreeView.MainPage.Person BuildGroupDataItem(GroupRow gr)
{
     var gs = gr.GetDataItems().OfType<CustomCellFactoryForTreeView.MainPage.Person>();
     CustomCellFactoryForTreeView.MainPage.Person p = new CustomCellFactoryForTreeView.MainPage.Person();
      if (gs != null && gs.Count() > 0)
            p = gs.ElementAt(0) as CustomCellFactoryForTreeView.MainPage.Person;
       return new CustomCellFactoryForTreeView.MainPage.Person()
      {
            ParentID = p.ParentID,
            Description = p.Description,
            ChildID = p.ChildID,
            ChildDescription = p.ChildDescription
       };
 }

Now lets look at the main core functionality to  create the check box and text in the group row. Again following code is purely stripped down version itunes sample in C1 samples.  Lets first create the ParentCell

    public  class ParentCell : ImageCell
    {
        const double ALPHA = 0.5;
        GroupRow _gr;
        Image _nodeImage;
        static ImageSource _bmpExpanded, _bmpCollapsed;

        public ParentCell(Row row)
            : base(row)
        {
            CustomCellFactoryForTreeView.MainPage.Person per = row.DataItem as CustomCellFactoryForTreeView.MainPage.Person;
            if (per != null && per.ChildID != null)
            {
                // create collapsed/expanded images
                if (_bmpExpanded == null)
                {
                    _bmpExpanded = ImageCell.GetImageSource("Expanded.png");
                    _bmpCollapsed = ImageCell.GetImageSource("Collapsed.png");
                }

                // store reference to row
                _gr = row as GroupRow;

                // initialize collapsed/expanded image
                _nodeImage = new Image();
                _nodeImage.Source = _gr.IsCollapsed ? _bmpCollapsed : _bmpExpanded;
                _nodeImage.Width = _nodeImage.Height = 9;
                _nodeImage.VerticalAlignment = VerticalAlignment.Center;
                _nodeImage.Stretch = Stretch.None;
                _nodeImage.MouseLeftButtonDown += img_MouseLeftButtonDown;
                _nodeImage.MouseEnter += img_MouseEnter;
                _nodeImage.MouseLeave += img_MouseLeave;
                _nodeImage.Opacity = ALPHA;
                Children.Insert(0, _nodeImage);
            }
            // make text bold
            TextBlock.FontWeight = FontWeights.Bold;
        }
        void img_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            var img = sender as Image;
            var cell = img.Parent as NodeCell;
            cell.IsCollapsed = !cell.IsCollapsed;
        }
        void img_MouseEnter(object sender, MouseEventArgs e)
        {
            var img = sender as Image;
            img.Opacity = 1;
        }
        void img_MouseLeave(object sender, MouseEventArgs e)
        {
            var img = sender as Image;
            img.Opacity = ALPHA;
        }
        public override Row Row
        {
            set
            {
                // update image
                _gr = value as GroupRow;
                _nodeImage.Source = _gr.IsCollapsed ? _bmpCollapsed : _bmpExpanded;
                _nodeImage.Opacity = ALPHA;

                // update text
                base.Row = value;
            }
        }
        public bool IsCollapsed
        {
            get { return _gr.IsCollapsed; }
            set
            {
                _gr.IsCollapsed = value;
                _nodeImage.Source = value ? _bmpCollapsed : _bmpExpanded;
            }
        }
    }

The point of interest in here is the constructor, where we check if there are any children available. If there is any child available, then we show expand or collapse icon. If there are no children then do not show any icon. We also add event handler which listens to the click event on the image and based on the event, it toggles the state. With this change, we took care the icon to show only when there is children. One thing left to do is, adding check box to the control. This is accomplished at base class ‘ImageCell’.

The Image cell is derived from StackPanel so it is easy to customize it to the look you want.

    public abstract class ImageCell:StackPanel
    {
        public ImageCell(Row row)
        {
            Orientation = System.Windows.Controls.Orientation.Horizontal;

            CheckBox box = new CheckBox();
            box.VerticalAlignment = System.Windows.VerticalAlignment.Center;
            box.Click += new RoutedEventHandler(box_Click);
            Children.Add(box);

            TextBlock tb = new TextBlock();
            tb.VerticalAlignment = System.Windows.VerticalAlignment.Center;
            Children.Add(tb);

            BindCell(row.DataItem);
        }

        void box_Click(object sender, RoutedEventArgs e)
        {
            int k = 0;
        }

        public TextBlock TextBlock
        {
            get { return Children[Children.Count - 1] as TextBlock; }
        }

        public CheckBox CheckBox
        {
            get
            {
                return Children[Children.Count - 2] as CheckBox;
            }
        }

        public virtual Row Row
        {
            set { BindCell(value.DataItem); }
        }

        private void BindCell(object dataItem)
        {
            var binding = new Binding("Description");
            binding.Source = dataItem;
            TextBlock.SetBinding(TextBlock.TextProperty, binding);
            var cbbinding = new Binding("TwoState");
            cbbinding.Source = dataItem;
            CheckBox.SetBinding(CheckBox.IsCheckedProperty, cbbinding);
        }

        public static ImageSource GetImageSource(string imageName)
        {
            var bmp = new BitmapImage();
            bmp.CreateOptions = BitmapCreateOptions.None;
            var fmt = "Resources/{0}";
            bmp.UriSource = new Uri(string.Format(fmt, imageName), UriKind.Relative);
            return bmp;
        }
    }

The final result of course is like the following

image

Our requirement is to have a check box and a text block in the group row and that is what we did in the constructor of the ImageCell. Once it is created we need to bind it to the proper fields. That’s is what done in the BindCell method. One thing, it is important here on how you get to the control for doing the binding. Please see the TextBlock getter property, which in turn take the first child from the children collection and return it for binding. If you remember the children have three elements and they are in the following order. First element (at 0) is image for collapse and expand. Second one is the check box (at 1) and third one is the TextBlock (at 2). I was so lazy to I hard coded it to get the elements by hard coded index. You can traverse and identify the control to make it flexible if you want to move around the control. One another thing, you may want is to listen to the check box click event that you can listen with click event.

I would like to thank Bernardo @ Component One to take time to answer my questions and excellent example of itunes on their web site. This three part blog was more for me to understand how the whole components works together and what you can do with this. You can customize the grid any way you want. In this whole example we looked at only one method, create cell, but there are five or six more methods available completely change the look of the grid.

Creating TreeView using Grid in Silverlight (Component One – Cell Factory) – II

[Component one already have a new attribute in C1FlexGrid ‘ ChildItemsPath="Children" which will create the tree view look and feel without any of this custom cell factory. I did it anyway as a means to learn]

In the previous blog post we looked the problem at hand. In this post I am going to go through steps on how to get as close as  to the tree view using C1FlexGrid. I approached the problem using their iTunes sample code. They have a very good online documentation on what is in iTunes sample. 

1. I am going to change the record layout to meet grid grouping model. So my new person class is something like the following

public class Person:INotifyPropertyChanged
 {
      public string ParentID { get; set; }
      public string Description { get; set; }
      public string ChildID { get; set; }
      public string ChildDescription { get; set; }
      public bool TwoState { get; set; }

       public Person()
       {
            TwoState = true;
        }

       public event PropertyChangedEventHandler PropertyChanged;
       private void NotifyPropertyChanged(string info)
       {
             if (PropertyChanged != null)
            {
                 PropertyChanged(this, new PropertyChangedEventArgs(info));
             }
        }
}


2. Since we need to do grouping I am changing my data collection to PagedCollectionView and load some data like the following;

private PagedCollectionView LoadData()

{

       List<Person> ppl = new List<Person>();

       ppl.Add(new Person() { ParentID = "1", Description = "Parent1", ChildID = null, ChildDescription = null });

       for (int i = 0; i < 2000;i++ )

            ppl.Add(new Person() { ParentID = "2", Description = "Parent2", ChildID = "20"+i.ToString(), ChildDescription = "Desc 20"+i.ToString() });

       for (int i = 0; i < 2000; i++)

            ppl.Add(new Person() { ParentID = "3", Description = "Parent3", ChildID = "30"+i.ToString(), ChildDescription = "Desc 31"+i.ToString() });

       ppl.Add(new Person() { ParentID = "4", Description = "Parent4", ChildID = "40", ChildDescription = "Desc 40" });

       ppl.Add(new Person() { ParentID = "4", Description = "Parent4", ChildID = "41", ChildDescription = "Desc 41" });

       return new PagedCollectionView(ppl);

}


3. Before we bind the data to the grid, we will create the grouping up front based on parent id.

using (People.DeferRefresh())
{
     People.GroupDescriptions.Clear();
     People.GroupDescriptions.Add(new PropertyGroupDescription("ParentID"));
}
_grid.ItemsSource = People;


4. Now to render data, we go to XAML

<c1:C1FlexGrid x:Name="_grid" AutoGenerateColumns="False" HeadersVisibility="None">
    <c1:C1FlexGrid.Columns>
       <c1:Column Header="Data">
             <c1:Column.CellTemplate>
                   <DataTemplate>
                         <Grid>
                             <Grid.ColumnDefinitions>
                                 <ColumnDefinition Width="15"/>
                                 <ColumnDefinition Width="15"/>
                                 <ColumnDefinition Width="*"/>
                             </Grid.ColumnDefinitions>
                             <TextBlock Text=" " Grid.Column="0"/>
                             <CheckBox IsChecked="{Binding TwoState}" Grid.Column="1"/>
                             <TextBlock Text="{Binding Converter={StaticResource Description}}" Grid.Column="2"/>
                        </Grid>
                    </DataTemplate>
                </c1:Column.CellTemplate>
            </c1:Column>
        </c1:C1FlexGrid.Columns>
</c1:C1FlexGrid>


In the above code, we have a converter in place. It simply check if the child description has value then use it, if not, use parent description.

public class DescriptionConverter:IValueConverter
    {

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            CustomCellFactoryForTreeView.MainPage.Person per = value as CustomCellFactoryForTreeView.MainPage.Person;
            return (per.ChildDescription != null) ? per.ChildDescription : per.Description;
        }

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

5. Since we want to grid to look like TreeView, I removed the column and row heading with

HeadersVisibility="None"

in the C1FlexGrid attribute.

So far, we did nothing that important. This is simple data binding with the grouping. Now if you would run the code as it is, what you get is a grid with grouping applied to parent id.

image

As you would notice the first group does not have any children. Since we used converters, it puts parent’s description in the child row. But if you would use TreeView, if the parent does not have a child then it will not show child row to start with. We can fix that easily with the following hack

foreach (Row row in _grid.Rows)
{   
 if (row.DataItem != null)    
 {      
   Person p = row.DataItem as Person;      
   if (p.ChildID == null)        
      row.Height = 0;  
 }
}

Now if you would run, you will see the child is hidden from the list.

image

We got the children to show up properly. The next step to make it like tree view is to change the parent id to show a check box and parent id information. Also remove the total number of items in parentheses.

One thing that we can easily resolve in the current model is changing the group header to show parent ID instead of all these three values. This can be done by implementing a groupheader converter as follows

_grid.GroupHeaderConverter = new GroupHeaderConverter();


and the group header conversion is nothing but a normal converter. Which check and see if the row is a group row then just return group name nothing else

class GroupHeaderConverter:IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            // return group name only (no counts)
            var group = value as CollectionViewGroup;
            if (group != null && targetType == typeof(string))
            {
                return group.Name;
            }

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

Now if you would run the result would be one step closer to where we need to be

image

Now what we need to add check box to the parent node then we are all set.

Creating TreeView using Grid in Silverlight (Component One – Cell Factory) – I

[Component one already have a new attribute in C1FlexGrid ‘ ChildItemsPath="Children" which will create the tree view look and feel without any of this custom cell factory. I did it anyway as a means to learn.]

Silverlight 4 tree view have one performance problem, when you try to expand a node with 1000 odd children, it takes close to 10 to 14 second to load. But it is only the initial cost, once the data loaded into memory, when you expand or collapse the same node it expands quickly. If you have many number of nodes with 1000s of children then you are going incur the cost of each node expansion. As you can imagine it does not go very well with the end users. For starters, it will not go past QA when they see that bad of performance. So I was thinking about adopting data grid to display the same content, yet make it look like tree view. The reason I chose grid instead of any other controls is virtualization. I tested a simple grid with same amount of data in it and it was simply amazing. It was so fast. You can’t time it since it expand and collapse instantly.

I have been using Component One control lately for our development and I very comfortable using it. I decided to develop the grid using C1FlexGrid and one of its power feature called cell factory. I never really appreciated the full power of  till I attempted to solve my problem using cell factory. I decided to blog about it as a document of this feature and as a reference for future. The best reference if you want to learn how the cell factory can be used, I would recommend you to get to Component One web site and look for Silverlight FlexGrid samples. There is also a PDF documentation explaining in detail how to use cell factory with an iTunes like grid.

Ok, that is enough talk, lets see the problem at hand. Let me first show, what am I trying to solve and then we see the code.

image

image

The first picture shows the parents collapsed and the second picture shows the first parent expanded. To make this exercise simple, I have only two level. There are 1000 parents and every even parent have 500 children. Lets look at the class definition for the collection. The class is called Person and shown as follows

 1: public class Person:INotifyPropertyChanged
 2: {
 3:     public string Name { get; set; }
 4:     public int Age { get; set; }
 5:     public bool TwoState { get; set; }
 6:     public ObservableCollection<Person> Children { get; set; }
 7:    8:     public Person()
 9:     {
 10:         TwoState = false;
 11:         Children = new ObservableCollection<Person>();
 12:     }
 13:    14:     public event PropertyChangedEventHandler PropertyChanged;
 15:     private void NotifyPropertyChanged(string info)
 16:     {
 17:         if (PropertyChanged != null)
 18:        {
 19:             PropertyChanged(this, new PropertyChangedEventArgs(info));
 20:        }
 21:     }
 22: }

Following code load the data to be displayed on the treeview

 1: private void LoadPeople()
 2: {
 3:      _people = new List<Person>();
 4:      for (int i = 0; i < 1000; i++)
 5:      {
 6:           Person p1;
 7:           if (i % 2 == 0)
 8:          {
 9:               p1 = new Person() { Name = "parent" +i.ToString(), Age = 20 };
 10:               for (int j = 0; j < 500; j++)
 11:                  p1.Children.Add(new Person() { Name = "Child" + j.ToString(), Age = 10 });
 12:          }
 13:          else
 14:          {
 15:              p1= new Person() { Name = "Parent" + i.ToString(), Age = 20 };
 16:          }
 17:          _people.Add(p1);
 18:     }
 19: }

As one of my friend pointed out, it is better to use string.format rather than ‘+’ in string operations. Now lets look at the XAML which does the actual work of showing the data

 1: <controls:TreeView Grid.Column="1" VerticalAlignment="Stretch" VirtualizingStackPanel.VirtualizationMode="Standard"
 2:                                    ItemsSource="{Binding People}" >
 3:      <controls:TreeView.ItemTemplate>
 4:           <common:HierarchicalDataTemplate ItemsSource="{Binding Children}">
 5:                 <StackPanel>
 6:                     <Grid>
 7:                         <Grid.ColumnDefinitions>
 8:                             <ColumnDefinition Width="10*"/>
 9:                             <ColumnDefinition Width="90*"/>
 10:                          </Grid.ColumnDefinitions>
 11:                          <CheckBox IsChecked="{Binding TwoState}" Grid.Column="0"/>
 12:                             <TextBlock Grid.Column="1" Text="{Binding Name}"/>
 13:                       </Grid>
 14:                  </StackPanel>
 15:            </common:HierarchicalDataTemplate>
 16:        </controls:TreeView.ItemTemplate>
 17: </controls:TreeView>

With these three set of code, I am able to generate the tree view using toolkit tree view control. When you run the code initial load takes almost 30 to 35 seconds to load and then each child expand and collapse takes around 10 to 15 seconds to load. Once children are loaded in memory, future expand and collapse are very fast. The main problems are initial load and time to expand each node.

Now we know the problem at hand, we can look at how we can use C1FlexGrid to create tree view look and feel.