• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1190
  • Last Modified:

Can the autocompletebox be used with an ObservableCollection and CollectionViewSource

I am creating a user control which will contain an autocompletebox.

Here is my xaml;
<my1:AutoCompleteBox 
                                 Text="{Binding Path=pvTextBoxText}" Height="45" HorizontalAlignment="Left" 
                             Margin="2,30,0,0" Name="pvTextBox" VerticalAlignment="Top" Width="337" 
                             FontFamily="Verdana" FontSize="17" Foreground="{Binding Path=pvTextBoxTextColor}"
                             ItemsSource="{Binding Path=pvPeople}" 
                             ValueMemberPath="pName"  IsTextCompletionEnabled="True"
                             FilterMode="Contains">
                
            </my1:AutoCompleteBox>

Open in new window


pvPeople is set up in the code behind as a DependanceProperty and it is an observablecollection.  The binding works and I see all the properties of pName in the drop down list.  (ps the person class which is the object of the pvPeople class also implements INotifyPropertyChanged)

I then try to filter this collection;
If colViewSource Is Nothing Then
            colViewSource = CollectionViewSource.GetDefaultView(pvPeople)

           colViewSource.Filter = New Predicate(Of Object)(AddressOf Me.peopleFilterCallback)
        End If

Open in new window


Just to be complete here is the predicate
Private Function peopleFilterCallback(ByVal de As Object) As Boolean

        Dim p As Person = TryCast(de, Person)

        Select Case _currentFilterType

            Case filterType.pHistoryType
                Return (p.pHistory = True And p.pNotInIpas = False)

            Case filterType.pMyListType
                Return (p.pMyList = True And p.pNotInIpas = False)

            Case filterType.pUseType
                Return (p.pUse = True And p.pNotInIpas = False)

            Case Else
                Return (p.pUse = True And p.pNotInIpas = False)

        End Select

    End Function

Open in new window


I am also checking the CurrentChanged event of the CollectionView and it fires a number of times.  In my test the Collection has 6 objects, after the filter the Collection still has 6 (which I think is right) and the CollectionView has 4.  This is expected.

The problem is that the dropdown of the autcomplete box still has the full 6 and has not changed.

In the CurrentChanged event I have also called the "colViewSource.Refresh()" method but this does not help.
0
darbid73
Asked:
darbid73
  • 6
  • 3
2 Solutions
 
ToddBeaulieuCommented:
Hello,

I think what's happening is that you've bound the the list source to a property and you're manipulating a view on the same underlying collection, but those are two different sources. The control isn't bound to the view, it's bound directly to the collection via the property. I think you need to change the pvPeople to return the VIEW on the collection, not the collection. Then when you manipulate the view, the control should update.

Hopefully I'm not steering you in the wrong direction.
0
 
darbid73Author Commented:
Thank you for picking up the question. I think we have a different understanding of this collectionview. Is what your saying still correct after you read Something like this here.
0
 
ToddBeaulieuCommented:
This link is what I was talking about:

http://xamlcoder.com/blog/2010/10/27/filtering-data-using-collectionviewsource/

Note that he's binding to the collection itself, as well as to the view and that the two lists show unfiltered and filtered content respectively.

I haven't coded in WPF in quite a while, so I'm real rusty, but I just worked up a quick sample that works with a dropdown. I don't know what the autocomplete control is, but I have to believe this is a binding issue and not control-specific.

Here's my test:

<Window x:Name="MainWindow"
    x:Class="WpfUseForQuickTests.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <CollectionViewSource Source="{Binding Path=Accounts}" x:Key="view" Filter="CollectionViewSource_Filter" />
    </Window.Resources>
    <StackPanel Height="100" Name="stackPanel1" Width="200">
        
        <ComboBox Height="23" Name="comboBox1" Width="120" ItemsSource="{Binding Source={StaticResource view}}"/>
        <CheckBox Content="Filter" Height="16" Name="Filter" Unchecked="Filter_Checked"  Checked="Filter_Checked" />
    </StackPanel>
</Window>

Open in new window


And the code behind:

using System.Windows;
using System.Collections.Generic;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows.Data;

namespace WpfUseForQuickTests
{
    public partial class Window1 : Window, INotifyPropertyChanged
    {
        private ObservableCollection<Account> _accounts = new ObservableCollection<Account>();
        public ObservableCollection<Account> Accounts { get { return _accounts; } }

        

        public class Account
        {
            public string AccountNumber { get; set; }
            public string CompanyName { get; set; }

            public override string ToString()
            {
                return string.Format("{0} : {1}", AccountNumber, CompanyName);
            }
        }

        public Window1()
        {
            InitializeComponent();
            MainWindow.DataContext = this;
            GenerateData();
        }

