How do you dynamically update a collection view in .net maui? - c#

I have a test app im working with and im populating the collection view with a sqlite DB. But whenever I use a swipe view to delete the item from the database it works fine but the view never removes the item from the collection view unless the entire view is reloaded. I've tried a few different things, but nothing has any effect, Any recommendations? Would the OnAppearing life cycle cause any issues?
<Grid BackgroundColor="White">
<StackLayout Margin="20">
<CollectionView x:Name="data"
SelectionMode="Single"
SelectionChanged="ItemSelected"
HeightRequest="750"
VerticalScrollBarVisibility="Never">
protected override async void OnAppearing()
{
base.OnAppearing();
TodoItemDatabase database = await TodoItemDatabase.Instance;
data.ItemsSource = await database.GetItemsAsync();
}

From the docs
If the CollectionView is required to refresh as items are added,
removed, or changed in the underlying collection, the underlying
collection should be an IEnumerable collection that sends property
change notifications, such as ObservableCollection.

Related

How to refresh a Telerik datagrid using UWP and MVVM Light

Thanks for visiting.
I am having trouble refreshing a Telerik grid when the Refresh button is clicked. In other words, if the user changes the data and decides not to save it, clicking the refresh button should reload the grid with the original values.
Here is my XAML in the View for the Refresh:
<Button Content="Refresh" Grid.Row="1" Margin="92,5,0,11" Command="{x:Bind ViewModel.RefreshDataCommand}"/>
Here is my XAML in the View for the Grid:
<tg:RadDataGrid ColumnDataOperationsMode="Flyout" x:Name="grid" ItemsSource="{Binding Source,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectedItem="{x:Bind ViewModel.CurrentUserData, Mode=TwoWay}" UserEditMode="Inline" Grid.ColumnSpan="4" Grid.Row="1" AutoGenerateColumns="False">
Notice that in the above code the grid is bound to the Source property in the ViewModel. Here is the bound property in the ViewModel:
public ObservableCollection<UserData> Source
{
get
{
try
{
udCol = GetUserData(FileName).Result;
return udCol;
}
catch (AggregateException ex)
{
return null;
}
}
set
{
udCol = value;
}
}
The above property automatically loads the data in the grid when the page is navigated to.
Here is the function that the Refresh button is bound to:
private void RefreshDataCommandAction()
{
udCol[0].Name = "test1";
CurrentUserData = udCol[0];
Source = udCol;
RaisePropertyChanged("Source");
}
I was experimenting in the above function, that is why the code looks redundant but no matter what I do the new assignment in this function does not update the UI. Ideally, the user will update the cell in the UI and click Refresh to go back to original or just reload the data. The ViewModel inherits ViewModelBase which contains INotifyPropertyChanged and I thought that that should be enough to propagate the changes to the UI when the property is changed. I don't want to break the MVVM pattern just to update the UI.
I would really appreciate some help. Thanks a lot in advance!!!
EDIT:
I changed my XAML in the View back to this because it broke my ADD functionality:
<tg:RadDataGrid ColumnDataOperationsMode="Flyout" x:Name="grid" ItemsSource="{x:Bind ViewModel.Source}" SelectedItem="{x:Bind ViewModel.CurrentUserData, Mode=TwoWay}" UserEditMode="Inline" Grid.ColumnSpan="4" Grid.Row="1" AutoGenerateColumns="False">
Source does not implement Change Notification, so the UI has no way of knowing you assigned a different instance.
When binding any form of list in MVVM, 3 parts need change notification:
the propery you expose the list on (source)
the list itself (observable collection takes care of that)
each property of the type you are exposing (every property of UserData)

UWP ListView only displays data sometimes

