CollectionView updating ImageButton scroll repeating UI issue - c#

I've got a CollectionView containing an ImageButton. When the image is pressed I replace a.png with b.png.
This is working fine but when I scroll down the list, every 10th item is now showing b.png!
If instead of setting the button.source, I called the line below again after saving to the DB which solves my problem but then I start at the top of the list and not at the current position I was at:
ItemsListView.ItemsSource = items;
How can I set the button.source without it creating this bug on every 10th item?
<CollectionView x:Name="Items">
<CollectionView.ItemTemplate>
<DataTemplate>
<ImageButton CommandParameter="{Binding Id}" Source="a.png" Clicked="OnInventoryClicked" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
void OnInventoryClicked(object sender, EventArgs e)
{
var button = (sender as ImageButton);
var itemId = button.CommandParameter.ToString();
var inventoryItem = await App.Database.GetItemAsync(itemId);
inventoryItem.IsInInventory = !inventoryItem.IsInInventory;
await App.Database.SaveItemAsync(inventoryItem);
button.Source = inventoryItem.IsInInventory? "b.png" : "a.png";
}

You could change with the souce property.
Xaml:
<CollectionView x:Name="Items" ItemsSource="{Binding infos}">
<CollectionView.ItemTemplate>
<DataTemplate>
<!--<ImageButton
Clicked="OnInventoryClicked"
CommandParameter="{Binding Id}"
Source="a.png" />-->
<ImageButton Clicked="ImageButton_Clicked" Source="{Binding image}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Code behind:
public partial class MainPage : ContentPage
{
public ObservableCollection<Info> infos { get; set; }
public MainPage()
{
InitializeComponent();
infos = new ObservableCollection<Info>()
{
new Info{image = "pink.jpg" },
new Info{image = "pink.jpg" },
new Info{image = "pink.jpg" },
new Info{image = "pink.jpg" },
new Info{image = "pink.jpg" },
};
this.BindingContext = this;
}
private void ImageButton_Clicked(object sender, EventArgs e)
{
var button = (sender as ImageButton);
button.Source = "dog.jpg";
}
}
public class Info
{
public string image { get; set; }
}

Related

Re-render the screen when an object changes

I would like to slightly modify the code generated when creating a maui project to implement the following
add an object to Meetings in MainPage.xaml.cs when the button is clicked
display the contents of that Meetings
I wrote the following code for this purpose, but there is no change in the output content. One possible reason for this is that adding data to the object does not re-render the screen. How can I solve this problem?
Views/MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App.Views"
x:Class="App.Views.MainPage">
<ScrollView>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<Image
Source="dotnet_bot.png"
SemanticProperties.Description="Cute dot net bot waving hi to you!"
HeightRequest="200"
HorizontalOptions="Center" />
<Label
Text="Hello, World!"
SemanticProperties.HeadingLevel="Level1"
FontSize="32"
HorizontalOptions="Center" />
<Label
Text="Welcome to .NET Multi-platform App UI"
SemanticProperties.HeadingLevel="Level2"
SemanticProperties.Description="Welcome to dot net Multi platform App U I"
FontSize="18"
HorizontalOptions="Center" />
<Button
x:Name="CounterBtn"
Text="Click me"
SemanticProperties.Hint="Counts the number of times you click"
Clicked="OnCounterClicked"
<ListView ItemsSource="{Binding Meetings}" />
</VerticalStackLayout>
</ScrollView>
</ContentPage>
Views/MainPage.xaml.cs
namespace App.Views;
using App.Models;
public partial class MainPage : ContentPage
{
int count = 0;
public MainPage()
{
InitializeComponent();
BindingContext = new Models.AllMeetings();
}
private void OnCounterClicked(object sender, EventArgs e)
{
count++;
if (count == 1)
CounterBtn.Text = $"Clicked {count} time";
else
CounterBtn.Text = $"Clicked {count} times";
SemanticScreenReader.Announce(CounterBtn.Text);
((Models.AllMeetings)BindingContext).Meetings.Add(new Models.Meeting() { Name = "foo" });
}
}
Modes/AllMeetings
namespace App.Models;
internal class AllMeetings
{
public List<Meeting> Meetings { get; set; }
}
Models/Meetings.cs
namespace App.Models;
internal class Meeting
{
public string Name { get; set; }
}
Updates
Models/AllMeetings.cs
using System.Collections.ObjectModel;
namespace ailia_speech_gui.Models;
internal class AllMeetings
{
public ObservableCollection<Meeting> Meetings { get; set; }
public void Add_Meeting(Meeting meeting)
{
this.Meetings.Add(meeting);
}
}
I made a demo on my side. You can refer to my demo to change your project.
Here is the code in my Model named Products.cs:
namespace ListViewDelete.Models
{
public class Products
{
public string Name
{
get; set;
}
public double Price
{
get; set;
}
}
}
Then you need to create a viewmodel to realize the delete and add method and create the ObservableCollection to load the data.
Here is the code in my ViewModel:
namespace ListViewDelete.ViewModels
{
internal class ProductsViewModels
{
public ObservableCollection<Products> Products
{
get; set;
}
public Command<Products> RemoveCommand
{
get
{
return new Command<Products>((Product) => {
Products.Remove(Product);
});
}
}
public Command<Products> AddCommand
{
get
{
return new Command<Products>((Product) => {
Products.Add(Product);
});
}
}
public ProductsViewModels()
{
Products = new ObservableCollection<Products> {
new Products {
Name = "name1",
Price = 100
},
new Products {
Name = "name2",
Price = 100
},
new Products {
Name = "name3",
Price = 100
}
};
}
}
}
Last, you need to create the ListView or the CollectionView in the MainPage.xaml. Here is the code in the MainPage.xaml:
<StackLayout>
<Button Text="add" Clicked="Button_Clicked"></Button>
<CollectionView ItemsSource="{Binding Products}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding Name}" />
<Label Text="{Binding Price}" />
<Button Text="Remove" Clicked="Remove_Clicked" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
Here is the code in MainPage.xaml.cs:
namespace ListViewDelete
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
// bind the viewmodel to the Mainpage
BindingContext = new ProductsViewModels();
}
//delete the item from the observablecollection
public void Remove_Clicked(object sender, EventArgs e)
{
var button = sender as Button;
var product = button.BindingContext as Products;
var vm = BindingContext as ProductsViewModels;
vm.RemoveCommand.Execute(product);
}
//add the new item to the observablecollection
private void Button_Clicked(object sender, EventArgs e)
{
var product = new Products()
{
Name =" new name",
Price = 100
};
var vm = BindingContext as ProductsViewModels;
vm.AddCommand.Execute(product);
}
}
}
Meeting collection must be somewhere initialized before calling any operation on collestion (be it on property level or in constructor):
public class AllMeetings
{
public ObservableCollection<Meeting> Meetings { get; } = new ObservableCollection<Meeting>();
public void Add_Meeting(Meeting meeting)
{
this.Meetings.Add(meeting);
}
}
And ListView must have some data template to tell UI how data should be presented:
<ListView ItemsSource="{Binding Meetings}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Name}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