        private void GenerateData()
        {
            _accounts.Clear();
            _accounts.Add(new Account { AccountNumber = "101", CompanyName = "ACME" });
            _accounts.Add(new Account { AccountNumber = "102", CompanyName = "Microsoft" });
            _accounts.Add(new Account { AccountNumber = "103", CompanyName = "Sam Adams" });
            _accounts.Add(new Account { AccountNumber = "104", CompanyName = "Disney Studios" });
        }

        private Account _selectedAccount;
        public Account SelectedAccount
        {
            get
            {
                return _selectedAccount;
            }

            set
            {
                _selectedAccount = value;
                OnPropertyChanged("SelectedAccount");
            }
        }

        #region Change Notification

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }

        #endregion


        private void CollectionViewSource_Filter(object sender, System.Windows.Data.FilterEventArgs e)
        {
            Account account = e.Item as Account;

            if (this.Filter.IsChecked == true && account.CompanyName.EndsWith("s"))
                e.Accepted = false;
            else
                e.Accepted = true;
        }

        private void Filter_Checked(object sender, RoutedEventArgs e)
        {
            CollectionViewSource view = this.Resources["view"] as CollectionViewSource;
            view.View.Refresh();
        }

    }
}

Open in new window

0
Veeam Disaster Recovery in Microsoft Azure

Veeam PN for Microsoft Azure is a FREE solution designed to simplify and automate the setup of a DR site in Microsoft Azure using lightweight software-defined networking. It reduces the complexity of VPN deployments and is designed for businesses of ALL sizes.

 
darbid73Author Commented:
Hey I really appreciate the example, but I fear we going in two different directions.

I am really not sure you need to bind to the collection view.

When a user binds a WPF property to a collection of data, WPF automatically creates a view to wrap the collection, and binds the property to the view, not the raw collection. This behavior always happens, and is independent of CollectionViewSource.

Source;WPF's Collection View Source
0
 
darbid73Author Commented:
Ps. I am reading around on the different options and I see your suggestion is possible.
0
 
darbid73Author Commented:
Hi Todd,

Well it seems to be control specific and as per usual it does not take rocket science to come up with my theory on this.

All people reading this my experience with WPF and this AutoCompleteBox control is minimal and I have not looked at the source code of the control.  

When you start typing in the control it searches the itemssource for matches. I think and it sounds good that it always searches the itemssource itself and not the view of the collection.  It is my understanding that most controls when their itemssource is an observablecollection that what is actually displayed is a view of the observablecollections and thus you can manipulate what is seen by the user by filtering or grouping or sorting the view of the observablecollection through the ICollectionView interface.

As this control seems to ignore the view (at least I could not get it to work) I am now giving the control the view as the itemssource.

Thus I am binding the itemssource of the control to pvPeopleView which is a dependency property of my control.  Then I have
pvPeopleView = CollectionViewSource.GetDefaultView(pvPeople)

Open in new window

This gets the view fo the pvPeople observable collection.  After doing this if i then filter, sort or do anything to the CollectionView this is reflected in the drop down of the Autocompletebox.

One thing I have not tested yet is if I change the filter of the CollectionView after the control is initiated.  It might be the case that you need to use the "Populating" event of the AutoCompletebox to either set the itemssource again or to call the Refresh method of the CollectionView.
0
 
ToddBeaulieuCommented:
Honestly, that doesn't sound right at all. If you bind a control to a viewsource it will absolutely respect the filters, etc. There's be no reason to want to bypass that and go to the underlying connection.

I'll bet a you a donut of any kind that if you establish the view and filter up front (close to how I did it) you'll see the control filtered from the get-go.

If that's the case it proves the problem is in the difference with how you're accessing the view versus how the control is bound. I just installed the latest VS and toolkit last night and will help you test this if you'd like. I'm curious.
0
 
darbid73Author Commented:
I'll bet a you a donut of any kind that if you establish the view and filter up front (close to how I did it) you'll see the control filtered from the get-go.
You get a donut.  You are right about the view, but that is only half the story.

But I bet you a beer that if you bind it to a observablecollection, then WPF automatically shows the view, and that if you get the ViewSource from the collection with CollectionViewSource.GetDefaultView() then you can manipulate this ViewSource and thereby what is being seen by the user.  

It is like having your cake and eating it too.
0
 
darbid73Author Commented:
Thank you.
0

Featured Post

[Webinar] Cloud and Mobile-First Strategy

Maybe you’ve fully adopted the cloud since the beginning. Or maybe you started with on-prem resources but are pursuing a “cloud and mobile first” strategy. Getting to that end state has its challenges. Discover how to build out a 100% cloud and mobile IT strategy in this webinar.

  • 6
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now