xamarin.forms : ListView not updating as expexcted - c#

I have built a app using Xamarin.Forms that I am displaying in Windows.
Requirement:
- tap on item in the ListView >
- the displayed value for the item changes in the UI
Here is the relevant xaml
NOTE: I have set the ListView.ItemsSource from code behind
<ListView.ItemTemplate >
<StackLayout VerticalOptions="Center" HorizontalOptions="Center" >
<Label Text="Passo dopo passo" VerticalOptions="Center" HorizontalOptions="Center" />
<Button Clicked="GetGroceries" Text="Get Groceries" ></Button>
<ListView x:Name="lvGroceries" ItemTapped="GroceryItemTapped" >
<ListView.ItemTemplate >
Here is the c# code behind, which runs fine when the item is tapped, but the UI does not change.
public ObservableCollection oc;
public void GroceryItemTapped (object o, ItemTappedEventArgs e )
{
if (e.Item == null ) {
return;
}
var g = ((GroceryItem)e.Item);
// oc is populated and exactly what I expect here
foreach (var gr in oc)
{
// the next line grabs the item I want
if (gr.GroceryId == g.GroceryId)
{
gr.GroceryName = "snot";
} } }
// when the above code runs, the data is already in oc and this code ran
lvGroceries.ItemsSource = oc;
Ideas?

You need to bind your ListView to your collection:
<ListView x:Name="lvGroceries" ItemsSource="{Binding oc}" ItemTapped="GroceryItemTapped" >
You also need to set your ContentPage.BindingContext. You also need to implement INotifyPropertyChanged and run OnPropertyChanged() anytime your collection is updated. Finally, you need to add to your collection by doing oc.Add() instead of reseting the ItemSource every time because that will overwrite your binding.
Highly suggest looking through the Xamarin Forms Binding Documentation.

Related

How to get ListView item index from custom ViewCell in Xamarin Forms?

I created a ListView that have a custom ViewCell as below:
<ListView x:Name="ListView1" ItemTapped="ListView1_ItemTapped"
SeparatorVisibility="None" RowHeight="192" HasUnevenRows="False"
FlowDirection="RightToLeft" CachingStrategy="RecycleElement" >
<ListView.ItemTemplate>
<DataTemplate>
<custom:ViewCell1 />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
and here is the XAML for the custom ViewCell
<ViewCell.View>
<StackLayout>
<Label Text="{Binding Name}" />
<Label Text="{Binding ID}" />
<Button x:Name="Button1" Text="Get index" Clicked="Button1_Clicked" />
</StackLayout>
</ViewCell.View>
All I need is when I click on Button1, I get the ListView1 item index (or ViewCell index)
The problem is I can't access ListView1 from Button1_Clicked event from the code behind in the custom ViewCell and gets tapped item index of ListView1 (or even gets ViewCell tapped item index).
I searched a lot, and found that it can be done by 3 ways:
1- Create an attached property for the ViewCell to get its index.
2- Use indexer for the ViewCell and get its index.
3- Use ITemplatedItemsView interface as mentioned in this question
But unfortunately I couldn't implement any of them from Button1_Clicked event in the custom ViewCell code behind because I'm not an expert in either MVVM or C#.
Can I have an expert assistance please.
Thanks
There are many ways which can implements it . If you are new to data binding and MVVM. I will provide the easiest way .
Firstly , add a property in the model of ItemSource .
public class YourModel
{
public int Index { get; }
//other properties like name and ID
public YourModel(int index)
{
Index = index;
}
}
And set the value of Index when init the ItemSource of ListView .
sources = new ObservableCollection<YourModel>() { };
for(int i=0;i<20;i++)
{
sources.Add(new YourModel(i) { /**other propertes**/});
}
In CustomCell
Get it like following
var model = this.BindingContext as YourModel;
int index = model.Index;
try using Button1.Parent.Parent.Parent... and so on, unless you get your listview's object.
Also pass BindingContext of viewcell in button's CommandParameter like CommandParameter={Binding} and then get the index of your received object from ItemsSource.

Issue displaying images with FlowListView & FFImageLoading in Xamarin.Forms

