Professional, Software

Navigation Support for Paperstack Control

Since PaperStack control supports user drilling down , it also need to support stepping out. This paradigm obviously cries for some sort of navigation support. WPF support navigation paradigm and so I decided to implement the navigation support for the control. There are some unique problems that I came across.

  1. Control may or may not be hosted inside navigation container so control needs to be smart enough to act accordingly
  2. Additive Filtering behavior of  ListCollectionView means we need to keep track of all the filter applied to get to current state and reapply them one by one to restore the journal entry
  3. Control is not at the root of the content so IProvideCustomContentState so control needs to use the Navigating Event to store the custom content state
  4. Un hook from navigating event at the right time other control instance wont be GCed since container will hold on to the reference
  5. Page can multiple controls of the same type, that means each journal entry needs to know which control it was associated too. This is done by using Application properties bag. Storing a reference on the properties bag can also lead to memory leak just like navigating event handler. That is why we need to use weak reference.
  6. There can be some other control storing some other content state. That means content state object of PaperStack control needs to wrap contentstate stored in the event args so that it is not lost.

Before we get into those individual items. this is the only change I made to page.xaml of application. It has a text box and a hyperlink to make sure control not only supports navigation when there is no top level navigation but it also restores its state when user navigates away from the page containing the control and hits back.

<TextBlock Grid.Row=”1″>
                           <Hyperlink NavigateUri=”Page2.xaml”> Navigate to Page2</Hyperlink>
                  </TextBlock>
                  <TextBox Grid.Row=”2″></TextBox>

<ccl:PaperStack Name=”stack” ItemsSource=”{Binding Source={StaticResource feedcollection}}” Grid.Row=”4″ DrilledDown=”OnDrilledDown”/>

1) Navigate or Not

Control has a member ns to store the reference to the NavigationService. OnLoaded event, control tries to get handle to the NavigationService.. If the control is not in the navigation supported container then, value will be null. Value of ns is used everywhere internally to decide if the control should execute navigation related code. I think in my code I only try to get reference once. Ideally I should have tried to get reference every time I try to use it because application might tweak the tree after control is loaded.

void PaperStack_Loaded(object sender, RoutedEventArgs e)
{
            ns = NavigationService.GetNavigationService(this);
            if (ns != null)
            {
                        ns.Navigating += new NavigatingCancelEventHandler(ns_Navigating);
            }
}

2) Keep Track of Filtering and Grouping

Keep track of grouping was straight forward because in my control at any given time there is only one grouping applied but filtering was a different deal all together. Especially because filtering has additive behavior as I explained in the last post. That means for any given view I have to keep track of existing grouping but all the filters applied so far.

//Check if the navigation service is null
//if not null, control will start journaling the navigation state
if (ns != null)
{
          //Create a new journal entry object
          PaperStackJournalEntry pje = new PaperStackJournalEntry();

          //add Filter Values and Filter Properties to journal entry
          for (int i = 0; i < _filterdvalues.Count; i++)
          {
                    pje.FilterCollection.Add(_filterdvalues[i].ToString());
                    pje.FilterPropertyCollection.Add(_filterproperties[i].ToString());
          }

          //add current grouping property
          pje.Group = _currentgroupingproperty;

          //store the control guid
          pje.MyControlGuid = MyGuid;

          //Call AddBackEntry to add the journal entry in the journal
          ns.AddBackEntry(pje);
}

 

3) Navigating Event Instead of IProvideCustomContentState

Since the Control can live anywhere in the content, IProvideCustomContentState based solution would not work.  NavigatingCancelEventArgs provides access to ContentStateToSave property that allows NavigatingEventHandler to specify other information that needed to be stored. This is very useful mechanism when you can not use IProvideCustomContentState interface.

