简体   繁体   中英

ListView not updating when the bound ObservableCollection changes

I'm working on an search window that loads search results into an ObservableCollection and then displays the results using a ListView.

Setting the ListView's ItemSource to the ObservableCollection after the search is complete populates the list correctly.

I'm trying to get the ListView to update as the search adds additional results, but the ListView doesn't populate with any data at all. I can't work out where my binding is falling over.

My research showed various ways of using DataContext, though none seem to help; I've tried assigning it to "this" and to my CachedData class using CodeBehind as well as at the xaml Window level.

Sorry for the long code snippets, I've left anything that I think may help add context to the question.

XAML:

<Window x:Class="SLX_Interface.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:SLX_Interface"
    mc:Ignorable="d"
    Title="SLX Search" Height="auto" Width="auto">
<Window.CommandBindings>
</Window.CommandBindings>
<Grid>
    <Grid.Resources>
        <local:CachedData x:Key="cachedData" />
    </Grid.Resources>
    <TabControl x:Name="tabControl" Grid.RowSpan="2" Margin="0,20,0,0">
        <TabItem Header="Accounts" Name="accountsTab">
            <Grid>
                <ListView x:Name="accountSearchResultsListView" Margin="5,32,5,30" DataContext="staticResource cachedData" ItemsSource="{Binding Path=accounts}" IsSynchronizedWithCurrentItem="True">
                    <ListView.View>
                        <GridView x:Name="accountSearchResultsGridView">
                            <GridViewColumn Header="SData Key" DisplayMemberBinding="{Binding SDataKey}"/>
                            <GridViewColumn Header="Account Name" DisplayMemberBinding="{Binding AccountName}"/>
                        </GridView>
                    </ListView.View>
                </ListView>
             </Grid>
        </TabItem>
    </TabControl>
</Grid>

Code-Behind from within MainWindow.xaml.cs:

private async void SearchAccount(string searchTerm, string searchField, string searchOperator)
    {
        //Create the string we'll use for searching
        string urlString = "Stuff";

        //Create an ObservableCollection, then use it to blank the cache
        ObservableCollection<Account> resultsList = new ObservableCollection<Account>();
        CachedData.accounts = resultsList;

        //Getting data from the search using an XML Reader
        XmlReader resultsReader = null;

        try
        {
            //Using XmlReader to grab the search results from SLX
            XmlUrlResolver resultsResolver = new XmlUrlResolver();
            resultsResolver.Credentials = LoginCredentials.userCred;

            XmlReaderSettings resultsReaderSettings = new XmlReaderSettings();
            resultsReaderSettings.XmlResolver = resultsResolver;
            resultsReaderSettings.Async = true;

            resultsReader = XmlReader.Create(urlString, resultsReaderSettings);
        }
        catch (Exception error)
        {

        }

        //Grabbing data from the XML and storing it, hopefully updating the ListView as we go
        using (resultsReader)
        {
            while (await resultsReader.ReadAsync())
            {
                while (resultsReader.ReadToFollowing("slx:Account"))
                {
                    //Setting data from the XML to a new Account object ready to be passed to the list
                    Account account = new Account();
                    account.GUID = new Guid();

                    resultsReader.MoveToFirstAttribute(); account.SDataKey = resultsReader.Value;
                    resultsReader.ReadToFollowing("slx:AccountName"); account.AccountName = resultsReader.ReadElementContentAsString();

                    CachedData.accounts.Add(account);

                    //--Uncommenting this gives odd results;
                    //--The first item is displayed, any others aren't.
                    //--If there are a lot of items, the application eventually errors like mad and ends.
                    //--Looks like one error window for each item, though I don't see the message before they die along with the application.
                    //accountSearchResultsListView.ItemsSource = CachedData.accounts;
                }
            }
        }

        //--Uncommenting this works but shows the data once the entire XML has been read through, which can take some time so isn't ideal.
        //accountSearchResultsListView.ItemsSource = CachedData.accounts;        }

Classes references above, stored in a separate .cs file but under the same namespace:

public class CachedData
{
    public static ObservableCollection<Account> accounts { get; set; }

    public static event PropertyChangedEventHandler PropertyChanged;

    public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged = delegate { };
    private static void NotifyStaticPropertyChanged(string propertyName)
    {
        StaticPropertyChanged(null, new PropertyChangedEventArgs(propertyName));
    }
}

public class Account : IEquatable<Account>
{
    public Guid GUID { get; set; }
    public string SDataKey { get; set; }
    public string AccountName { get; set; }

    public override string ToString()
    {
        return AccountName;
    }

    public override bool Equals(object obj)
    {
        if (obj == null) return false;
        Account objAsPart = obj as Account;
        if (objAsPart == null) return false;
        else return Equals(objAsPart);
    }

    public override int GetHashCode()
    {
        return 0;
    }

    public bool Equals(Account other)
    {
        if (other == null) return false;
        return (GUID.Equals(other.GUID));
    }
}

I appreciate any help you can offer, this has stumped me for days now.

When you set a data binding in xaml, an instance of ObservableCollection which exists when the app starts will be bound. So DO instantiate an instance before the app starts and DO NOT replace it with new instance unless you reset the data binding in code behind. If you need to clear its elements, use Clear method.

The problem is you are using ObservableCollection which implements INotifyCollectionChanged internally. which doesn't raise every change to collection. It will raise a collection change only when an item is added or removed from collection.

So the problem occurs what will happen if someone assign a new instance of collection ( as your case ). so resetting the binding is not really good option instead you can raise the change yourself. by simply implementing the INotifyPropertyChanged.(usual cases)

public class DataClass : INotifyPropertyChanged
{

    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }


    private ObservableCollection<string> collection;

    public ObservableCollection<string> Collection
    {
        get { return collection; }
        set
        {
            collection = value;
            OnPropertyChanged("Collection");
        }
    }       
}

so assigning the collection to null or a new instance will also get reflected to bound control. (you have already have NotifyStaticPropertyChanged you just need to create a full property and just raise the change when required.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM