People that have worked with Windows Presentation Foundation probably know the usefulness of the CollectionViewSource control. This control is simple to be configured and enables to easily filter, sort and group data items before binding them to an ItemsControl, without any need to change the underlying datasource. Many times we have to operate filtering on a datasource before displaying it in a listbox, or we need to sort the same data answering to a user request. Both this behaviors may be handled using the CollectionViewSource; filtering will raise an event asking to choice the items to show or to hide and we have simply to select the elements with the correct criteria. On the other side, the control can sort elements on one or more properties ascending or descending. If the target control support grouping we can also specify a grouping criteria and the control will operate seamless for us. But probably the better thing is that we can change sorting, filtering or grouping and the target control will be updated according to the new criteria.

Unfortunately Silverlight 2.0 does not have a CollectionViewSource but while exploring the capabilities of databinding I found that it is possible to build a control similar to the CollectionViewSource that support both filtering and sorting. There aren't controls supporting grouping in Silverlight 2.0 so it is unuseful to implement it in this control. Before getting to know how the control works it is better to understand how databinding works in Silverlight 2.0 so let me begin with a brief introduction to this argument.

Databinding in Silverlight 2.0

Databinding is a feature introduced many years ago so there is no need to explain what it is. Windows Presentation Foundation has taken this ancient tecnique to an high level of simplicity and Silverlight 2.0 implements a databinding capability very close to WPF with some little limitations.

The two main components of databinding are DataContext and the Binding markup extension. DataContext is a property, exposed by all the classes inheriting from FrameworkElement so it is available on all the controls, shapes, panels and other user interface elements. Through DataContext property we may specify a class instance that will be the source of databinding for the target element and for all his children. The DataContext will propagate to children elements until the end of the hierarchy or until another DataContext will be encountered. All the elements of the hierarchy may be binded with a property of the object that is associated to the DataContext. To bind the properties each other we have to use the Binding markup extension. In the simplest form the markup extension require that we specify the property of the datasource to bind to.

Another way to bind to a user interface control is using an ItemsControl. Connecting a datasource to its ItemsSource property will cause the collection to be displayed in the control repeating a DataTemplate for every item in the collection. The DataTemplate will be binded to each element throught the Binding markup extension itself. Here is an example showing both the tecniques applied.

   1: <UserControl x:Class="Elite.Silverlight.Samples.Databinding"
   2:     xmlns="http://schemas.microsoft.com/client/2007" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   4:     xmlns:d="clr-namespace:Elite.Silverlight.Samples"
   5:     Width="400" Height="300">
   6:     <UserControl.Resources>
   7:         <d:DataSource x:Key="data" />
   8:     </UserControl.Resources>
   9:     <Grid x:Name="LayoutRoot" DataContext="{StaticResource data}" Background="White">
  10:         <ItemsControl ItemsSource="{Binding Strings}">
  11:             <ItemsPanelTemplate>
  12:                 <DataTemplate>
  13:                     <TextBlock Text="{Binding}" />
  14:                 </DataTemplate>
  15:             </ItemsPanelTemplate>
  16:         </ItemsControl>
  17:     </Grid>
  18: </UserControl>

In this sample an instance of a class named DataSource has been referenced in the user control resources. This class has a property named "Strings" returning an IEnumerable<string>. The yielded strings is the data to be displayed in the ItemsControl. The DataContext property of the Grid is assigned to a reference of the DataSource class and the ItemsSource property is binded to the Strings property throught the Binding markup extension. The data collected from the datasource become the DataContext for the each item with the template specified in ItemsTemplatePanel so the Binding markup extension assign each string to the Text property of the TextBlock.

For the purpose of this article we don't need to deepen our knowledge of this aspect. Instead, we need to concentrate on another aspect. Databinding enable developers to bind properties of a control to properties of a data item and have them updated when the data item change. In the same way it is possible to bind a collection to a specific property and have the control updated when we add or remove items to the collection. This two behaviors came from two interfaces: INotifyPropertyChanged and INotifyCollectionChanged. Objects implementing one of these interfaces have to notify internal changes through an event. PropertyChanged notify changes to single properties and is useful for objects binded directly to control properties, instead CollectionChanged notify about adding or removal of items from a collection and it has to be used for controls binded to collections like an ItemsControl.