In XAML & C# using Xamarin.Forms (iOS Project) I'm trying to create a gallery where the user can add photos to it. Currently I can show a list that does have the photo data binded to each entry because the user can click on an item in the list and the correct image will show up.. However I have not been successful in actually displaying a smaller version of the pictures in my FlowListView. I know it has to be something with the binding and I'm trying to grab the image uri from each object to display it but I'm still pretty new to this, especially to xaml and haven't been successful with this part yet.
If you could point me in the right direction that would be sweet!
Expected Result
To display images in 2 columns using FlowListView and FFImageLoading
Actual Result
I currently an able to display a 2 column list that has the right objects tied to each item but the only way I can actually see if anything is there is if I add a frame or add a text label to each item.
The user can click on each label currently and the correct image will show.
This is part of my TicketPageModel
private void AddItems()
{
public void UpdatePhotosData()
{
//get the notes and set the source for the list to them.
photos = NLTicketPhotos.Get(_ticket).OrderByDescending(x => x.PhotoCreatedAt).ToList();
}
foreach (var i in photos)
{
Items.Add(i);
}
}
private ObservableCollection<NLTicketPhoto> _items = new ObservableCollection<NLTicketPhoto>();
public ObservableCollection<NLTicketPhoto> Items
{
get
{
return _items;
}
set
{
if (_items != value)
{
_items = value;
OnPropertyChanged(nameof(Items));
}
}
}
My XAML
<flv:FlowListView FlowColumnCount="2" SeparatorVisibility="Default" HasUnevenRows="True" FlowItemTapped="OnImageTapped" FlowItemsSource="{Binding Items}">
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<StackLayout Padding="10" Spacing="0" AutomationProperties.Name="Too Cool">
<ff:CachedImage Aspect="AspectFill" HeightRequest="30">
<ff:CachedImage.Source>
<UriImageSource Uri="{Binding Items.PhotoFileUrl}"/>
</ff:CachedImage.Source>
</ff:CachedImage>
<StackLayout Orientation="Horizontal" Padding="10" Spacing="0">
<Label HorizontalOptions="Fill" VerticalOptions="Fill" TextColor="Black" XAlign="Center" YAlign="Center" Text="Too Cool For School"/>
</StackLayout>
</StackLayout>
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
My Code Behind
void OnImageTapped(object sender, ItemTappedEventArgs e)
{
NLTicketPhoto photo = (NLTicketPhoto)e.Item;
//listPhotos.SelectedItem = null; //deselect the item
switch (photo.PhotoFileType)
{
case "mov":
if (photo.PhotoIsOnServer)
{
Device.OpenUri(new Uri(photo.PhotoFileName));
}
else
{
//Only watch videos after sync
DisplayAlert("Video Unavailable", string.Format("This video will be viewable after it is synced to the server."), "OK");
}
break;
case "jpg":
//View image
NLPageViewPhoto preview = new NLPageViewPhoto(photo);
Navigation.PushAsync(preview);
break;
default:
DisplayAlert("Photo Unavailable", string.Format("The photo format .{0} is currently not viewable", photo.PhotoFileType), "OK");
break;
}
}
Once I implemented INotifyPropertyChanged to my NLTicketPhoto model and tweaked my PhotoUrl and PhotoDescription properties to use OnPropertyChanged() I was able to get my list to display properly.
Following this example helped me. https://www.c-sharpcorner.com/article/grid-view-in-xamarin-forms-using-flowlistview/

New Xamarin.Forms CollectionView doesn't allow multi pre-selection