My data from an ObservableCollection only sometimes displays on my ListView. If I restart the app, the data displays fine. Sometimes when I navigate away from the page and go back, the data will sometimes display and other times not. It seems to be random.
Here is my XAML code:
<ScrollViewer Grid.Row="2" Margin="0,42,0,0">
<Grid>
<ListView ItemsSource="{x:Bind collection, Mode=OneWay}" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Top" IsItemClickEnabled="True" SelectionChanged="MySelectionChanged" Visibility="Visible">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:ObjectName">
<TextBlock Text="{x:Bind Data0, Mode=OneWay}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView ItemsSource="{x:Bind collection, Mode=OneWay}" HorizontalAlignment="Left" Margin="375,0,0,0" VerticalAlignment="Top" SelectionMode="None" Visibility="Visible">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:ObjectName">
<TextBlock Text="{x:Bind Data1, Mode=OneWay}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ScrollViewer>
I tried doing a Debug.WriteLine() on all the data that should be displayed, and the debug output successfully prints it every time. There's only a problem when it comes to displaying this data on the ListView.
EDIT: Here's the corresponding xaml.cs code:
private ObservableCollection<ObjectName> collection;
private List<ObjectName> sorted;
private ObjectName clicked = new ObjectName();
public MainPage()
{
GetSave();
this.InitializeComponent();
}
private async void GetSave()
{
DataStorage ds = new DataStorage();
await ds.DeserializeObjectAsync();
collection = ds.ObjectName;
if (collection != null)
{
sorted = new List<ObjectName>(collection);
sorted.Sort((x, y) => string.Compare(x.Data0, y.Data0));
collection = new ObservableCollection<ObjectName>(sorted);
}
}
I tried InitializeComponent() before and after the logic, and it gives the same result.
Based on the xaml.cs code you provided, the problem looks like this:
First, you are not awaiting an async call.
Second, you are replacing the ObservableCollection.
The solution:
Don't call async code from your page's constructor. Async calls are
called async for a reason and trying to 'hide' their true nature by
omitting the await keyword when calling them won't work. Change GetSave()'s
return value to Task, override the OnNavigatedTo() method (read more
about it in it's documentation) of MainPage and await GetSave()
inside that. This alone won't solve your problem though.
Never replace an ObservableCollection. I remember banging my head on the
wall multiple times because of this. What you need to understand is
that when you are binding to an object in XAML, a dedicated Binding
object is created that links the source (in your case, the
ObservableCollection) and the target (the ListView) together. In
your code, initially 'collection' is set to null. When your MainPage
is created, the Binding object is created as well and it binds that
null value to your ListViews' Source property. Later, when your
async initialzation code is finished, you replace that null value
with an actual ObservableCollection, but the ListViews are not
notified about that, they are only looking for changes in the
collection's items that they are bound to, they are not prepared for
handling the situation when the collection itself is swapped
under them. So what you can do to fix this problem is: only create
your ObservableCollection instance in MainPage's constructor (or at
declaration - matter of taste in this case) and in GetSave() first
call collection.Clear() and then add your items to it with
collection.Add().
What currently happening in your code is that in some cases you are replacing the ObservableCollection before the Binding object is created and sometimes after that, so that's why it looks like you app's behavior is totally random - because actually it is. :) By adding the modifications I suggested above, you'll make sure that the Binding object is bound to the ObservableCollection you created in the constructor (empty at the time of the binding), and then you are initializing that collection after MainPage is already loaded, so your ListViews are getting notified about the changes.

Unable to Programmatically Navigate Dynamic ListView

<ListView x:Name="myListView" ItemsSource="{x:Bind PageViewModel.myCollectionOfThings, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewmodels:ThingViewModel>
<TextBlock Text="{x:Bind Name, Mode=OneWay}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Name="TestButton" Tapped="TestButton_Tapped"/>
private void TestButton_Tapped(object sender, TappedRoutedEventArgs e)
{
PageViewModel.myCollectionOfThings.Add(newItem);
myListView.SelectedIndex++;
}
I'm trying to create a carousel control**. Using the code above I can dynamically add an item to the end of the collection each time its selected index increases. It works as one would expect except one problem. Even though the UI is reflecting the new items added to the end of the ListView, if I iterate past the last index of the original collection size, the UI jumps back to the beginning of the list and the selected index becomes 0. I've tried many variations of the above code and tried different collection types. I've also tried re-assigning the ListView's ItemSource each iteration which didn't do anything. Any help would be appreciated.
**I know there's something called Carousel in the UWP Community Toolkit but it isn't actually a carousel. A carousel can scroll endlessly as its collection will loop, which that control does not do.
If you want Carousel that can scroll endlessly then take a look at the Carousel control in the Windows AppStudio NuGet package. Download Windows App Studio UWP Samples to learn about the control.
Here an image of this Carousel Control
It looks like the listview is rebinding with binding source when the collection is changed. Try to re-set the index manually after adding the item.
int currentIndex = myListView.SelectedIndex;
PageViewModel.myCollectionOfThings.Add(newItem);
myListView.SelectedIndex = currentIndex++;
// If above doesn't work try setting selectedItem property to newItem.