Silverlight 2.0 and WPF come with a generic collection ObservableCollection<T> that implements INotifyCollectionChanged interface. This class helps us to bind collection of objects to an ItemsControl. We may create a class, expose a property of this type and bind it to an ItemsControl. Then we can add or remove elements to the collection and them will be added to or removed from the ItemsControl seamless.

Thinking about our CollectionViewSource

It is now the time to think about how to implement our CollectionViewSource. We need to create the control as a filter that will be interposed between the datasource and the consuming control and it will operate notifying changes responding to user choices. So probably our control has to appear as a collection for  the control that will displays the data items. To notify changes we need to implement the INotifyCollectionChanged interface so we get the consuming control to update its appearance responding to changes to the filtering or sorting criteria. Watching to the control from the side of the datasource it has to expose a Source property that we'll connect to the underlying datasource we need to transform.

So let start creating the control. It will be called CollectionViewSource as expected and extends the Control class. We cannot create a simple class that does not implement any other base class because we need to get access to the DependencyProperty system. The Control class is tipically dedicated to elements that have a visual aspect, so probably it would be better we extend a class like DependencyObject for our control to work. Unfortunately we have to use the Control class because all the base classes from FrameworkElement to DependencyObject does not have a public constructor so we are unable to inherit from them. The Control class is not the best choice, but it is the only choice available.

   1: public class CollectionViewSource : Control, IEnumerable, INotifyCollectionChanged
   2: {
   3:     // implement the control... 
   4: }

The class has also to implement two interfaces: IEnumerable and INotifyCollectionChanged. The first interface is required to grant our control the capability to be consumed from an ItemsControl. Using the IEnumerable interface we get two goals. We can assign the CollectionViewSource directly to the ItemsSource property and we can use LINQ to Objects to yield resulting items to the consuming control. Taking advantage of LINQ to Objects will simplify our control structure and get our code to a better level of readability.

   1: #region IEnumerable Members
   2:  
   3: /// <summary>
   4: /// Returns an enumerator that iterates through a collection.
   5: /// </summary>
   6: /// <returns>
   7: /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
   8: /// </returns>
   9: public IEnumerator GetEnumerator()
  10: {
  11:     var result = this.Source
  12:         .Cast<object>();
  13:  
  14:     return result.GetEnumerator();
  15: }
  16:  
  17: #endregion

For the filtering to work we have to expose an event that we'll call every time we have to filter the collection, one time for every item in the collection asking the user to choice the items to show. The simplest thing to do is to create a delegate, that we use as a Where clause lambda expression in the Linq query yielding items. So the delegate will be called by the query every time it needs to examine an element to decide if it has to be yielded or skipped.

   1: /// <summary>
   2: /// Occurs when [filter].
   3: /// </summary>
   4: public event EventHandler<FilterCollectionEventArgs> Filter;
   5:  
   6: /// <summary>
   7: /// Returns an enumerator that iterates through a collection.
   8: /// </summary>
   9: /// <returns>
  10: /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
  11: /// </returns>
  12: public IEnumerator GetEnumerator()
  13: {
  14:     var result = this.Source
  15:         .Cast<object>()
  16:         .Where(FilterItem);
  17:  
  18:     return result.GetEnumerator();
  19: }
  20:  
  21: /// <summary>
  22: /// Filters the item.
  23: /// </summary>
  24: /// <param name="o">The o.</param>
  25: /// <returns></returns>
  26: private bool FilterItem(object o)
  27: {
  28:     FilterCollectionEventArgs args = new FilterCollectionEventArgs(o);
  29:     OnFilter(args);
  30:     return args.Accept;
  31: }
  32:  
  33: /// <summary>
  34: /// Raises the <see cref="E:Filter"/> event.
  35: /// </summary>
  36: /// <param name="e">The <see cref="Elite.Silverlight.FilterCollectionEventArgs"/> instance containing the event data.</param>
  37: protected virtual void OnFilter(FilterCollectionEventArgs e)
  38: {
  39:     EventHandler<FilterCollectionEventArgs> handler = Filter;
  40:  
  41:     if (handler != null)
  42:         handler(this, e);
  43: }