My autocomplete suggestion list takes much time to appear and it freezes the keyboard?

In Ui, I am suggesting a list if anyone enters any character by filtering it from a list. I am using listview.itemsource. It takes much time to appear and the keyboard gets frozen until the list appears.
I am filtering a list based on the character entered and storing it in listview.itemsource. the filtered list takes much time to appear on the UI side and till then my keyboard stays frozen, not able to enter the next character.
list2 = list.Where(x => x.CardHolderName.Trim().ToLower().Contains(e.NewTextValue.Trim().ToLower())).ToList();
FilteredPersonList = ConvertListToObservableCollection(list2);
if (FilteredPersonList != null && FilteredPersonList.Count > 0)
{
this.listView.IsVisible = true;
this.searchBarStack.HeightRequest = 250;
this.listView.ItemsSource = FilteredPersonList;
}
here listView is a variable that denotes ListView.
You could try the code below which shows a SearchBar query with results displayed in a ListView. It would not take much time to appear and it does not freeze the keyboard.
Xaml:
<StackLayout>
<StackLayout>
<SearchBar x:Name="_searchbar" Placeholder="Search..." TextChanged="_searchbar_TextChanged"/>
</StackLayout>
<ListView x:Name="list" ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Name}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Code:
public partial class Page22 : ContentPage
{
public List<ListViewItem> Items { get; set; }
public Page22()
{
InitializeComponent();
Items = new List<ListViewItem>
{
new ListViewItem { Name = "AAAA" },
new ListViewItem { Name = "ABBB" },
new ListViewItem { Name = "AAAA" },
new ListViewItem { Name = "CCCC" },
new ListViewItem { Name = "DAAB" },
new ListViewItem { Name = "DDDC" }
};
this.BindingContext = this;
}
private void _searchbar_TextChanged(object sender, TextChangedEventArgs e)
{
list.ItemsSource = FilterItem(e.NewTextValue);
}
IEnumerable<ListViewItem> FilterItem(string filter = null)
{
if (string.IsNullOrEmpty(filter))
return Items;
return Items.Where(p => p.Name.StartsWith(filter));
}
}
public class ListViewItem
{
public string Name { get; set; }
}

How to add Photo from Gallery in CollectionView Project?

