Accelerated Development

March 13, 2009

A Custom Silverlight List – Part V, Implementing A Sorted List View

Filed under: Silverlight, Uncategorized — Tags: , , , , , — mwohltman @ 7:58 pm

Overview:

So while I was trying to validate my List design I started thinking about other things I could do with a List.  Obviously I could add selection but that just seemed boring.  I happened to be watching some NASCAR at the time and an idea popped in my head.  If I were writing an application with real time race information, I would really need to sort some data.  Further, since this data would be highly dynamic it wouldn’t be viable to rely on the data source to sort this data either.  Additionally, we may want to display two different views of the same data.  For race information, one view might sort by current race position while another view might sort by current speed or time of the driver’s last lap.  So I had my answer.  How could I develop a Sorted List View on top of my current List infrastructure? 

 

Simplifying our Use Case

With some thought on what would be involved to accomplish this goal, I immediately started trying to simplify my problem.  First, how would I do my sorting?  I think a tree would be most appropriate but I don’t need to be efficient on my first pass.  I am not going to implement a binary tree, but I will make sure it will be straightforward replace my linear sort/search with a logarithmic algorithm at a later time.  Secondly, will I support an observable list?   This is actually a pretty interesting question.  At first thought, it sounds like an important feature but note that I am not talking about listening for value changes I am talking about list changes.  I will obviously want to watch for data changes in the list, but do I need to watch for list changes?  After more thought I concluded that 1) it wasn’t needed for my use case (new drivers won’t enter the race) and 2) I’m not even sure it’s viable for the general scenario and 3) I already implemented binding to an observed list so the excitement was gone J.  Ok, Again, I will not implement binding to an observed list, but I will make sure I know where to place this logic when I add it later.  Finally, will I queue updates or handle them synchronously?  Same decision – I will defer until later.

 

Design:

So I looked at the components I needed to develop and might develop in the future and came up with the following design.

Sorted List Class Diagram

Sorted List Class Diagram

 

SortedList

The SortedList class will inherit from my existing ListBase class.  The ListBase class provides ItemType initialization without doing any binding.  The SortedList defines the following properties. 

1)      ItemsSource – bound list of items

2)      SortProperty – Property to sort by

3)      SortOrder – Asc or Desc?

4)      Top – How many items to display

The SortedList class will implement Top functionality as well as maintaining the displayed list items.  In the future it will also implement binding to an observable collection.  It will not do any sorting itself.  This class will rely on a SortedListManager to maintain sorted items.  I put Add and Remove methods on the SortedListManager.  Currently, I just call Add, but when I implement an observed binding, I will have to utilize remove as well. 

 

SortedListManager

The SortedListManager class maintains the sorted order of items in the source list.  In the future, this class will also implement queuing before doing the re-sort.  It will also be updated to a logarithmic algorithm.  The SortedListManager will communicate updates to the sorted order of items via a ListUpdated event that the SortedList will listen to and handle.  To monitor the individual list items, the SortedListManager will attach listeners to the individual objects INotifyPropertyChanged interfaces.

 

DataObjectWatcher

The DataObjectWatcher listens for updates to the individual data objects.  It does this by binding to the objects INotifyPropertyChanged interface.  When I first wrote this class it just had a SourceIndex property which was the index of the watched object in the source array.  This way when I received the PropertyChanged event, I would know exactly which item changed.  As I got further with development this object changed completely so I would like to spend some time discussing the important properties in detail.

KnownValue : I added the KnownValue attribute on this class to keep a local copy of the data objects sorted property value.  This property isn’t so useful now but when I implement event queuing this will be an extremely important attribute.  When we have multiple pending updates to re-sort, it will be an error to always sort against other data objects current values because those values may themselves be a pending update.  Sorting against current values could lead to incorrectly ordering the items.

DataObject:    I’ll be perfectly honest; I wasn’t thrilled about having this property here!  While developing the SortedList I realized that it was going to be important to have the SortedList class “own” the original list.  By this, I mean that the SortedList was going to monitor the source list for changes and then call appropriate Add/Remove methods on the SortedListManager.  This way I simplified the SortedListManager class.  It also occurred to me that this not only simplified the SortedListManager but it also added to the overall functionality by allowing for use cases which don’t actually include a source list!  Now, at that time I could have asserted for non source list scenario’s that the SortedList class would create its own internal List but that would create unnecessary complexity in the SortedList class.  I decided that having the DataObject Property here, not a ‘SourceIndex’ property into the original list, was the best solution.

Implementation (top down)

SortedList

Start with the skeleton including dependency properties:

  public enum SortOrder { Asc, Desc };

 

  public class SortedList<ItemType, DataObjectType> : ListBase<ItemType, DataObjectType>

    where ItemType : FrameworkElement, new()

    where DataObjectType : INotifyPropertyChanged

  {

    #region Members

    private SortedListManager<DataObjectType> _manager;

    #endregion

 

    #region Dependency Properties

    public static readonly DependencyProperty SortPropertyProperty;

    public static readonly DependencyProperty SortOrderProperty;

    public static readonly DependencyProperty ItemsSourceProperty;

    public static readonly DependencyProperty TopProperty;

 

    public SortOrder SortOrder

    {

      get { return (SortOrder)GetValue(SortOrderProperty); }

      set { SetValue(SortOrderProperty, value); }

    }

    public string SortProperty

    {

      get { return (string)GetValue(SortPropertyProperty); }

      set { SetValue(SortPropertyProperty, value); }

    }

 

    public int Top

    {

      get { return (int)GetValue(TopProperty); }

      set { SetValue(TopProperty, value); }

    }

 

    public IList<DataObjectType> ItemsSource

    {

      get { return (IList<DataObjectType>)GetValue(ItemsSourceProperty); }

      set { SetValue(ItemsSourceProperty, value); }

    }

 

    #endregion

 

    #region Constructor

    public SortedList()

    {

    }

 

    static SortedList()

    {

      SortPropertyProperty = DependencyProperty.Register(

        “SortProperty”, typeof(string),

        typeof(SortedList<ItemType, DataObjectType>), null);

 

      ItemsSourceProperty = DependencyProperty.Register(

        “ItemsSource”, typeof(IList<DataObjectType>),

        typeof(SortedList<ItemType, DataObjectType>), null);

 

      SortOrderProperty = DependencyProperty.Register(

        “SortOrderProperty”, typeof(SortOrder),

        typeof(SortedList<ItemType, DataObjectType>), null)

 

      TopProperty = DependencyProperty.Register(

        “TopProperty”, typeof(int),

        typeof(SortedList<ItemType, DataObjectType>),

        new PropertyMetadata(-1));

 

    }

 

    #endregion

 

Then Include Some Initialization Logic:

public override void OnApplyTemplate()

{

  base.OnApplyTemplate();

 

  Initialize();

}

 

 

private void Initialize()

{

  _manager = new SortedListManager<DataObjectType>(SortProperty, SortOrder);

  _manager.ListUpdated += new ListEventHandler<DataObjectType>(_manager_ListUpdated);

 

  if (null != base.Panel)

  {

    base.Clear();

 

    for ( int i = 0; i < ItemsSource.Count; ++ i )

      _manager.AddItem(ItemsSource[i]);

  }

}

 

And finally logic to handle list changed events:

 

   void _manager_ListUpdated(object sender, ListEventArgs<DataObjectType> args)

    {

      switch (args.EventType)

      {

        case ListEventType.Insert:

 

          HandleInsert(args);

          break;

 

        case ListEventType.Move :

 

          HandleMove(args);

          break;

      }

    }

 

    private void HandleInsert(ListEventArgs<DataObjectType> args)

    {

      if (Top < 0) // top not used

        base.InsertItem(args.NewIndex, args.DataObject);

      else

      {

        if (args.NewIndex < Top) // insert into top

        {

          base.InsertItem(args.NewIndex, args.DataObject);

 

          if (Count > Top)      // remove old top

            RemoveAt(Top);

        }

      }

    }

 

    private void HandleMove(ListEventArgs<DataObjectType> args)

    {

      if (Top < 0)  // Top not used

        base.Move(args.OldIndex, args.NewIndex);

      else

      {

 

        if (args.NewIndex < Top)  // Moved into top

        {

          if (args.OldIndex < Top)   // From within top

          {

            base.Move(args.OldIndex, args.NewIndex);

            return;

          }

          else  // From outside top

          {

            InsertItem(args.NewIndex, _manager[args.NewIndex]);

            if (Count > Top)

              RemoveAt(Top);

          }

        }

        else if (args.OldIndex < Top) // Moved out of top

        {

          base.RemoveAt(args.OldIndex);

 

          if (_manager.Count >= Top)

            InsertItem(Top – 1, _manager[Top - 1]);

        }

 

      }

    }

 

 

Sorted List Manager: ( a linear implementation)

 

  public class SortedListManager<DataObjectType>

    where DataObjectType : INotifyPropertyChanged

  {

    private delegate bool LessThanDelegate(IComparable a, IComparable b);

 

    #region members

    private PropertyInfo _propertyInfo;

    private LessThanDelegate _comparer;

    private List<DataObjectWatcher<DataObjectType>> _list;

    #endregion

 

    #region Events

    public event ListEventHandler<DataObjectType> ListUpdated;

    #endregion

 

    #region Properties

    public int Count

    {

      get { return _list.Count; }

    }

 

    public DataObjectType this[int index]

    {

      get { return _list[index].DataObject; }

    }

    #endregion

 

    #region Constructor

    public SortedListManager( string sortProperty, SortOrder order)

    {

 

      _propertyInfo = typeof(DataObjectType).GetProperty(sortProperty);

      _list = new List<DataObjectWatcher<DataObjectType>>();

 

      if (SortOrder.Asc.Equals ( order ))

        _comparer = delegate(IComparable a, IComparable b)

          {

            return (a.CompareTo(b) <= 0);

          };

      else

        _comparer = delegate(IComparable a, IComparable b)

        {

          return (a.CompareTo(b) >= 0);

        };

 

    }

    #endregion

 

    #region List Updates

    public void AddItem(/*int index,*/ DataObjectType dataObject)

    {

      DataObjectWatcher<DataObjectType> watcher =

        new DataObjectWatcher<DataObjectType>( _propertyInfo.Name,dataObject);

 

      watcher.KnownValue = (IComparable)_propertyInfo.GetValue(dataObject,null);

     

      watcher.DataObjectUpdated +=

        new DataObjectChangeHandler(watcher_DataObjectUpdated);

 

      DataObjectWatcherInserted(watcher);

    }

 

    public void RemoveItem(int sourceIndex)

    {

    }

    #endregion

 

    #region Data Object Updates

    void watcher_DataObjectUpdated(object sender)

    {

      DataObjectWatcherUpdated (( DataObjectWatcher<DataObjectType>) sender);

    }

 

    private void DataObjectWatcherInserted(DataObjectWatcher<DataObjectType> watcher)

    {

      lock (this)

      {

        int index = FindIndex ( watcher.KnownValue);

 

        _list.Insert(index, watcher);

        ListUpdated(this,

          new ListEventArgs<DataObjectType>(

            ListEventType.Insert, watcher.DataObject, -1, index));

 

      }

    }

 

    private int FindIndex(IComparable knownValue)

    {

      int index = 0;

      for (index = 0; index < _list.Count; ++index)

      {

        if (_comparer(knownValue, _list[index].KnownValue))

          break;

      }

 

      return index;

    }

 

    private void DataObjectWatcherUpdated(DataObjectWatcher<DataObjectType> watcher)

    {

      lock (this)

      {

        watcher.KnownValue =

          (IComparable)_propertyInfo.GetValue( watcher.DataObject,null);

 

        int sourceIndex = _list.IndexOf(watcher);

        _list.RemoveAt(sourceIndex);

 

        int destinationIndex = FindIndex(watcher.KnownValue);

 

        _list.Insert(destinationIndex, watcher);

        ListUpdated(this,

          new ListEventArgs<DataObjectType>(

            ListEventType.Move, watcher.DataObject, sourceIndex, destinationIndex));

      }

    }

    #endregion

 

  }

 

 

DataObjectWatcher

 

  public delegate void DataObjectChangeHandler (object sender );

 

  public class DataObjectWatcher<DataObjectType>

    where DataObjectType : INotifyPropertyChanged

  {

    #region Members

    private string _monitoredProperty;

    #endregion

 

    #region Properties

 

    public IComparable KnownValue { get; set; }

    public DataObjectType DataObject { get; set; }

 

    #endregion

 

    #region Events

    public event DataObjectChangeHandler DataObjectUpdated;

    #endregion

 

    #region Constructors

 

    public DataObjectWatcher( string monitoredProperty, DataObjectType dataObject)

    {

      DataObject = dataObject;

      _monitoredProperty = monitoredProperty;

 

      dataObject.PropertyChanged +=

        new PropertyChangedEventHandler(dataObject_PropertyChanged);

    }

    #endregion

 

    void dataObject_PropertyChanged(object sender, PropertyChangedEventArgs e)

    {

      if (e.PropertyName == _monitoredProperty)

      {

        if (null != DataObjectUpdated)

          DataObjectUpdated(this);

      }

    }

  }

 

And some miscellaneous classes

 

  public enum ListEventType { Insert, Remove, Move };

  public class ListEventArgs<DataObjectType>

  {

    public ListEventArgs()

    {

    }

 

    public ListEventArgs(ListEventType eventType,

      DataObjectType dataObject, int oldIndex, int newIndex)

    {

      EventType = eventType;

      DataObject = dataObject;

      OldIndex = oldIndex;

      NewIndex = newIndex;

    }

 

    public ListEventType EventType { get; set; }

    public int OldIndex { get; set; }

    public int NewIndex { get; set; }

    public DataObjectType DataObject{ get; set; }

  }

 

  public delegate void ListEventHandler<DataObjectType>(

    object sender, ListEventArgs<DataObjectType> args);

 

 

 

 

March 12, 2009

A Custom Silverlight List – Part IV, Evaluation

Filed under: Silverlight, Uncategorized — Tags: , , — mwohltman @ 4:40 pm

So when I got to this point with my List I took a step back and asked myself a couple questions. 

1)      Did I reach my goal