/// <summary>
/// Since Control is not at the root of the content for
/// a navigation container, typical use of IProvideCustomContentState
/// implemention wont work here. So to be able to set the custom content
/// state, we will use navigating event. Event args for that event gives
/// access to content state property.
/// </summary>
/// <param name=”sender”></param>
/// <param name=”e”></param>
void ns_Navigating(object sender, NavigatingCancelEventArgs e)
{
          //gets the datasource for the control
          ListCollectionView cs = this.ItemsSource as ListCollectionView;
          //creates a new journal entry
          PaperStackJournalEntry pje = new PaperStackJournalEntry();
          //journal the guid of the control
          pje.MyControlGuid = MyGuid;
          //journal collection of filters applied
          //to the datasource
          pje.FilterCollection = _filterdvalues;
          //journal collection of properties to which
          //those filter values are applied
          pje.FilterPropertyCollection = _filterproperties;
          //journal the grouping criteria
          pje.Group = (cs.GroupDescriptions[0] as           PropertyGroupDescription).PropertyName;

          //This is very interesting since there might
          //some other controls who might be trying to do
          //the same thing, we just cant overwrite customstate
          //so we need to wrap around existing custom state
          //so that it is not lost.
          pje.OtherContentState = e.ContentStateToSave;
          //sets the content state of navigating event args
          //to journal entry
          e.ContentStateToSave = pje;
}

 

4) Unhooking from Navigating Event

Control hooks into navigating event to store the custom state if it is hosted inside a navigation container. This means control wont be GC’ed since Navigation container will hold on to the reference of the control even after control is unloaded. So to work around this, control needs to unhook from the Navigating event so that control can be disposed easily.

void PaperStack_Unloaded(object sender, RoutedEventArgs e)
{
          if (ns != null)
                    ns.Navigating -= new NavigatingCancelEventHandler(ns_Navigating);
}

5) Who is my Owner?

There can be multiple controls of the same type all on same page. How does a journal entry know which control it was associated with. For that I use my own cooked up scheme. Every control gets a guid  when the instance is created and then I use Application object’s property bag to map guid to reference to the control. When the journal entry is created, control guid is part of the content state that is stored. So when there is time to restore the content state, replay method gets the handle to control using the application properties bag and uses that reference to restore the content state. There are two main problems with above approach…

  1. Control gets a new guid on instance creation that means user navigates away from the page containing control and comes back, control gets a new guid. To work around this problem I use journalble Dependency Property. Since this Dependency property is journalble it gets restored when users comes back to the page containing control. I hook into property change notification callback to update the property bag with correct guid.
  2. Since I used Application properties to map guid to reference handle to the control, that means when user navigates away from the page containing control, control would not get GC’ed. To get around this problem I use WeakReference instead of directly using the control reference.

//Change the guid mechanism to add DP and weak references
private Guid _myguid;

//Declare Dependency Property for GUID
public static readonly DependencyProperty MyGuidProperty;

//CLR Accessor for the property
public Guid MyGuid
{
         set { SetValue(MyGuidProperty, value); }
         get { return (Guid)GetValue(MyGuidProperty); }
}

/// <summary>
/// Static Constructor
/// </summary>
static PaperStack()
{
         //This OverrideMetadata call tells the system that this element
         //wants to provide a style that is different than its base class.
         //This style is defined in themes\generic.xaml
         DefaultStyleKeyProperty.OverrideMetadata(typeof(PaperStack), new          FrameworkPropertyMetadata(typeof(PaperStack)));

         //register the dependency property
         //Create Metadata object
         FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
         //Make the dependency property journalable.
         metadata.Journal = true;
         //specify the call for property changed notification
         metadata.PropertyChangedCallback += new PropertyChangedCallback         (PaperStack.OnMyGuidPropertyChanged);
         //register the property
         MyGuidProperty = DependencyProperty.Register(“MyGuid”, typeof(Guid),          typeof(PaperStack), metadata);

}

