Okay, so I have a IEnumerable
collection which I x:Bind
into a ListView
.
While the app is running I want whenever this IEnumerable
list changes, my listview
to be updated as well. This is not the case even when using INotifyPropertyChanged
, so I decided to move towards converting the IEnumerable
to an ObservableCollection
.
I found out that the cast that exists is:
myCollection = new ObservableCollection<object>(the_list);
That would work fine in a different problem, but in my case I am x:Bind this to the listview
and each time I run the method with the above code a new reference is created and the bind will not work (It doesn't work). Is that correct ? If yes how can I work around this ? If not, what am I doing wrong ? Thank you.
Currently my code looks like:
public ObservableCollection<IMessage> MessageList;
private async Task SetMessages()
{
MessageList = new ObservableCollection<IMessage>(await channel.GetMessagesAsync(NumOfMessages).Flatten());
}
What Worked for me:
Thanks solely to the Marian Dolinský's answer everything works now. I present some for of my code to make it more clear What I've done was:
class ChatViewModel : INotifyPropertyChanged
{
//Number of messages to have in list at once.
private int NumOfMessages = 20;
//Called when a user clicks on a channel.
public async Task SelectChannel(object sender, ItemClickEventArgs e)
{
//Set the channel to clicked item
channel = (SocketTextChannel)e.ClickedItem;
//Receive the messages in the list.
IEnumerable<IMessage> data = await channel.GetMessagesAsync(NumOfMessages).Flatten();
//Clear the observablecollection from any possible previous messages (if for example you select another channel).
messageList.Clear();
//Loop and add all messages to the observablecollection
foreach (var item in data)
{
messageList.Add(item);
}
}
//The event handler when a message is received.
private async Task Message_Received(SocketMessage arg)
{
//Calls to add message only if the message received is of interest.
if (arg.Channel == channel)
{
//Sets the thread equal to the UI thread.
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
//Adds the new message, removes the oldest to keep always 20.
messageList.Add(arg);
messageList.Remove(messageList[0]);
});
}
}
private ObservableCollection<IMessage> messageList = new ObservableCollection<IMessage>();
public ObservableCollection<IMessage> MessageList
{
get
{
return messageList;
}
}
}
And here is the code in XAML:
<Page.DataContext>
<vm:ChatViewModel x:Name="ChatViewModel"/>
</Page.DataContext>
<ListView VerticalContentAlignment="Bottom" ItemsSource="{x:Bind ChatViewModel.MessageList, Mode=OneWay}" SelectionMode="None" ItemTemplate="{StaticResource MessageListDataTemplate}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0,0,5,5"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Thanks again for your help :)
It's not updated because of how the ObservableCollection
works - it notifies only about changes inside the collection but when you are assigning the MessageList
in your SetMessages
method, you're creating new instance of ObservableCollection
and the ListView.Source
is pointing to the original bound instance of ObservableCollection
.
Also, I see that MessageList
is not a property, but field. Fields doesn't work with bindings.
You have four options how to achieve updating of the ListView
:
1) You could synchronize the existing MessageList
with new data:
private async Task SetMessages()
{
IEnumerable newData = await channel.GetMessagesAsync(20).Flatten();
// Assuming MessageList is not null here you will sync newData with MessageList
}
2) If you are not using the MessageList
collection anywhere else, you can set the ItemsSource
property of ListView
directly from code:
private async Task SetMessages()
{
YourListView.ItemsSource = await channel.GetMessagesAsync(20).Flatten();
}
3) Since you are using the x:Bind
expression, you can call Bindings.Update();
method everytime you want to refresh any binding on the page:
private async Task SetMessages()
{
MessageList = new ObservableCollection<IMessage>(await channel.GetMessagesAsync(20).Flatten());
Bindings.Update();
}
4) You could implement INotifyPropertyChanged
on the page or create DependencyProperty
for the MessageList
and then bind it with Mode
set to OneWay
:
<ListView ItemsSource="{x:Bind MessageList, Mode=OneWay}">
Personally I'd not recommend the fourth option. The best option would be to sync the data since ListView
automatically animates adding and removing the ListViewItems.
EDIT:
I think the problem is in these two lines:
messageList.Add(arg);
messageList.Remove(messageList[NumOfMessages - 1]);
since new items are added at the end of the collection, so you are removing the item you added last time. For deleting the oldest item, you should use messageList.RemoveAt(0);
to remove the item at the first position.