2)      What doesn’t my List support

3)      Will my list be easy to use

4)      Do I even like it

5)      Will my list be easy to expand/update with other complex needs

 

Did I reach my goal?

YES!  The VisualListBase class provides an indexer property for access to our item controls. 

 

What doesn’t my list support?

At this point, the List still does not support selection.  I spoke about this feature in my first post and have a strategy to implement it.  I should note, I haven’t implemented selection but I may go back and make it a topic for a future post. 

My list also does not inherently support scrolling or a default Panel type.  This is probably the major disadvantage to using my list.  With the ListBox, you don’t have to provide a template for the List.   If you don’t provide a custom ItemsPanel the ListBox will inject a vertical StackPanel with a Scrollbar.  I thought about doing this because let’s face it; it will save time each time I use my list.  In the end, however, I decided I didn’t like polluting my List class with extra code when defining a template is a very ‘Silverlight-ish’ thing to do.  What I may be able to do though is provide a default template at my base class’s level.  It wasn’t obvious to me how to do this because my type utilizes generics but I suspect the “DefaultStyleKey” property is designed for just this thing.  I will have to revisit!

 

Will my list be easy to use?

As stated above, it will be as easy to use as the ListBox with the disadvantage of (1) having to define our list type and (2) having to define our default template. 

I suppose there is one other disadvantage as well.  Because I couldn’t use a template (DataTemplate, FrameworkTemplate) for my ItemControl an explicit type is needed.  Now your ItemControl can certainly be a template control (I have already used both UserControl’s and custom control’s), but I can’t use ad hoc DataTemplates. 

 