/// <summary>
/// Property change noification handler.
/// This gets called when GUID property changes.
/// There are two conditions when guid property changes.
/// 1) in Constructor when control gets instantiated
/// 2) journaling scenariop where navigation system restores the property
/// It adds the value using weakreference so that app does not leak memory
/// by keeping control alive as a result of holding on to its reference
/// </summary>
/// <param name=”obj”></param>
/// <param name=”args”></param>
static void OnMyGuidPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
         //gets the handle to the control
         PaperStack ps = obj as PaperStack;
         //removes the old entry for the guid if it exists
         Application.Current.Properties.Remove(((Guid)args.OldValue).ToString());
         //add entry for the new guid
         Application.Current.Properties[ps.MyGuid.ToString()] = new WeakReference(ps);
}

Other Content State

In a given page there might be other controls that trying to store their own custom states to the journal. That means I cant just assign ContentStateToSave property to PaperStackContentState because then application will lose information that somebody else might have stored. At the same time, PaperStack does not know anything about other controls to predict what the content state might be. so solution to that is to wrap the existing content state in the PaperStackContentState and when Navigation framework calls Replay on PaperStackContentState, it restore its own content state and then calls Replay on wrapped ContentState. This model will only work if all other controls are being nice citizens and doing the similar things and not overriding each other’s content state.

Code in Navigating Event handler for the control

//This is very interesting since there might
//some other controls who might be trying to do
//the same thing, we just cant overwrite customstate
//so we need to wrap around existing custom state
//so that it is not lost.
pje.OtherContentState = e.ContentStateToSave;
//sets the content state of navigating event args
//to journal entry
e.ContentStateToSave = pje;

PaperStackContentState definition

//Other custom content state that might be part of navigating event
private CustomContentState _othercs = null;

/// <summary>
/// Property get/set for Other content state
/// </summary>
public CustomContentState OtherContentState
{
         get { return _othercs; }
         set { _othercs = value; }
}

PaperStackContentState Replay Method

//When a user navigates back through navigation history to a piece of content
//that has an associated CustomContentState object, Replay is called;
//you override Replay to provide an implementation that reapplies state to the
//current content.
public override void Replay(NavigationService navigationService, NavigationMode mode)
{
         //gets the access to the control using application property bag
         PaperStack ps = (Application.Current.Properties[MyControlGuid.ToString()] as WeakReference).Target as PaperStack;

         //update the control’s propertye
         ps.FilterdValues = FilterCollection;
         ps.FilterProperties = FilterPropertyCollection;

         //get the datasource of the object to apply the journaled data
         ListCollectionView cs = ps.ItemsSource as ListCollectionView;
         //undo all grouping
         cs.GroupDescriptions.Clear();
         //add a grouping based on journaled value
         PropertyGroupDescription temp = new PropertyGroupDescription(Group);
         cs.GroupDescriptions.Add(temp);

         //undo all existing filters
         cs.Filter = null;
         if (FilterCollection != null)
         {
                  //apply one filter at a time since filtering is additive
                  for (int i = 0; i < FilterCollection.Count; i++)
                  {
                           _curfiltervalue = FilterCollection[i].ToString();
                           _curfilterproperty = FilterPropertyCollection[i].ToString();
                           cs.Filter = new Predicate<object>(FilterOutOtherGroups);
                  }
         }
         //if there was other content state stored the
         //we will call reply on the content state
         if(this.OtherContentState != null)
                  this.OtherContentState.Replay(navigationService, mode);
}

 

Restoring PaperStackContentState from Journal

This class stores the the custom state of the control. For my control, I need to store grouping and filtering separately. for showing the data with same grouping is pretty straight forward because all I needed to do was to store the current grouping value in the journal entry.

//get the datasource of the object to apply the journaled data
ListCollectionView cs = ps.ItemsSource as ListCollectionView;
//undo all grouping
cs.GroupDescriptions.Clear();
//add a grouping based on journaled value
PropertyGroupDescription temp = new PropertyGroupDescription(Group);
cs.GroupDescriptions.Add(temp);

 But for filtering, I needed to keep track of all the filter applied to data source till the current view to generate the current view. Since filters on the data source is not a collection, I have to actually create a collection that keeps track of all the filters applied so far after clearing all the existing filters.