To configure the sorting behavior we have to expose a property that collect the sorting criteria in a way similar to the CollectionViewSource contained in the Windows Presentation Foundation. This require to create a SortDescriptionCollection class that will collect the SortDescription instances. Every SortDescription will require a PropertyName and a Direction property to tell the control wich property use for sorting and the direction of the sorting. All the names I've proposed come directly from the WPF control. Using this namig will grant our CollectionViewSource the maximum compatibility with the WPF control so we may use code snippet created for WPF in Silverlight 2.0.

We use the same LINQ to Objects expression adding OrderBy and ThenBy calls to the query if the sorting is enabled. The sorting will be called immediately after the Where clause.

   1: /// <summary>
   2: /// Returns an enumerator that iterates through a collection.
   3: /// </summary>
   4: /// <returns>
   5: /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
   6: /// </returns>
   7: public IEnumerator GetEnumerator()
   8: {
   9:     var result = this.Source
  10:         .Cast<object>()
  11:         .Where(FilterItem);
  12:  
  13:     foreach (SortDescription sd in this.SortDescriptions)
  14:     {
  15:         if (sd.Direction == ListSortDirection.Ascending)
  16:             result = AppendOrderBy(result, sd.PropertyName);
  17:         else
  18:             result = AppendOrderByDescending(result, sd.PropertyName);
  19:     }
  20:  
  21:     return result.GetEnumerator();
  22: }
  23:  
  24: /// <summary>
  25: /// Appends the order by.
  26: /// </summary>
  27: /// <param name="result">The result.</param>
  28: /// <param name="propertyName">Name of the property.</param>
  29: /// <returns></returns>
  30: private IEnumerable<object> AppendOrderBy(IEnumerable<object> result, string propertyName)
  31: {
  32:     if (result is IOrderedEnumerable<object>)
  33:         return ((IOrderedEnumerable<object>)result).ThenBy(o => GetPropertyValue(o, propertyName));
  34:  
  35:     return result.OrderBy(o => GetPropertyValue(o, propertyName));
  36: }
  37:  
  38: /// <summary>
  39: /// Appends the order by descending.
  40: /// </summary>
  41: /// <param name="result">The result.</param>
  42: /// <param name="propertyName">Name of the property.</param>
  43: /// <returns></returns>
  44: private IEnumerable<object> AppendOrderByDescending(IEnumerable<object> result, string propertyName)
  45: {
  46:     if (result is IOrderedEnumerable<object>)
  47:         return ((IOrderedEnumerable<object>)result).ThenByDescending(o => GetPropertyValue(o, propertyName));
  48:  
  49:     return result.OrderByDescending(o => GetPropertyValue(o, propertyName));
  50: }

With the INotifyCollectionChanged interface we'll notify the control to update his aspect when changes occur. The only thing we have to take care is the way to notify events. The NotifyCollectionChangedEventArgs class we have to use to raise the CollectionChanged event require us to specify the kind of event we are raising. The constructor of this class need to specify a NotifyCollectionChangedAction enumeration parameter that define if we are updating the datasource for Add, Remove, Replace, Move or Reset. To get the ItemsControl updating we need to specify the NotifyCollectionChangedAction.Reset value because every other value does not have an effect on the ItemsControl. This behavior become from the implementation of the ItemsSource property. Inspecting this property with Lutz Roeder Reflector it will show us that it is wrapped with an internal private EnumerableCollectionView that will mask every other type of event. The drawback of this choice is that the target control will be updated entirely instead of handling only the added or removed items. I hope that a future implementation of this control will change this thing, but now we have to accept this behavior to get the CollectionViewSource working.