I have a collection view and I want to add a photo from the gallery to it.If I add for example picture from gallery in tag Image.it works.But I want to add it in my collection view and I don`t understand why it does not add picture
XAML
<StackLayout>
<Button Text="Select"
Clicked="Handle_Clicked" />
<StackLayout HeightRequest="120" BackgroundColor="LightGray">
<!-- <Label Text="No photo yet" TextColor="#616161" HorizontalOptions="CenterAndExpand" FontSize="Large"
VerticalOptions="CenterAndExpand" ></Label>-->
<CollectionView x:Name="AddCar" ItemsSource="{Binding Types}"
SelectionMode="None">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Horizontal"
/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="120" />
</Grid.RowDefinitions>
<Frame CornerRadius="10" BorderColor="Black" Margin="5,5,5,5" Padding="0" >
<Image Source="{Binding Source}"
HorizontalOptions="Center"
BackgroundColor="{Binding CustButtonColor}"/>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</StackLayout>
Code Behind
public MainPage()
{
InitializeComponent();
BindingContext = new VM();
}
async void Handle_Clicked(object sender, System.EventArgs e)
{
//! added using Plugin.Media;
await CrossMedia.Current.Initialize();
var image = new Image();
//// if you want to take a picture use this
// if(!CrossMedia.Current.IsTakePhotoSupported || !CrossMedia.Current.IsCameraAvailable)
/// if you want to select from the gallery use this
if (!CrossMedia.Current.IsPickPhotoSupported)
{
await DisplayAlert("Not supported", "Your device does not currently support this functionality", "Ok");
return;
}
//! added using Plugin.Media.Abstractions;
// if you want to take a picture use StoreCameraMediaOptions instead of PickMediaOptions
var mediaOptions = new PickMediaOptions()
{
PhotoSize = PhotoSize.Medium
};
// if you want to take a picture use TakePhotoAsync instead of PickPhotoAsync
var selectedImageFile = await CrossMedia.Current.PickPhotoAsync(mediaOptions);
/* if (selectedImage == null)
{
await DisplayAlert("Error", "Could not get the image, please try again.", "Ok");
return;
}
*/
image.Source = ImageSource.FromStream(() => selectedImageFile.GetStream());
var page = new VM();
page.Types.Add(image);
}
}
VM
class VM : INotifyPropertyChanged
{
// public Command Photo { get; set; }
public ObservableCollection<Image> types { get; set; }
public ObservableCollection<Image> Types { get => types; set { types = value; OnPropertyChanged("Types"); } }
public VM()
{
// Photo = new Command(OnPickPhotoButtonClicked);
Types = new ObservableCollection<Image>();
Types.Add(new Image() { Source = "heart", BackgroundColor = Color.White });
Types.Add(new Image() { Source = "heart", BackgroundColor = Color.White });
Types.Add(new Image() { Source = "heart", BackgroundColor = Color.White });
Types.Add(new Image() { Source = "heart", BackgroundColor = Color.White });
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Please help. There is a link to my project on GitHub
https://github.com/kamenskyh/PhotoFromGallery/tree/master/Photos
first, keep a reference to your VM
VM ViewModel;
public MainPage()
{
InitializeComponent();
BindingContext = ViewModel = new VM();
}
then, when you add the picture, do NOT create a new VM instance
var page = new VM();
page.Types.Add(image);
instead, use the instance of the VM your page is already bound to
ViewModel.Types.Add(image);

Xamarin Forms Stop loading data in CollectionView

I have a problem. I created a CollectionView that uses a custom ViewModel. In that ViewModel I do a webcall to my webpage to get 20 filenames of images. After I got the result I do foreach filename a call to get the ImageSource of that filename. Now I created a Load data incrementally code to load the CollectionView data in bundles of 20. Here is my xaml:
<ContentPage.Content>
<StackLayout HorizontalOptions="Fill" Padding="15">
<Frame IsClippedToBounds="True" HeightRequest="45" CornerRadius="5" Padding="0" BackgroundColor="Transparent">
<Entry Placeholder="Search" ReturnType="Done" PlaceholderColor="Gray" x:Name="txtSearch" Margin="5,0,0,0" TextColor="White" />
</Frame>
<CollectionView ItemsSource="{Binding sourceList}" RemainingItemsThreshold="6"
RemainingItemsThresholdReachedCommand="{Binding LoadTemplates}">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="2" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<ff:CachedImage
Source="{Binding Source}"
VerticalOptions="Center"
HorizontalOptions="Center"
WidthRequest="{Binding WidthHeight}"
HeightRequest="{Binding WidthHeight}">
<ff:CachedImage.GestureRecognizers>
<TapGestureRecognizer Tapped="imgTemplate_Clicked" />
</ff:CachedImage.GestureRecognizers>
</ff:CachedImage>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage.Content>
Here is the page constructor:
public TemplateList()
{
InitializeComponent();
TemplateListViewModel vm = new TemplateListViewModel();
BindingContext = vm;
}
Here is the ViewModel:
public class TemplateListViewModel
{
public ICommand LoadTemplates => new Command(LoadTemplateList);
public int CurrentTemplateCountReceived;
public ObservableCollection<TemplateSource> sourceList { get; set; }
public double MemeWidthHeight { get; set; }
public TemplateListViewModel()
{
CurrentTemplateCountReceived = 0;
sourceList = new ObservableCollection<TemplateSource>();
var mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
var width = mainDisplayInfo.Width;
var density = mainDisplayInfo.Density;
var ScaledWidth = width / density;
MemeWidthHeight = (ScaledWidth / 2);
loadingTemplates += onLoadingTemplates;
LoadTemplateList();
}
private event EventHandler loadingTemplates = delegate { };
private void LoadTemplateList()
{
loadingTemplates(this, EventArgs.Empty);
}
private async void onLoadingTemplates(object sender, EventArgs args)
{
List<Template> templateList = await App.RestService.GetTemplates(App.User, CurrentTemplateCountReceived);
foreach (var template in templateList)
{
ImageSource source = ImageSource.FromUri(new Uri("mysite.org/myapp/" + template.FileName));
TemplateSource templateSource = new TemplateSource { Id = template.Id, Source = source, WidthHeight= MemeWidthHeight, FileName = template.FileName };
sourceList.Add(templateSource);
}
CurrentTemplateCountReceived = sourceList.Count;
}
}
Now App.RestService.GetTemplates(App.User, CurrentTemplateCountReceived); just returns me a list with filenames, but the problem is that it keeps doing webcalls when I got nothing to receive anymore. On my server I have 38 images, so after 2 webcalls the app got everything. After that the result that the app receives from the webcall is "Nothing".
So my question is:
How can I stop doing the webcalls when I am at the bottom of my CollectionView?
bool moreData = true;
private async void onLoadingTemplates(object sender, EventArgs args)
{
if (!moreData) return;
List<Template> templateList = await App.RestService.GetTemplates(App.User, CurrentTemplateCountReceived);
if (templateList is null or templateList.Count == 0) {
moreData = false;
return;
}
foreach (var template in templateList)
{
ImageSource source = ImageSource.FromUri(new Uri("mysite.org/myapp/" + template.FileName));
TemplateSource templateSource = new TemplateSource { Id = template.Id, Source = source, WidthHeight= MemeWidthHeight, FileName = template.FileName };
sourceList.Add(templateSource);
}
CurrentTemplateCountReceived = sourceList.Count;
}

Xamarin forms how to pass Entry value from one to class to another class List?

I want to pass inserted number value from page where I am inserting value to another page list and update ListView with new value.
Here is my AddCardPage xaml:
<Entry x:Name="CardNr" Text="" Completed="Entry_Completed" Keyboard="Numeric" />
here is AddCardPage xaml.cs method:
void Entry_Completed(object sender, EventArgs e)
{
var text = ((Entry)sender).Text; //cast sender to access the properties of the Entry
new Cards { Number = Convert.ToInt64(text) };
}
Here is my CardsPage class where I am already declared some data:
public CardsPage()
{
InitializeComponent();
base.Title = "Cards";
List<Cards> cardsList = new List<Cards>()
{
new Cards { Number=1234567891234567, Name="dsffds M", ExpDate="17/08", Security=123 },
new Cards { Number=9934567813535135, Name="Jason T", ExpDate="16/08", Security=132 },
new Cards { Number=4468468484864567, Name="Carl S", ExpDate="17/01", Security=987 },
};
listView.ItemsSource = cardsList;
AddCard.GestureRecognizers.Add(new TapGestureRecognizer
{
Command = new Command(() => OnLabelClicked()),
});
}
private void OnLabelClicked()
{
Navigation.PushAsync(new AddCardPage());
}
and CardsPage xaml with ListView:
<ListView x:Name="listView">
<ListView.Footer>
<StackLayout Padding="5,5" Orientation="Horizontal" >
<Label x:Name="AddCard" Text="Add new Card" FontSize="15" />
</StackLayout>
</ListView.Footer>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Number}" FontSize="15" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
How to pass inserted Entry value from page to another class List and update ListView content?
In the page you want to get the value change it's constructor to accept the value and pass that value from the calling page.
Eg:
class Page1
{
private async void OnSomeEvent()
{
await Navigation.PushAsync(new Page2("xyz"));
}
}
class Page2
{
public Page2(string myValue)
{
}
}

Categories