//undo all existing filters
cs.Filter = null;
if (FilterCollection != null)
{
          //apply one filter at a time since filtering is additive
          for (int i = 0; i < FilterCollection.Count; i++)
         {
                    _curfiltervalue = FilterCollection[i].ToString();
                    _curfilterproperty = FilterPropertyCollection[i].ToString();
                    cs.Filter = new Predicate<object>(FilterOutOtherGroups);
         }
 }

//////////////////////////////////////////////////////////////////////////
Full Code
//////////////////////////////////////////////////////////////////////////

**************************************************************************
Updated PaperStack
**************************************************************************

namespace MyControls
{

          //PaperStack control inheriting from ItemsControl.
          public class PaperStack : System.Windows.Controls.ItemsControl
          {

           //Routed command that control uses to describe that user wants to
           // drill down on the current view. In the default group style, button will
          // send this command that
          //this control will catch to rearrange the it’s own view.
          public static RoutedCommand DrillDownCmd = new RoutedCommand();

          //Current grouping criteria E.g Foldername
          private string _currentgroupingproperty = null;

          //data value of the group selected by user by clicking 
          //E.g. FolderName = “Friends”
          private string _selectedgroup = null;

          //when user clicks on a button Let’s say FolderName=”Friends” then
          //Friends becomes the filtered value for the next navigation
          private ArrayList _filterdvalues = null;

          //when user clicks on a button Let’s say FolderName=”Friends” then
          //folderName becomes the filtered property for the next navigation
          //(Condition for filtering will be Foldername=”Frends”)
          private ArrayList _filterproperties = null;

          /// <summary>
          /// These are internals because did not application settings these value
          /// but Journal entry needs to
          /// </summary>
          internal ArrayList FilterdValues
          {
                    set { _filterdvalues = value; }
                    get { return _filterdvalues; }
          }

          /// <summary>
          /// These are internals because did not application settings these value
          /// but Journal entry needs to
          /// </summary>
          internal ArrayList FilterProperties
          {
                    set { _filterproperties = value; }
                    get { return _filterproperties; }
          }

          // Create a custom routed event by first registering a RoutedEventID
          // This event uses the bubbling routing strategy
          public static readonly RoutedEvent DrilledDownEvent = EventManager.RegisterRoutedEvent(“DrilledDown”, RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(PaperStack));

          //Reference to container’s Navigation Service
          private NavigationService ns = null;

          //Change the guid mechanism to add DP and weak references
          private Guid _myguid;

          //Declare Dependency Property for GUID
          public static readonly DependencyProperty MyGuidProperty;

          //CLR Accessor for the property
          public Guid MyGuid
          {
                    set { SetValue(MyGuidProperty, value); }
                    get { return (Guid)GetValue(MyGuidProperty); }
          }

          /// <summary>
          /// Static Constructor
          /// </summary>
          static PaperStack()
          {
                    //This OverrideMetadata call tells the system that this element
                    //wants to provide a style that is different than its base class.
                    //This style is defined in themes\generic.xaml
                    DefaultStyleKeyProperty.OverrideMetadata(typeof(PaperStack), new FrameworkPropertyMetadata(typeof(PaperStack)));

                    //register the dependency property
                    //Create Metadata object
                    FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
                    //Make the dependency property journalable.
                    metadata.Journal = true;
                    //specify the call for property changed notification
                    metadata.PropertyChangedCallback += new PropertyChangedCallback(PaperStack.OnMyGuidPropertyChanged);
                    //register the property
                    MyGuidProperty = DependencyProperty.Register(“MyGuid”, typeof(Guid), typeof(PaperStack), metadata);
          }

          /// <summary>
          /// Instance constructor…
          /// 1) Initilizes the arraylist to store the filter values
          /// </summary>
          public PaperStack()
          {
                    //Intialize the Filterd Value and Filter Properties lists
                    _filterdvalues = new ArrayList();
                    _filterproperties = new ArrayList();

                    //set the guid property
                    MyGuid = Guid.NewGuid();

                    //hook into loaded and unloaded event of self so that
                    //those events can be used to hook/unhook into
                    //the navigating event
                    this.Loaded += new RoutedEventHandler(PaperStack_Loaded);
                    this.Unloaded += new RoutedEventHandler(PaperStack_Unloaded);
          }

