52

I'm using a an ObservableCollection with two ICollectionView for different filters.

One is for filtering messages by some type, and one is for counting checked messages. As you can see message filter and message count works OK, but when I'm un-checking the message disappear from the list (the count is still working).

BTW sorry for the long post, I wanted to include all relevant stuff.

The XAML Code:

<!-- Messages List -->
<DockPanel Grid.Row="1"
           Grid.Column="0"
           Grid.ColumnSpan="3"
           Height="500">
  <ListBox Name="listBoxZone"
           ItemsSource="{Binding filteredMessageList}"
           Background="Transparent"
           BorderThickness="0">
    <ListBox.ItemTemplate>
      <DataTemplate>
        <CheckBox Name="CheckBoxZone"
                  Content="{Binding text}"
                  Tag="{Binding id}"
                  Unchecked="CheckBoxZone_Unchecked"
                  Foreground="WhiteSmoke"
                  Margin="0,5,0,0"
                  IsChecked="{Binding isChecked}" />
      </DataTemplate>
    </ListBox.ItemTemplate>
  </ListBox>
</DockPanel>
<Button Content="Test Add New"
        Grid.Column="2"
        Height="25"
        HorizontalAlignment="Left"
        Margin="34,2,0,0"
        Click="button1_Click" />
<Label Content="{Binding checkedMessageList.Count}"
       Grid.Column="2"
       Height="25"
       Margin="147,2,373,0"
       Width="20"
       Foreground="white" />

Screenshot: enter image description here

Code:

/* ViewModel Class */
public class MainViewModel : INotifyPropertyChanged
{

    // Constructor
    public MainViewModel()
    {
        #region filteredMessageList
        // connect the ObservableCollection to CollectionView
        _filteredMessageList = CollectionViewSource.GetDefaultView(messageList);
        // set filter 
        _filteredMessageList.Filter = delegate(object item)
        {
            MessageClass temp = item as MessageClass;

            if ( selectedFilter.Equals(AvailableFilters.All) )
            {
                return true;
            }
            else
            {
                return temp.filter.Equals(_selectedFilter);
            }
        };
        #endregion

        #region checkedMessageList
        // connect the ObservableCollection to CollectionView
        _checkedMessageList = CollectionViewSource.GetDefaultView(messageList);
        // set filter 
        _checkedMessageList.Filter = delegate(object item) { return (item as MessageClass).isChecked; };
        #endregion
    }

    // message List
    private ObservableCollection<MessageClass> _messageList =
            new ObservableCollection<MessageClass>();
    public ObservableCollection<MessageClass> messageList
    {
        get { return _messageList; }
        set { _messageList = value; }
    }

    // CollectionView (filtered messageList)
    private ICollectionView _filteredMessageList;
    public ICollectionView filteredMessageList
    {
        get { return _filteredMessageList; }
    }

    // CollectionView (filtered messageList)
    private ICollectionView _checkedMessageList;
    public ICollectionView checkedMessageList
    {
        get { return _checkedMessageList; }
    }

    // SelectedFilter property
    private AvailableFilters _selectedFilter = AvailableFilters.All; // Default is set to all
    public AvailableFilters selectedFilter
    {
        get { return _selectedFilter; }
        set
        {
            _selectedFilter = value;
            RaisePropertyChanged("selectedFilter");
            _filteredMessageList.Refresh(); // refresh list upon update
        }
    }

    // FilterList (Convert Enum To Collection)
    private List<KeyValuePair<string, AvailableFilters>> _AvailableFiltersList;
    public List<KeyValuePair<string, AvailableFilters>> AvailableFiltersList
    {
        get
        {
            /* Check if such list available, if not create for first use */
            if (_AvailableFiltersList == null)
            {
                _AvailableFiltersList = new List<KeyValuePair<string, AvailableFilters>>();
                foreach (AvailableFilters filter in Enum.GetValues(typeof(AvailableFilters)))
                {
                    string Description;
                    FieldInfo fieldInfo = filter.GetType().GetField(filter.ToString());
                    DescriptionAttribute[] attributes =
                                (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);

                    /* if not null get description */
                    if (attributes != null && attributes.Length > 0)
                    {
                        Description = attributes[0].Description;
                    }
                    else
                    {
                        Description = string.Empty;
                    }

                    /* add as new item to filterList */
                    KeyValuePair<string, AvailableFilters> TypeKeyValue =
                                new KeyValuePair<string, AvailableFilters>(Description, filter);

                    _AvailableFiltersList.Add(TypeKeyValue);
                }
            }
            return _AvailableFiltersList;
        }
    }