Do I like it?

Yes.  I like it.  Clearly, for fast ad hoc usage the ListBox is still a better choice.  Once I implement selection capabilities my feature set should be the same.  Of course, having a list framework ready to extend with custom logic like selection capabilities when they arrive is certainly useful.

 

Can it be extended?

Stay tuned for my next post …

A Custom Silverlight List – Part III, Using the List

Filed under: Silverlight, Uncategorized — Tags: , , — mwohltman @ 4:08 pm

Developing Our Item Control (Visual Control)

The first thing we want to develop is our Item Control (Visual Control) and our Data Object.  For my test bed, I made these extremely simple.  I did use a custom control with 3 VisualState’s.  After all, the inability to manipulate a control’s VisualState within a ListBox was the main reason for developing my List.  So my simple custom control for our Item type was:

  [TemplateVisualState(GroupName = "Status", Name = "Drafting")]

  [TemplateVisualState(GroupName = "Status", Name = "Drafted")]

  [TemplateVisualState(GroupName = "Status", Name = "Normal")]

  public class MyCustomControl : ContentControl

  {

    public MyCustomControl()

    {

      DefaultStyleKey = typeof(MyCustomControl);

    }

  }

 I also created a default template and put into the resource dictionary.  I not going to show the theme, but remember it has three visual states.

 