          /// <summary>
          /// Provide CLR Accessor for events
          /// </summary>
          public event RoutedEventHandler DrilledDown
          {
                    add { AddHandler(DrilledDownEvent, value); }
                    remove { RemoveHandler(DrilledDownEvent, value); }
          }

          /// <summary>
          /// Method raises Drilled Down event that Application can use
          /// </summary>
          void RaiseDrilledDownEvent()
          {
                    RoutedEventArgs newEventArgs = new RoutedEventArgsv(PaperStack.DrilledDownEvent);
                    RaiseEvent(newEventArgs);
          }

          /// <summary>
          /// Override to EndInit to specify a group style if it is not already specified
          /// This is because the same model that works for ItemsStyle does not work
          ///for group style In other words, control does not fall back and look inside
          ///generic.xaml for grouping style
          /// </summary>
          public override void EndInit()
          {
                    //calling ItemsControl.EndInit
                    base.EndInit();

                    //Check if the group style has been specified by application developer
                    //if the group style is specified then dont apply the default style
                    //if the group style is not specified then applyc the default style.
                    if (this.GroupStyle.Count == 0)
                    {
                              //This code actually loads the resource dictionary embedded
                              // inside the assembly using pack:// syntax. It merges the loaded
                              //dictionary with controls dictionary.
                              ResourceDictionary r = new ResourceDictionary();
                              r.Source = new Uri                                        (“pack://application:,,,/MyControls;component/groupstyle.xaml”,                               UriKind.RelativeOrAbsolute);
                              this.Resources.MergedDictionaries.Add(r);
                              this.GroupStyle.Add(this.Resources[“groupstyle”] as GroupStyle);
                    }

                    // Creates new command binding to bind a command to routed command
                    // handler
                    CommandBinding cmdbnd = new CommandBinding(PaperStack.DrillDownCmd, DrillDownExecuted);

                    //adds the binding to control so that control can get those commands
                    this.CommandBindings.Add(cmdbnd);
          }

          /// <summary>
          /// Routed Command Handler. Ccommand handler does two things…
          /// 1) After user Clicks on a particular group,
          /// this handler needs to filter out all other items except the group selected
          /// 2) This handler needs to raise an event that allows application to specify
          /// the next criteria for grouping
          /// </summary>
          /// <param name=”sender”></param>
          /// <param name=”e”></param>
          private void DrillDownExecuted(object sender, ExecutedRoutedEventArgs e)
          {
                    //gets the data source for the control
                    ListCollectionView cs = this.ItemsSource as ListCollectionView;
                    //gets the grouping property E.g. FolderName
                    _currentgroupingproperty = (cs.GroupDescriptions[0] as                     PropertyGroupDescription).PropertyName;

                    //Gets the selected group
                    if ((e.Parameter as CollectionViewGroup).Name != null)
                              _selectedgroup = (e.Parameter as CollectionViewGroup).Name.ToString();
                    else
                              _selectedgroup = null;

                    //Check if the navigation service is null
                    //if not null, control will start journaling the navigation state
                    if (ns != null)
                    {
                              //Create a new journal entry object
                              PaperStackJournalEntry pje = new PaperStackJournalEntry();

                              //add Filter Values and Filter Properties to journal entry
                              for (int i = 0; i < _filterdvalues.Count; i++)
                              {
                                        pje.FilterCollection.Add(_filterdvalues[i].ToString());
                                        pje.FilterPropertyCollection.Add(_filterproperties[i].ToString());
                              }

                              //add current grouping property
                              pje.Group = _currentgroupingproperty;

                              //store the control guid
                              pje.MyControlGuid = MyGuid;

                              //Call AddBackEntry to add the journal entry in the journal
                              ns.AddBackEntry(pje);
                    }

                    //Adds the current group property and selected group for filter
                    // E.g. if user clicks on foldername=friends
                    //then all items except FolderName=friends need to be filtered out
                    _filterdvalues.Add(_selectedgroup);
                    _filterproperties.Add(_currentgroupingproperty);

                    //Adds the filter delegate
                    cs.Filter = new Predicate<object>(FilterOutOtherGroups);

                    //After filtering is done, raises the Drill down even
                    //to allow applications to specify the next level of grouping
                    RaiseDrilledDownEvent();

          }

          /// <summary>
          /// Filtering Delegate. This gets called for each item to decide
          /// if the items should be filtered out. It uses reflection to get the
          /// value of grouping property (foldername) for each of the item and
          /// checks if it is equal to selected group (friends).
          /// If it matches then it returns true (item should not be filtered)
          /// If it does not match then it returns false (item should be filtered)
          /// </summary>
          /// <param name=”item”></param>
          /// <returns></returns>
          public bool FilterOutOtherGroups(object item)
          {
                    string _filtervalue = null;
                    Type itemtype = item.GetType();
                    PropertyInfo pinfo = itemtype.GetProperty(_currentgroupingproperty);
                    if (pinfo.GetValue(item, null) != null)
                    {
                              _filtervalue = pinfo.GetValue(item, null).ToString();
                              if (_filtervalue != null)
                              {
                                        if (_filtervalue.Equals(_selectedgroup))
                                                  return true;
                                        else
                                                  return false;
                              }
                              else
                                        return false;
                    }
                    else
                              return false;
          }

          /// <summary>
          /// Property change noification handler.
          /// This gets called when GUID property changes.
          /// There are two conditions when guid property changes.
          /// 1) in Constructor when control gets instantiated
          /// 2) journaling scenariop where navigation system restores the property
          /// It adds the value using weakreference so that app does not leak memory
          /// by keeping control alive as a result of holding on to its reference
          /// </summary>
          /// <param name=”obj”></param>
          /// <param name=”args”></param>
          static void OnMyGuidPropertyChanged(DependencyObject obj,           DependencyPropertyChangedEventArgs args)
          {
                    //gets the handle to the control
                    PaperStack ps = obj as PaperStack;
                    //removes the old entry for the guid if it exists
                    Application.Current.Properties.Remove(((Guid)args.OldValue).ToString());
                    //add entry for the new guid
                    Application.Current.Properties[ps.MyGuid.ToString()] = new WeakReference(ps);
          }

          /// <summary>
          /// On Unloaded Event, control unhooks itself
          /// from the Navigating event
          /// </summary>
          /// <param name=”sender”></param>
          /// <param name=”e”></param>
          void PaperStack_Unloaded(object sender, RoutedEventArgs e)
          {
                    if (ns != null)
                              ns.Navigating -= new NavigatingCancelEventHandler(ns_Navigating);
          }

          /// <summary>
          /// On Loaded Event, control hooks itself
          /// to the Navigating event
          /// </summary>
          /// <param name=”sender”></param>
          /// <param name=”e”></param>
          void PaperStack_Loaded(object sender, RoutedEventArgs e)
          {
                    ns = NavigationService.GetNavigationService(this);
                    if (ns != null)
                              ns.Navigating += new NavigatingCancelEventHandler(ns_Navigating);
          }

          /// <summary>
          /// Since Control is not at the root of the content for
          /// a navigation container, typical use of IProvideCustomContentState
          /// implemention wont work here. So to be able to set the custom content
          /// state, we will use navigating event. Event args for that event gives
          /// access to content state property.
          /// </summary>
          /// <param name=”sender”></param>
          /// <param name=”e”></param>
          void ns_Navigating(object sender, NavigatingCancelEventArgs e)
          {
                    //gets the datasource for the control
                    ListCollectionView cs = this.ItemsSource as ListCollectionView;
                    //creates a new journal entry
                    PaperStackJournalEntry pje = new PaperStackJournalEntry();
                    //journal the guid of the control
                    pje.MyControlGuid = MyGuid;
                    //journal collection of filters applied
                    //to the datasource
                    pje.FilterCollection = _filterdvalues;
                    //journal collection of properties to which
                    //those filter values are applied
                    pje.FilterPropertyCollection = _filterproperties;
                    //journal the grouping criteria
                    pje.Group = (cs.GroupDescriptions[0] as                     PropertyGroupDescription).PropertyName;

                    //This is very interesting since there might
                    //some other controls who might be trying to do
                    //the same thing, we just cant overwrite customstate
                    //so we need to wrap around existing custom state
                    //so that it is not lost.
                    pje.OtherContentState = e.ContentStateToSave;
                    //sets the content state of navigating event args
                    //to journal entry
                    e.ContentStateToSave = pje;
          }
          }
}

 

**************************************************************************
PaperStack Journal Entry
**************************************************************************

namespace MyControls
{
           //A class that derives from CustomContentState must be serializable,
           //which means it must either be augmented with SerializableAttribute
           //or implement ISerializable
           [Serializable]
           class PaperStackJournalEntry : CustomContentState
           {
                      //value of the filtered property. For FolderName=”Friends”
                      //Friends is the filter value
                      private ArrayList _filtercollection = null;