We have to handle two type of events that raise the CollectionChanged event. The first one is when the underliyng collection raise the same event. This is because if the datasource change we have to reflect this change to the user interface itself forwarding this event. So when the Source property will be assigned we have to hook up its CollectionChanged event (if the source itself implements the INotifyCollectionChanged). For this purpose we create the source property as a DependencyProperty to take advantage of the PropertyChangedCallback that will notify when the Source property change its value. When the value change we have to remove event from a previous instance before to hook the new instance to avoid memory leaks and unexpected results.

   1: public static readonly DependencyProperty SourceProperty =
   2:     DependencyProperty.Register("Source", typeof(IEnumerable), typeof(CollectionViewSource), new PropertyChangedCallback(SourceProperty_Changed));
   3:  
   4: /// <summary>
   5: /// Gets or sets the source.
   6: /// </summary>
   7: /// <value>The source.</value>
   8: public IEnumerable Source
   9: {
  10:     get { return (IEnumerable)this.GetValue(SourceProperty); }
  11:     set { this.SetValue(SourceProperty, value); }
  12: }
  13:  
  14: /// <summary>
  15: /// Sources the property_ changed.
  16: /// </summary>
  17: /// <param name="sender">The sender.</param>
  18: /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
  19: private static void SourceProperty_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
  20: {
  21:     CollectionViewSource cvs = sender as CollectionViewSource;
  22:  
  23:     if (cvs != null)
  24:     {
  25:         if (e.OldValue != null && e.OldValue is INotifyCollectionChanged)
  26:             ((INotifyCollectionChanged)e.OldValue).CollectionChanged -= new NotifyCollectionChangedEventHandler(cvs.CollectionViewSource_CollectionChanged);
  27:  
  28:         if (e.NewValue != null && e.NewValue is INotifyCollectionChanged)
  29:             ((INotifyCollectionChanged)e.NewValue).CollectionChanged += new NotifyCollectionChangedEventHandler(cvs.CollectionViewSource_CollectionChanged);
  30:     }
  31: }
  32:  
  33: /// <summary>
  34: /// Handles the CollectionChanged event of the CollectionViewSource control.
  35: /// </summary>
  36: /// <param name="sender">The source of the event.</param>
  37: /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
  38: private void CollectionViewSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  39: {
  40:     if (sender != null)
  41:         this.OnCollectionChanged(e);
  42: }
  43:  
  44: /// <summary>
  45: /// Raises the <see cref="E:CollectionChanged"/> event.
  46: /// </summary>
  47: /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
  48: protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
  49: {
  50:     NotifyCollectionChangedEventHandler handler = CollectionChanged;
  51:  
  52:     if (handler != null)
  53:         handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
  54: }

Last but not least we have to expose a method to enable users to force the CollectionViewSource to refresh the criteria and notifying the changes if needed. This is the second type of event we have to handle to notify the collection change. If the user call this method we will assume that something has been changed in the criteria and we raise the CollectionChanged event.

   1: /// <summary>
   2: /// Refreshes this instance.
   3: /// </summary>
   4: public void Refresh()
   5: {
   6:     this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
   7: }

Using the CollectionViewSource control