Our Data Object

  public class CustomData : INotifyPropertyChanged

  {

    private string _valueString;

    public event PropertyChangedEventHandler PropertyChanged;

 

    public CustomData(string controlText)

    {

      ValueString = controlText;

    }

 

    public string ValueString

    {

      get { return _valueString; }

      set

      {

        if (_valueString != value)

        {

          _valueString = value;

 

          OnPropertyChanged(“ValueString”);

        }

      }

    }

 

    private void OnPropertyChanged(string propertyName)

    {

      if (null != PropertyChanged)

        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

    }

  }

 

 

Defining our List

The next thing we have to do is define our List.  Because of my use of generics, I have to define my list type in code before it can be used in XAML.  It’s also important to remember, that besides defining the type which will usually be extremely straightforward, you will need to also define a default control template.  Also, the control template for our list has to include a Panel with name “Container” which is defined as a template part of our base class (VisualListBase). 

List Definition:

public class MyCustomControlList : BindableList<MyCustomControl, CustomData>

  {

    private int _selectedIndex;

 

    public MyCustomControlList()

    {

      DefaultStyleKey = typeof(MyCustomControlList);

      _selectedIndex = 0;

    }

 

    public void Next()

    {

      if (_selectedIndex < Count)

      {

        _selectedIndex++;

        VisualStateManager.GoToState(

          this[_selectedIndex - 1], “Drafting”, false);

 

        if (_selectedIndex > 1)

          VisualStateManager.GoToState(

            this[_selectedIndex - 2], “Drafted”, false);

      }

    }

  }

 

You obviously won’t usually need the ‘Next’ method, but this demonstrates my lone goal for developing my list.  I have access to the individual Controls within the list, not just the data objects.  Next, I’ll show you the default template I added for the MyCustomControlList type.

  <Style  TargetType=”l:MyCustomControlList”>

    <Setter Property=”Template”>

      <Setter.Value>

        <ControlTemplate>

          <StackPanel x:Name=”Container” Orientation=”Vertical”

              Background=”{TemplateBinding Background}”

              Height=”{TemplateBinding Height}”

              Width=”{TemplateBinding Width}”

              Margin=”{TemplateBinding Margin}”

                       

                        />

 

        </ControlTemplate>

      </Setter.Value>

    </Setter>

  </Style>

 

So the magic is done and we are free to add our list into XAML with the following definition:

<l:MyCustomControlList x:Name=”MyControlList”

    ItemsSource=”{Binding Items, Source={StaticResource State}}” />

 

 

A Custom Silverlight List – Part II, Design & Implementation

Filed under: Silverlight, Uncategorized — mwohltman @ 12:52 am

Design

Designing a list doesn’t require tremendous thought but there were some things worth considering.  Primarily, what abstractions could be made?  For your basic list supporting databinding, I see three main pieces of functionality.

1)      Adding and the visual components

2)      Initializing List Items from Data Objects

3)      Binding to our ItemsSource property and maintaining our list based on modifications to our source list

Looking at this list, I decided to build a class hierarchy abstracting each “level” of functionality.  I came up with the following:

 

 

 

Bindable List Class Diagram

Bindable List Class Diagram

 

 

Here I used Generic classes with the Types: ItemType & DataObjectType.  DataObjectType is the type of object in the ItemsSource collection.  ItemType is the visual control added to the list.  Everything else seems pretty straightforward.  The VisualListBase abstracts adding and removing items from the List.  ListBase abstracts initializing the ItemType instance for each Data Object and BindableList listens for updates to the ItemsSource property. 

 

Implementation

Most of the implementation is pretty boring.  In fact, after the design was in place the code mostly wrote itself.  There were, however, some key details

 

VisualListBase:

The VisualListBase class is the collection of visual controls that get displayed.  To make the most flexible list and provide the same template capabilities of the ListBox I realized I needed to use a ContentControl with a TemplatePart to identify the Panel that would hold the items.  The class then started as :

  [TemplatePart(Name = "Container", Type = typeof(Panel))]

  public class VisualListBase<ItemType> : ContentControl

    where ItemType : FrameworkElement

  {

    protected Panel Panel { get; private set; }

    protected void InitializeVisualList()

    {

      Panel = GetTemplateChild(“Container”) as Panel;

    }

 

    public override void OnApplyTemplate()

    {

      base.OnApplyTemplate();

      InitializeVisualList();

    }

}

 

From here I filled in the list modification routines:

   public void Add(ItemType item)

    {

      Panel.Children.Add(item);

    }

 

    public void RemoveAt(int index)

    {

      Panel.Children.RemoveAt(index);

    }

 

    public void Insert(int index, ItemType item)

    {

      Panel.Children.Insert(index, item);

    }

 

    public void Move(int sourceIndex, int destinationIndex)

    {

      ItemType item = (ItemType)Panel.Children[sourceIndex];

 

      Panel.Children.RemoveAt(sourceIndex);

      Panel.Children.Insert(destinationIndex, item);

    }

 

    public void Clear()

    {

      Panel.Children.Clear();

    }

And finally some helpful list properties.

    public int Count

    {

      get {return Panel.Children.Count; }

    }

 

    public ItemType this[int index]

    {

      get { return (ItemType)Panel.Children[index];}

    }

 

 

See what I mean?  Code wrote itself …

 

ListBase

The ListBase class extends the VisualListBase class and provided routines to initialize a ItemType (the visual control). To that end, it provides a DependencyProperty ItemStyle.  The ItemStyle property allows for styling and templeting of the ItemType.  The class started as :

 

 

  public class ListBase<ItemType, DataObjectType> : VisualListBase<ItemType>

      where ItemType : FrameworkElement, new()

  {

    public static readonly DependencyProperty ItemStyleProperty;

 

    public Style ItemStyle

    {

      get { return (Style)GetValue(ItemStyleProperty); }

      set { SetValue(ItemStyleProperty, value); }

    }

 

    public ListBase()

    {

    }

 

    static ListBase()

    {

      ItemStyleProperty = DependencyProperty.Register(“ItemStyle”, typeof(Style), typeof(ListBase<ItemType, DataObjectType>), null);

    }

}

 