DataTemplate not loading in pivot windows phone 8.1

I'm trying to display different layouts in pivot items in a wp8.1 app (UNIAPP ). Ideally I would like to load different pages but since I could figure this out, I thought I'd try with the basics first as I'd use this before but for some reason I can't get this to work.
My pivot items are loaded dynamically based on the provided ViewModel
<Pivot.ItemTemplate>
<DataTemplate>
<controls:DataTemplateSelector Content="{Binding}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
</controls:DataTemplateSelector>
/DataTemplate>
</Pivot.ItemTemplate>
My resources are defined as follows within the same xaml page
<Page.Resources>
<DataTemplate x:Key="MyApp.ViewModel.PIDetailsVM">
<Button Content="test" Foreground="White"></Button>
</DataTemplate>
<DataTemplate x:Key="MyApp.ViewModel.PIListVM">
<Button Content="test" Foreground="White"></Button>
</DataTemplate>
</Page.Resources>
My DataTemplateSelector is defined as follows:
public class DataTemplateSelector : ContentControl
{
protected override void OnContentChanged(object oldContent,
object newContent)
{
ContentTemplate = this.FindResource<DataTemplate>(newContent.GetType
().FullName);
}
}
It is being triggered whenever I go to a new pivot item, but the ContentTemplate is always null.
The newContent.GetType().FullName returns the relevant viewmodel name which I can see being displayed in the relevant pivot.
One thing I noticed is that the DataTemplateSelector class (this) has no resources when I check it via this.Resources.count(), so it's obviously not finding them but how do I fix this?
UPDATE:
My DataTemplates are not getting loaded in my Pivot Items. There is obviously a problem with the .NET IDE as whenever I add or remove a from Content="{Binding}" it displays the button within the pivot item but that's within the IDE. Unfortunately, at run-time, it just displays the name of my viewmodel.
Thought the behaviour is erratic in the IDE, the fact that the button from my DataTemplate is displaying when messing around with the Content="{Binding<space>" would make you think that the code and xaml are correct but it's definitely not working at run-time.
Any idea what's wrong why my DataTemplates are not displaying in pivot item?
Thanks.
This is a partial answer. By this I mean that I did find a work-around to my problem but I did not resolve the issue itself.
My DataTemplateSelector which gets triggered whenever the pivot changes call a extension function called FindResource:
public static class ControlExtensions
{
public static T FindResource<T>(this DependencyObject initial,
string key) where T : DependencyObject
{
DependencyObject current = initial;
while (current != null)
{
if (current is FrameworkElement)
{
if ((current as FrameworkElement).Resources.
ContainsKey(key))
{
return (T)(current as FrameworkElement).Resources[key];
}
}
current = VisualTreeHelper.GetParent(current);
}
if (Application.Current.Resources.ContainsKey(key))
{
return (T)Application.Current.Resources[key];
}
return default(T);
}
}
For some strange reason, Windows Phone 8.1 (WinRT) does not like having the data templates in while it is not a problem in WP8/WP8.1 Silverlight.
As mentioned, this is unstable in the IDE where it sometimes displays the DataTemplate, and sometimes it doesn't depending on whether or not I add a space after the Binding keyword to the Content="{Binding}". One thing for sure is that it never works at run-time, well not at least not with the above code.
VisualTreeHelper.GetParent(current) always returns null no matter what. I've checked at debug time if I somehow could access the resources, but to no avail.
How did I fix it? Well, I moved my data templates to a resource dictionary
<DataTemplate x:Key="MyApp.ViewModel.PIDetailsVM">
<Button Content="test" Foreground="White"></Button>
</DataTemplate>
<DataTemplate x:Key="MyApp.ViewModel.PIListVM">
<Button Content="test" Foreground="White"></Button>
</DataTemplate>
The second I did this, the second part of my FindResources kicks in since the Current object is always null, no matter what
if (Application.Current.Resources.ContainsKey(key))
{
return (T)Application.Current.Resources[key];
}
and it finds the relevant DataTemplate and displays it accordingly in my pivot control based on the relevant PivotItem ViewModel.
Now, I'm not out of the woods yet as I have no idea if binding to the relevant viewmodel will work but that's a whole other story!
If anyone knows why DataTemplate cannot be found when defined in Pages.Resources or Grid.Resources, please update the post as I'd love to know why.
Thanks.