Now that the control has been completed we have to build a sample project that use the CollectionViewSource. For this purpose I have created an application that read the rss feed of my weblog (http://blog.boschin.it) and display it in an ItemsControl. The interface have a TextBox that we use to filter the post by title and two radio buttons that decide if sort the items ascending or descending by the publish date. The application is very simple but its purpose is only to dimostrate how to use the CollectionViewSource control and how simple is to use and customize its behavior. Here is a brief markup defining the application interface:

   1: <UserControl x:Class="Silverlight.Page"
   2:     xmlns="http://schemas.microsoft.com/client/2007" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   4:     xmlns:s="clr-namespace:Silverlight" Loaded="UserControl_Loaded"
   5:     xmlns:e="clr-namespace:Elite.Silverlight;assembly=Elite.Silverlight"
   6:     xmlns:ed="clr-namespace:Elite.Silverlight.Data;assembly=Elite.Silverlight"
   7:     Width="400" Height="400">
   8:     <UserControl.Resources>
   9:         <s:DataSource x:Name="dataSource" />
  10:         <ed:CollectionViewSource x:Name="rssView" Source="{Binding Items, Source={StaticResource dataSource}}" Filter="rssView_Filter">
  11:             <CollectionViewSource.SortDescriptions>
  12:                 <ed:SortDescription PropertyName="PublishDate" Direction="Ascending" />
  13:             </CollectionViewSource.SortDescriptions>
  14:         </ed:CollectionViewSource>
  15:         <e:StringToUriConverter x:Name="stringToUri" />
  16:         <e:FormatDateTimeConverter x:Name="dateToString" />
  17:     </UserControl.Resources>
  18:     <Grid x:Name="LayoutRoot" Background="#ffffffff">
  19:         <Grid.RowDefinitions>
  20:             <RowDefinition Height="*" />
  21:             <RowDefinition Height="80" />
  22:         </Grid.RowDefinitions>
  23:         <Grid.Resources>
  24:             <DataTemplate x:Name="rssTemplate">
  25:                 <Border Background="#ffeeeeee" Margin="2,2,2,0" Padding="5" BorderBrush="#00000000" BorderThickness="1" CornerRadius="3,3">
  26:                     <Grid>
  27:                         <Grid.ColumnDefinitions>
  28:                             <ColumnDefinition Width="50*"/>
  29:                             <ColumnDefinition Width="50*"/>
  30:                         </Grid.ColumnDefinitions>
  31:                         <Grid.RowDefinitions>
  32:                             <RowDefinition Height="60*" />
  33:                             <RowDefinition Height="40*" />
  34:                         </Grid.RowDefinitions>
  35:                         <HyperlinkButton x:Name="itemLabel" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Margin="3,3,3,3" 
  36:                                          Content="{Binding Title.Text}" NavigateUri="{Binding Id, Converter={StaticResource stringToUri}}" 
  37:                                          TargetName="_blank" FontSize="14" FontFamily="Verdana" Foreground="#ffff9900" />
  38:                         <TextBlock Grid.Column="0" Grid.Row="1" Margin="3,3,3,3" FontSize="14" FontFamily="Verdana" 
  39:                                    Foreground="#ff666666" Text="{Binding PublishDate, Converter={StaticResource dateToString}, ConverterParameter=dd.MM.yyyy - hh:mm}" />
  40:                         <TextBlock Grid.Column="1" Grid.Row="1" Margin="3,3,3,3" FontSize="14" FontFamily="Verdana"
  41:                                    Foreground="#ff666666" Text="{Binding Copyright}" />
  42:                     </Grid>
  43:                 </Border>
  44:             </DataTemplate>
  45:         </Grid.Resources>
  46:         <Border Grid.Column="0" Grid.Row="0" Margin="2" BorderBrush="#ffff9900" BorderThickness="1" CornerRadius="3,3">
  47:             <ScrollViewer DataContext="{StaticResource dataSource}" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
  48:                 <ItemsControl ItemsSource="{Binding Source={StaticResource rssView}}" ItemTemplate="{StaticResource rssTemplate}">
  49:                 </ItemsControl>
  50:             </ScrollViewer>
  51:         </Border>
  52:         <Grid Grid.Column="0" Grid.Row="1" Background="#55ff9900" Margin="2">
  53:             <Grid.ColumnDefinitions>
  54:                 <ColumnDefinition Width="*"/>
  55:                 <ColumnDefinition Width="*"/>
  56:             </Grid.ColumnDefinitions>
  57:             <Grid.RowDefinitions>
  58:                 <RowDefinition Height="50*" />
  59:                 <RowDefinition Height="50*" />
  60:             </Grid.RowDefinitions>
  61:             <RadioButton x:Name="sortDescending" TextAlignment="Right" HorizontalAlignment="Left" Margin="10" Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="1" Content="Descending" GroupName="sort" Checked="sort_Checked" IsChecked="true" />
  62:             <RadioButton x:Name="sortAscending" TextAlignment="Left" HorizontalAlignment="Right" Margin="10" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="1" Content="Ascending" GroupName="sort" Checked="sort_Checked" />
  63:             <WatermarkedTextBox x:Name="txtKey" Padding="3" Grid.Column="0" Grid.Row="0" Margin="5" FontSize="14" Grid.ColumnSpan="2" TextChanged="txtKey_TextChanged" Watermark="Insert Keywork..." />
  64:         </Grid>
  65:     </Grid>
  66: </UserControl>

In this code snippet it is visible how to bind correctly the datasource to the CollectionViewSource and the CollectionViewSource itselft to the consuming ItemsControl. When we have established this connection we hook up the filtering event where we use a Contains() to check that the element contains the text written in the textbox. Every time we press a key the TextChanged event simply call the Refresh() method of the CollectionViewSource causing the filtering to happen again with the new text entered. This is a very intensive use of the  CollectionViewSource but with a few collection items (up to one or two hundred) there will be no side effects. In a production application this scenario probably will be handled in a different way, but I'm sure there is many other times you may take advantage of my control. The sorting happen in a similar way. When a radio button has been hit we swap the direction of the configured sorting and then recall the Refresh() method to rebind the interface. Try changing the SortDescription properties to sort by another property.

   1: public partial class Page : UserControl
   2: {
   3:     public Page()
   4:     {
   5:         InitializeComponent();
   6:     }
   7:  
   8:     private void UserControl_Loaded(object sender, RoutedEventArgs e)
   9:     {
  10:         this.dataSource = this.Resources["dataSource"] as DataSource;
  11:         this.dataSource.Load();
  12:     }
  13:  
  14:     private void sort_Checked(object sender, RoutedEventArgs e)
  15:     {
  16:         CollectionViewSource rssView = this.Resources["rssView"] as CollectionViewSource;
  17:  
  18:         if (rssView != null)
  19:         {
  20:             SortDescription sort = rssView.SortDescriptions[0];
  21:             sort.Direction = (sender == sortAscending ? ListSortDirection.Ascending : ListSortDirection.Descending);
  22:             rssView.Refresh();
  23:         }
  24:     }
  25:  
  26:     private void txtKey_TextChanged(object sender, TextChangedEventArgs e)
  27:     {
  28:         rssView.Refresh();
  29:     }
  30:  
  31:     private void rssView_Filter(object sender, Elite.Silverlight.FilterCollectionEventArgs e)
  32:     {
  33:         SyndicationItem item = e.Item as SyndicationItem;
  34:  
  35:         if (item != null)
  36:         {
  37:             e.Accept = item.Title.Text.ToLower().Contains(txtKey.Text.ToLower()) || txtKey.Text == txtKey.Watermark || txtKey.Text == string.Empty;
  38:         }
  39:     }
  40: }

Now we may run the application and try to enter some text in the TextBox. We will see that the displayed item will change according to the text entered. Also checking and unchecking the sort direction radio button will change the order of the the items.

Conclusion

The CollectionViewSource control is part of my Elite.Silverlight Library 1.0.3037.0 where I'm collecting all my reusable controls and classes. Please download it from codeplex (http://www.codeplex.com/silverlightlibrary) and use for free in your projects. The project come under the CCPL license and is free for non-commercial purposes. For use in commercial project please contact me on my weblog contact form at (http://blog.boschin.it/contact.aspx).

 


Commenti (3) -

# | Zákányi Balázs | 30.01.2009 - 00.44

Thank you, it was very helpfull for me.

# | Hannes Larsson | 26.02.2009 - 23.38

Hi

Great stuff! I really missed the CVS in Silverlight.
One question, if i want multiple filers on my CVS, how do I do this? i only get the last filer to work. Like this is a master filter.

# | Hannes Larsson | 25.03.2009 - 22.21

Solved me previous question by changing the OnFilter event to:
protected virtual void OnFilter(FilterCollectionEventArgs e)
        {
            if(Filter != null)
            {
                var filters = Filter.GetInvocationList();
                var result = false;

                foreach (var filter in filters)
                {
                    var handler = (EventHandler<FilterCollectionEventArgs>) filter;
                    if (handler != null)
                        handler(this, e);
                    
                    if(e.Accept)
                    {
                        result = true;
                    }
                }

                e.Accept = result;
            }
        }




Now my question is how i use the CollectionViewSource in the XAML code and bind it to a ListBox?!? I only get a dab AG parser exception.

Please help.
/Hannes


Aggiungi Commento