                      //value of the filtered property name. For FolderName=”Friends”
                      //FolderName is the property
                      private ArrayList _filterpropertycollection = null;

                      //value for grouping
                      private string _group = null;

                      //internal variables to be used for filtering
                      private string _curfiltervalue = null;
                      private string _curfilterproperty = null;

                      //Guid of the control the journal entry is associated
                      private Guid _mycontrolguid;

                      //Other custom content state that might be part of navigating event
                      private CustomContentState _othercs = null;

                      /// <summary>
                      /// Instance constructor
                      /// </summary>
                      public PaperStackJournalEntry()
                      {
                                 //initializes collection that will be used to store filter information
                                 _filtercollection = new ArrayList();
                                 _filterpropertycollection = new ArrayList();
                      }

                      /// <summary>
                      /// Property get/set for Other content state
                      /// </summary>
                      public CustomContentState OtherContentState
                      {
                                 get { return _othercs; }
                                 set { _othercs = value; }
                      }

                      /// <summary>
                      /// Property get/set for Filtercollection
                      /// </summary>
                      public ArrayList FilterCollection
                      {
                                 get { return _filtercollection; }
                                 set { _filtercollection = value; }
                      }

                      /// <summary>
                      /// Property get/set for Filter Properites
                      /// </summary>
                      public ArrayList FilterPropertyCollection
                      {
                                 get { return _filterpropertycollection; }
                                 set { _filterpropertycollection = value; }
                      }