And I then add the ItemType initialization code:

 

    #region Item Initialization

    protected virtual ItemType InitializeNewItem(DataObjectType dataObject)

    {

      ItemType item = new ItemType();

      item.Style = ItemStyle;

      item.DataContext = dataObject;

 

      return item;

    }

    #endregion

 

    #region List Modifications

 

    protected virtual void InsertItem(int index, DataObjectType dataObject)

    {

      ItemType item = InitializeNewItem(dataObject);

      base.Insert(index, item);

    }

 

    protected virtual void AddItem(DataObjectType dataObject)

    {

      ItemType item = InitializeNewItem(dataObject);

      base.Add(item);

    }

 

 

    #endregion

Again, pretty straightforward.

 

BindableList

The BindableList extends ListBase and adds logic to monitor its ItemsSource DependencyProperty.  The class started as :

 

public class BindableList<ItemType, DataObjectType>

    : ListBase<ItemType, DataObjectType>

    where ItemType : FrameworkElement, new()

  {

    public static readonly DependencyProperty ItemsSourceProperty;

 

    public BindableList() {}

 

    static BindableList()

    {

      ItemsSourceProperty = DependencyProperty.Register(“ItemsSource”,

        typeof(IEnumerable<DataObjectType>),

        typeof(BindableList<ItemType, DataObjectType>),

        new PropertyMetadata(null,

          new PropertyChangedCallback(ItemsSourceChanged)));

    }

 

    public IEnumerable<DataObjectType> ItemsSource

    {

      get { return (IEnumerable<DataObjectType>)GetValue(ItemsSourceProperty); }

      set { SetValue(ItemsSourceProperty, value); }

    }

}

 

I then added code to handle changes in the ItemSource property:

    private static void ItemsSourceChanged(

      DependencyObject dependencyObject,

      DependencyPropertyChangedEventArgs args)

    {

      BindableList<ItemType, DataObjectType> list =

        (BindableList<ItemType, DataObjectType>)dependencyObject;

 

      if (args.NewValue is ObservableCollection<DataObjectType>)

      {

        ObservableCollection<DataObjectType> oc =

          (ObservableCollection<DataObjectType>)args.NewValue;

 

        oc.CollectionChanged += new NotifyCollectionChangedEventHandler(

          list.oc_CollectionChanged);

      }

 

      list.Initialize();

    }

 

    public void oc_CollectionChanged(object sender,

      NotifyCollectionChangedEventArgs e)

    {

      switch ( e.Action )

      {

        case NotifyCollectionChangedAction.Add :

 

          for (int i = 0; i < e.NewItems.Count; ++i)

            InsertItem(e.NewStartingIndex + i, (DataObjectType)e.NewItems[i]);

 

          break;

 

        case NotifyCollectionChangedAction.Remove :

 

          for (int i = 0; i < e.OldItems.Count; ++i)

            RemoveAt(e.OldStartingIndex + i);

 

          break;

 

        case NotifyCollectionChangedAction.Replace :

 

          RemoveAt(e.NewStartingIndex);

          InsertItem(e.NewStartingIndex, (DataObjectType)e.NewItems[0]);

          break;

 

        case NotifyCollectionChangedAction.Reset:

 

          Clear();

          break;

      }

    }

 

And finally some initialization code:

    public override void OnApplyTemplate()

    {

      base.OnApplyTemplate();

 

      Initialize();

    }

   

 

    private void Initialize()

    {

      if ((null != base.Panel) & (null != ItemsSource) )

      {

        base.Clear();

 

        foreach (DataObjectType dataObject in ItemsSource)

          AddItem(dataObject);

      }

    }

 

March 11, 2009