WPF ListBox Multi-Select binding

I have two listBoxes one on the left and one on the right. When I select a 'contactList' item on the left listBox the 'label' information should be displayed on the right listBox and this part works fine. The problem I am having is to do with multi-select because at the moment it will only display the information from one selection. I changed Selection mode in my XAML to multi-select but that did not seem to work. Would appreciate any assistance. Thanks.
XAML
<Grid x:Name="LayoutRoot" Background="#FFCBD5E6">
<ListBox x:Name="contactsList" SelectionMode="Multiple" Margin="7,8,0,7" ItemsSource="{Binding ContactLists, Mode=Default}" ItemTemplate="{DynamicResource ContactsTemplate}" HorizontalAlignment="Left" Width="254" SelectionChanged="contactsList_SelectionChanged"/>
<ListBox x:Name="tagsList" Margin="293,8,8,8" ItemsSource="{Binding AggLabels, Mode=Default}" ItemTemplate="{StaticResource TagsTemplate}" Style="{StaticResource tagsStyle}" />
</Grid>
Code
private void contactsList_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (contactsList.SelectedItems.Count > 0)
{
CollectionViewGroup collectionView = contactsList.SelectedItems[0] as CollectionViewGroup;
ContactList selectedContact = contactsList.SelectedItems[0] as ContactList;
ObservableCollection<AggregatedLabel> labelList = new ObservableCollection<AggregatedLabel>();
foreach (ContactList contactList in collectionView.Items)
{
foreach (AggregatedLabel aggLabel in contactList.AggLabels)
{
labelList.Add(aggLabel);
tagsList.ItemsSource = labelList;
}
}
}
}
I think everyone is confused about this part
CollectionViewGroup collectionView = contactsList.SelectedItems[0] as CollectionViewGroup;
ContactList selectedContact = contactsList.SelectedItems[0] as ContactList;
you're only looking at the first selected item. (SelectedItems[0]), but treating it as one thing or another?
you probably need something like
// only create the list once, outside all the loops
ObservableCollection<AggregatedLabel> labelList = new ObservableCollection<AggregatedLabel>();
foreach (var selected in contactsList.SelectedItems)
{
// pretty much your existing code here, referencing selected instead of SelectedItems[0]
}
// only set the list once, outside all the loops
tagsList.ItemsSource = labelList;
ideally, you wouldn't be setting the items source on the tagsList, you'd have that bound to a collection already, and you'd just be replacing the contents in this method. (just one call to clear the collection at the top, and no call to set ItemsSource, since it would have already been bound)
I don't really get what you are doing there at all with all that code but how you normally approach the kind of scenario you described is by binding the second ListBox directly to the first one, should look something like this:
<ListBox Name="ListBox1" ItemsSouce="{Binding SomeOriginalSource}" .../>
<ListBox ItemsSouce="{Binding ElementName=ListBox1, Path=SelectedItems}".../>
Edit: You then can either use a DataTemplate which enumerates the internal collections (which for example could cause you to have a ListBox containing other ListBoxes), or you add a converter to the the binding which merges the internal collections into a single collection like John Gardner noted.

Categories

Resources