                      /// <summary>
                      /// Property get/set for guid of the control
                      /// </summary>
                      public Guid MyControlGuid
                      {
                                 get { return _mycontrolguid; }
                                 set { _mycontrolguid = value; }
                      }

                      /// <summary>
                      /// Property get set for the grouping
                      /// </summary>
                      public String Group
                      {
                                 get { return _group; }
                                 set { _group = value; }
                      }

                      //The name that appears in the back and forward drop down navigation
                     //  buttons,displayed from NavigationWindow, Frame, or Microsoft
                      // Internet Explorer 7 Beta 3 navigation chrome is automatically
                      // generated, unless you return your own by overriding
                      // JournalEntryName  property.
                      public override string JournalEntryName
                      {
                                 get{ return “Drill Down”;}
                      }

                      //When a user navigates back through navigation history to a piece of
                      // content that has an associated CustomContentState object, Replay is
                      // called; you override Replay to provide an implementation that
                      // reapplies state to the current content.
                      public override void Replay(NavigationService navigationService,            NavigationMode mode)
                      {
                                 //gets the access to the control using application property bag
                                 PaperStack ps = (Application.Current.Properties[MyControlGuid.ToString()] as WeakReference).Target as PaperStack;

                                 //update the control’s propertye
                                 ps.FilterdValues = FilterCollection;
                                 ps.FilterProperties = FilterPropertyCollection;

                                 //get the datasource of the object to apply the journaled data
                                 ListCollectionView cs = ps.ItemsSource as ListCollectionView;
                                 //undo all grouping
                                 cs.GroupDescriptions.Clear();
                                 //add a grouping based on journaled value
                                 PropertyGroupDescription temp = new PropertyGroupDescription(Group);
                                 cs.GroupDescriptions.Add(temp);

                                 //undo all existing filters
                                 cs.Filter = null;
                                 if (FilterCollection != null)
                                 {
                                            //apply one filter at a time since filtering is additive
                                            for (int i = 0; i < FilterCollection.Count; i++)
                                            {
                                                       _curfiltervalue = FilterCollection[i].ToString();
                                                       _curfilterproperty = FilterPropertyCollection[i].ToString();
                                                       cs.Filter = new Predicate<object>(FilterOutOtherGroups);
                                            }
                                 }
                      //if there was other content state stored the
                      //we will call reply on the content state
                      if(this.OtherContentState != null)
                                 this.OtherContentState.Replay(navigationService, mode);
           }