A Custom Silverlight List – Part I: Overview, Goal & Concpetual Components

Filed under: Silverlight, Uncategorized — Tags: — mwohltman @ 6:33 pm

 Overview

I was recently working on a daft tool for my Fantasy Baseball League.  I found myself using a lot of lists to display the teams, free agents and selected players.  The first list I was looking at was a list of team names I wanted to stretch from left to right at the top of the page.  I wanted to use this list not just to display the teams in our league, but to also identify which team was currently selecting and what direction the draft was going.  Most fantasy drafts “snake” meaning that the last team to pick in the first round picks first in the second round. 

Now, being a newly equipped Silverlight programmer, I quickly wrote a control to display a team name and gave this control three states: ‘Drafting’, ‘Drafted’, ‘Normal’.  ‘Drafting’ would be for the team currently picking, ‘Drafted’ would be for teams that have selected during the current round and ‘Normal’ would be for teams left to pick.  I then added a ListBox, and created the appropriate DataTemplate.  What I later found out, however, was that I couldn’t obtain a reference to my team control (The control with three visual states) from outside the ListBox.  I needed this reference to pass into the VisualStateManager.GoToState function. 

So at this point, I said ‘shucks’ and just manually added my controls to a StackPanel instead of using the ListBox and DataBinding.  I moved on to another List on my page which was a list of the Teams in our league.  For each team I wanted to display all the players on their roster.  This would be an active list, as their rosters obviously grow during the draft.  Again, I was confronted with the same issue.  I added an “AddPlayer” function to this ‘TeamRoster’ control but could not readily get at the control from outside the List. 

Now I needed to give this some thought!  What I determined was that I decided to use the ListBox because it was the most appropriate databinding control.  I also realized I was not using the ListBox for its intended purpose.  In neither case was I displaying controls for the purpose of selecting an item from the list.  With this enlightenment, I decided it would pay great future dividends to me to design the List that I do want.

 

Goal

When I set out to develop my list class my goal was really quite simple.  Do everything as close to the ListBox as possible while providing indexed access to the visual controls within the list.  I’m using visual control to distinguish between the UIElement placed within the List and the Data Item from the usual ItemsSource databinding property. 

 

Conceptual Components:

I started looking at the Conceptual components that make up a list.  This work was pretty easy because after all, I had the ListBox to look at.  J  I quickly identified the following components.

1)      List Class – the list class (i.e. ListBox)

2)      List Panel – the panel within where the individual items were added

3)      Item Container  - a Container to wrap the visual controls

4)      Item Control – The Visual Control (UIElement) produced for each Data Item

5)      Data Object – The DataContext for the visual control.

 

 

Conceptual Components

Conceptual Components

At this point, the component I was immediately finding both the most interesting and the most irrelevant was the Item Container.  It’s in the Item Container where the ListBox places selection logic.  In the ListBox, we are free to override the appearance for each of the visual states but we cannot add logic or define our own states.  I started thinking about what logic could go in the Container and I came up with very few possibilities.  Logic that applies to the container would be logic that is data/visual control independent.  The best possibilities I came up with, besides selection logic, were visual states for 1) when an Item was inserted/removed from the list and 2) when an item was moved within the list.  Ultimately, I realized that this container was very dynamic.  Selection logic would be highly useful but needs for custom logic would be few and far between.  Because I didn’t need the selection logic right away, I decided I would figure out how it would be implemented and move on.  These were my thoughts:

1)      It would be easy to inject the Container around the ListItem.  Since I manually instantiate and insert the ListItem’s, all I needed to do was make this Initialization logic overridable. 

2)      There would be multiple communication options for calls between the item Container and the List.  The Item Container could provide events the list could subscribe to or the container could even be initialized with a reference to the parent list.  Further, the List would obviously have access to the individual item containers.

Great!  Simple yet even the complex is possible!  I’m moving on …

 

 

 

 

Theme: Silver is the New Black. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.