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.
- Control may or may not be hosted inside navigation container so control needs to be smart enough to act accordingly
- 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
- 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
- Un hook from navigating event at the right time other control instance wont be GCed since container will hold on to the reference
- 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.
- 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…
- 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.
- 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>
hey shot face where is source code and projext for download