           /// <summary>
           /// Filtering Delegate. This gets called for each item to decide
           /// if the items should be filtered out. It uses reflection to get the
           /// value of grouping property (foldername) for each of the item and
           /// checks if it is equal to selected group (friends).
           /// If it matches then it returns true (item should not be filtered)
           /// If it does not match then it returns false (item should be filtered)
           /// </summary>
           /// <param name=”item”></param>
           /// <returns></returns>
           public bool FilterOutOtherGroups(object item)
           {
                      string _filtervalue = null;
                      Type itemtype = item.GetType();
                      PropertyInfo pinfo = itemtype.GetProperty(_curfilterproperty);
                      if (pinfo.GetValue(item, null) != null)
                      {
                                 _filtervalue = pinfo.GetValue(item, null).ToString();
                                 if (_filtervalue != null)
                                 {
                                            if (_filtervalue.Equals(_curfiltervalue))
                                                       return true;
                                            else
                                                       return false;
                                 }
                                 else
                                            return false;
                      }
                      else
                                 return false;
           }
           }
}

 

**************************************************************************
Page.xaml
**************************************************************************

<Page x:Class=”WindowsApplication1.Page1″
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:ccl=”clr-namespace:MyControls;assembly=MyControls”
xmlns:feedns=”clr-namespace:IERSSInterop;assembly=IERSSInterop”
Title=”WindowsApplication1″>
         <Page.Resources>
                  <feedns:FeedCollection x:Key=”stack”/>
                  <CollectionViewSource Source=”{StaticResource stack}” x:Key=”feedcollection”/>
         </Page.Resources>
         <Grid>
                  <Grid.ColumnDefinitions>
                           <ColumnDefinition />
                  </Grid.ColumnDefinitions>
                  <Grid.RowDefinitions>
                           <RowDefinition Height=”30″/>
                           <RowDefinition Height=”30″/>
                           <RowDefinition Height=”30″/>
                           <RowDefinition />
                  </Grid.RowDefinitions>
                  <ComboBox SelectedIndex=”0″ SelectionChanged=”OnSelectionChanged” Grid.Row=”0″>
                           <ComboBoxItem Content=”FolderName”></ComboBoxItem>
                           <ComboBoxItem Content=”PubDate”></ComboBoxItem>
                           <ComboBoxItem Content=”Author”></ComboBoxItem>
                           <ComboBoxItem Content=”FeedName”></ComboBoxItem>
                  </ComboBox>
                  <TextBlock Grid.Row=”1″>
                           <Hyperlink NavigateUri=”Page2.xaml”> Navigate to Page2</Hyperlink>
                  </TextBlock>
                  <TextBox Grid.Row=”2″></TextBox>
<ccl:PaperStack Name=”stack” ItemsSource=”{Binding Source={StaticResource feedcollection}}” Grid.Row=”4″ DrilledDown=”OnDrilledDown”/>
         </Grid>
</Page>

Standard

One thought on “Navigation Support for Paperstack Control

Leave a comment