    #region Implement INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    public void RaisePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion
}

Code For un-check function

private void CheckBoxZone_Unchecked(object sender, RoutedEventArgs e)
{
    CheckBox chkZone = (CheckBox)sender;
    ucSystemMessageVM.checkedMessageList.Refresh();
}
Daniel
  • 10,864
  • 22
  • 84
  • 115
drtf
  • 1,886
  • 2
  • 21
  • 19
  • Dave this is not so much an answer but may help you down the road. Recently doing some WPF contract work I just couldn't find the right solution for filter,paging,sorting as I wanted it. I built this generic class. Thought you might like to poke around in it. http://www.origin1.com/downloads/PagedObservableCollection.txt. obviously change the ext. – origin1tech May 19 '13 at 16:31
  • While searching for a similar problem, I came across this question and answer. It's very difficult to understand what is going on here -- The definition for `MessageClass` and `AvailableFilters` is not included, and there are a number of C#/.NET features that would make this code far more condensed and easier to comprehend at a glance -- auto-properties, lambda expressions, LINQ. – Zev Spitz May 06 '18 at 18:53

2 Answers2

117

This answer helped me with this exact problem. The static CollectionViewSource.GetDefaultView(coll) method will always return the same reference for a given collection, so basing multiple collection views on the same reference will be counterproductive. By instantiating the view as follows:

ICollectionView filteredView = new CollectionViewSource { Source=messageList }.View;

The view can now be filtered/sorted/grouped independently of any others. Then you can apply your filtering.

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
randcd
  • 2,263
  • 1
  • 19
  • 21
  • 5
    Thanks, Actually this is very helpful as I couldn't achieve that and resulted with a big ugly workaround – drtf Jul 21 '13 at 08:07
  • I have an issue with this method: filteredView seems to not observing messageList, so it does not react on any change to source collection – Jakub Pawlinski Jul 06 '16 at 08:42
  • @JakubPawlinski, I'm seeing the same thing. Did you ever find a solution? – Pancake Aug 30 '18 at 03:59
  • 10
    @JakubPawlinski ,@Pancake . I found the solution . Directly create the List Collection view . ICollectionView filterdView=new ListCollectionView(sourceCollection); Thanks – Null Pointer Sep 13 '18 at 10:22
  • I need an example that uses this technique. It's not clear what to do with the single line of code provided. It does not fit in nicely anywhere in any of the other CollectionView examples I've found. – Scott Solmer Dec 09 '19 at 14:53
  • @Okuma.Scott Instead of using: ICollectionView filteredView = new CollectionViewSource { Source=messageList }.View; you use: ICollectionView filterdView=new ListCollectionView(messageList ); It worked perfect for me! – Axilleas Kar Feb 17 '20 at 10:32
  • 3
    This answer provided half of what I was looking for. The other half came from [here](https://stackoverflow.com/a/48543927/1932781). Basically, in some scenarios, refreshing the view throws a null reference exception because the source backing the view has been GC'd. The fix is to store the source in a class-scoped variable rather than throwing it away: `_viewSource = new CollectionViewSource { Source = messageList }; var filteredView = _viewSource.View;` – Travis May 28 '20 at 22:09
3

For anyone struggling with the problem that the filteredView does not observe the sourceCollection (messageList in this example):

I came around with this solution:

ICollectionView filteredView = new CollectionViewSource { Source=messageList }.View;
messageList.CollectionChanged += delegate { filteredView.Refresh(); };

So it will refresh our filteredView evertytime the CollectionChanged event of the source get's fired. Of course you can implement it like this too:

messageList.CollectionChanged += messageList_CollectionChanged;

private void messageList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    filteredView.Refresh(); 
}

Consider using PropertyChanged-Events when filtering on a specific Property is wanted.

wulf11
  • 79
  • 8
  • If xou want your filitered view to automatically reevaluate any time a relevant property (of any of the items in the collection) changes, consider using LiveFilteringProperties Then you don't have to explicitly write code to Refresh https://learn.microsoft.com/de-de/dotnet/api/system.windows.data.collectionviewsource.livefilteringproperties However afaics it's only designed to react on Item's PropertyChanged events. It doesn't listen to CollectionChanged (if I remember correctly). – lidqy May 23 '21 at 21:55