I have a CollectionView in my Xamarin.Forms project:
<CollectionView ItemsSource="{Binding Categories}" ItemSizingStrategy="MeasureFirstItem" x:Name="CategoryColView"
SelectionMode="Multiple" SelectionChangedCommand="{Binding SelectionChangedCommand}"
SelectionChangedCommandParameter="{Binding Source={x:Reference CategoryColView}, Path=SelectedItems}"
SelectedItems="{Binding SelectedCategoryItems}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout ...>
<BoxView .../>
<StackLayout ...>
<Label .../>
<Image .../>
</StackLayout>
<BoxView/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
I included the entire XAML element, but the only important part is the SelectedItems property. It is bound to the following viewmodel implementation:
class ViewModel {
private ObservableCollection<object> selectedCategories { get; set; }
public ObservableCollection<object> SelectedCategories {
get => selectedCategories;
set {
selectedCategories = value;
OnPropertyChanged();
}
//...
ctor() {
//...
var alreadySelectedCategoryItems = alreadySelectedCategories.Select(pc => new CategoryItem { PlantCategory = pc, IsSelected = true }).Cast<object>();
SelectedCategoryItems = new ObservableCollection<object>(alreadySelectedCategoryItems);
//...
}
}
The rest of the implementation should be irrelevant. My aim is to have pre-selected values.
First: I noticed that if the T in ObservableCollection<T> is not object, everything is ignored. Just like in Microsoft's example here. If the T is e.g. of type CategoryItem, literally nothing happens, as if the ObserveableCollection were completely ignored.
Second: alreadySelectedCategoryItem contains 2 elements in debugger mode, but then the last line in the constructor throws a:
System.ArgumentOutOfRangeException
Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
Of course, since this is Xamarin.Forms and VS for Mac, the error is thrown on the Main function, not at its actual location...
Am I doing something wrong, or is CollectionView just still buggy?
The issue was that I was creating new CategoryItem instances as the pre-selected ones, which is invalid, as they weren't by default the same instances that were in the CollectionView.ItemsSource property. I should have filtered the ItemsSource instances and put them as the pre-selected ones. Like this:
var alreadySelectedCategoryItems = alreadySelectedCategories.Select(pc => new CategoryItem { PlantCategory = pc, IsSelected = true }).Cast<object>();
SelectedCategoryItems = Categories
.Where(sci =>
alreadySelectedCategoryItems.Any(alreadySelected =>
alreadySelected.PlantCategory.Id == sci.PlantCategory.Id);
So the items are selected off the ItemsSource itself and not created as new.
Although the error message was not as desired, so Xamarin.Forms team is going to fix that.
SelectedItems is read-only,you could not use like SelectedItems="{Binding SelectedCategoryItems}" in xaml
you could try to change in your behind code like:
CategoryColView.SelectedItems.Add(your selectItem 1);
CategoryColView.SelectedItems.Add(your selectItem 2);
CategoryColView.SelectedItems.Add(your selectItem 3);

Xamarin Forms ListView Checkmark

I'm trying to implement a ListView in Xamarin Forms. A list that we can check or choose the item that we want. I want a single item selection at a time.
My xaml file :
ListView x:Name="listview" ItemSelected="OnItemSelected" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout HorizontalOptions="StartAndExpand" Orientation="Horizontal">
<StackLayout Padding="20,0,0,0" VerticalOptions="Center" Orientation="Vertical">
<Label Text="{Binding .}" YAlign="Center" FontSize="Medium" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
My xaml.cs file :
public void OnItemSelected (object sender, SelectedItemChangedEventArgs e) {
if (e.SelectedItem == null) return;
// add the checkmark in the event because the item was clicked
// be able to check the item here
DisplayAlert("Tapped", e.SelectedItem + " row was tapped", "OK");
((ListView)sender).SelectedItem = null;
}
There is a better way to do it?
I want something like this without alphabet and search menu :
Take a look on this https://github.com/ricardoromo/Listview-Multiselect, I made this sample to simulate miltiselect listview.
You could try using XLabs checkbox or radioButton control, Here is a list of controls examples, you just need to download the package via NuGet Manager.
If you only need to check 1 item, in your tapped event make an action after item is tapped. Here's an example:
async void FriendListView_ItemTapped(object sender, ItemTappedEventArgs e)
{
var el = e.Item as ProfileItem;
SelectedItem = el;
if (e.Item != null)
{
await Navigation.PushAsync(new FriendProfile(el));
}
((ListView)sender).SelectedItem = null; // de-select the row
}
I've tried the Multiselection listview in xamarin forms and I got it working for both iOS and Android.
This took me some time so I wrote a blog about the entire prototype. You can find it here :
http://androidwithashray.blogspot.com/2018/03/multiselect-list-view-using-xamarin.html
The blog gives you full info on creating the Listview with the checkbox and selecting multiple items. Hope this helps!!

How to access an inner ItemsControl in a ListBox in Windows Phone Silverlight

I am building a small Windows Phone application which has a databound ListBox as a main control. DataTemplate of that ListBox is a databound ItemsControl element, which shows when a person taps on a ListBox element.
Currently, I am accessing it by traversing the visual tree of the application and referencing it in a list, and than getting the selected item through SelectedIndex property.
Is there a better or more effective way?
This one works currently, but I am afraid if it would stay effective in case of larger lists.
Thanks
Have you tried wiring the SelectionChanged event of the ListBox?
<ListBox ItemsSource="{Binding}" SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- ... -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
With this in the code behind:
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox listBox = sender as ListBox;
// nothing selected? ignore
if (listBox.SelectedIndex != -1)
{
// something is selected
}
// unselect the item so if they press it again, it takes the selection
listBox.SelectedIndex = -1;
}
ListBoxItem item = this.lstItems.ItemContainerGenerator.ContainerFromIndex(yourIndex) as ListBoxItem;
Then you can use the VisualTreeHelper class to get the sub items
var containerBorder = VisualTreeHelper.GetChild(item, 0) as Border;
var contentControl = VisualTreeHelper.GetChild(containerBorder, 0);
var contentPresenter = VisualTreeHelper.GetChild(contentControl, 0);
var stackPanel = VisualTreeHelper.GetChild(contentPresenter, 0) as StackPanel; // Here the UIElement root type of your item template, say a stack panel for example.
var lblLineOne = stackPanel.Children[0] as TextBlock; // Child of stack panel
lblLineOne.Text = "Some Text"; // Updating the text.
Another option is to use services of the GestureServices class available in the WP7 Toolkit.
You'll need to add a GestureListner to the Root Element of your DataTemplate like so:
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Controls:GestureService.GestureListener>
<Controls:GestureListener Tap="GestureListener_Tap" />
</Controls:GestureService.GestureListener>
<TextBlock x:Name="lblLineOne" Text="{Binding LineOne}" />
<TextBlock Text="{Binding LineTwo}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
And in the GestureListener_Tap event handler, you use this snippet.
private void GestureListener_Tap(object sender, GestureEventArgs e)
{
var itemTemplateRoot = sender as StackPanel;
var lbl1 = itemTemplateRoot.Children[0] as TextBlock;
MessageBox.Show(lbl1.Text);
}
I'm not sure how the GestureListner recognize internally the item being tapped but I guess that it uses the VisualTreeHelper, at least this method is more concise.

Categories

Resources