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;
}
Related
I'm trying to make CollectionView in Shell but it's not updating.
I have one view model connected to Page and AppShell but when I update Collection view only page is updationg.
`public class AppShellViewModel : INotifyPropertyChanged
{
public Command Load { get; }
public ObservableCollection<ListData> _lists { get; set; }
public ObservableCollection<ListData> Lists
{
get { return _lists; }
set
{
_lists = value;
OnPropertyChanged();
}
}
public AppShellViewModel()
{
Lists = new ObservableCollection<ListData>()
{
new ListData(){id=0,name="test",UserId=0},
new ListData(){id=1,name="test1",UserId=1},
new ListData(){id=2,name="test2",UserId=2},
new ListData(){id=3,name="test3",UserId=3},
new ListData(){id=4,name="test4",UserId=4}
};
Load = new Command(async () => await GetUserLists());
}
async Task GetUserLists()
{
for (int i = 5; i < 15; i++)
{
Lists.Add(new ListData {id=i, name=$"test{ i }", UserId=i });
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}`
Then i have App Shell Collection View
`<Shell.FlyoutContent>
<StackLayout BackgroundColor="#34495e">
<Label Text="YOUR LISTS" FontSize="50" />
<CollectionView ItemsSource="{Binding Lists}" >
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10" x:DataType="model:ListData">
<Label Text="{Binding name}"
LineBreakMode="NoWrap"
FontSize="13" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</Shell.FlyoutContent>`
And There is Page CollectionView
`<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ToDoApp.Views.AboutPage"
xmlns:model="clr-namespace:ToDoApp.Models">
<StackLayout>
<Button Text="Load" Command="{Binding Load}"/>
<Label Text="{Binding error}"/>
<CollectionView ItemsSource="{Binding Lists}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10" x:DataType="model:ListData">
<Label Text="{Binding name}"
LineBreakMode="NoWrap"
FontSize="13" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage>`
before update it looks like this
Page before update
Shell before update
And after update the only what changed is content page and shell is the same as before
Page after update
Shell after update
Related to Jason's comment.
WON'T CHANGE TOGETHER
NOT the same instance - BindingContexts similar to these:
// In AppShell.xaml.cs.
public AppShell()
{
InitializeComponent();
BindingContext = new AppShellViewModel();
}
// In AboutPage.xaml.cs.
public AboutPage()
{
InitializeComponent();
BindingContext = new AppShellViewModel();
}
GOOD (SHARED BETWEEN TWO PLACES)
BindingContexts are SAME instance:
// In AppShellViewModel.cs.
public class AppShellViewModel ...
{
private static AppShellViewModel _it;
public static AppShellViewModel It
{
get {
if (_it == null)
_it = new AppShellViewModel();
return _it;
}
}
}
// In AppShell.xaml.cs.
public AppShell()
{
InitializeComponent();
BindingContext = AppShellViewModel.It;
}
// In AboutPage.xaml.cs.
public AboutPage()
{
InitializeComponent();
BindingContext = AppShellViewModel.It;
}
I have a collection view with a checkbox.
It looks like the following:
[Image][PetName][Checkbox]
I want to create a string with all the names of the pets which have been selected the pass this value through a function.
I have tried the following code but I am getting object reference is null in selectedPets.Add(ob) I'm sure Im proably going the wrong way with this but I am new to coding.
public List<PetProfile> selectedPets;
private void CheckBox_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
var checkbox = sender as CheckBox;
var ob = checkbox.BindingContext as PetProfile;
if (ob != null)
{
selectedPets.Add(ob);
}
}
private string CreatePetName()
{
var stringBuilder = new StringBuilder();
var listlenght = selectedPets.Count;
foreach (var pet in selectedPets)
{
if (selectedPets.Count == 0)
{
stringBuilder.Append(pet.PetName);
}
else if (listlenght > 0 && pet == selectedPets[0] )
{
stringBuilder.Append(pet.PetName + " ");
}
else if (pet == selectedPets[listlenght])
{
stringBuilder.Append(" " + pet.PetName );
}
else
{
stringBuilder.Append(" " + pet.PetName + " ");
}
}
return stringBuilder.ToString();
}
private async void SubmitBtn_Clicked(object sender, EventArgs e)
{
var Petnames = CreatePetName();
}
XAML:
<CollectionView x:Name="petCollectionView" ItemsSource="{Binding EmptyPetInfo}" HeightRequest="200">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10" RowDefinitions="80" ColumnDefinitions="120,60,60">
<Image Grid.Column="0"
Grid.Row="0"
x:Name="PetImage"
Source="{Binding imageUrl}"/>
<Label Grid.Column="1"
Grid.Row="0"
Text="{Binding PetName}"
FontAttributes="Bold"
x:Name="labelpetname" VerticalTextAlignment="Center" HorizontalTextAlignment="Center"/>
<CheckBox Grid.Row="0" Grid.Column="2" HorizontalOptions="End" IsChecked="{Binding Selected, Mode=TwoWay}" BindingContext="{Binding .}" CheckedChanged="CheckBox_CheckedChanged"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
I want to create a string with all the names of the pets which have been selected the pass this value through a function.
I suggest you don't need to use CheckBox_CheckedChanged event to get selected PetName, you can create one class name Petclass, implementing INotifyPropertyChanged, to notify Selected property changed.
public class Petclass:ViewModelBase
{
public string imageUrl { get; set; }
public string PetName { get; set; }
private bool _Selected;
public bool Selected
{
get { return _Selected; }
set
{
_Selected = value;
RaisePropertyChanged("Selected");
}
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then loading some data to test in collectionview. foreach EmptyPetInfo Collectionview ItemsSource="{Binding EmptyPetInfo}" to check Selected property is true or false.
<CollectionView
x:Name="petCollectionView"
HeightRequest="200"
ItemsSource="{Binding EmptyPetInfo}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid
Padding="10"
ColumnDefinitions="120,60,60"
RowDefinitions="80">
<Image
x:Name="PetImage"
Grid.Row="0"
Grid.Column="0"
Source="{Binding imageUrl}" />
<Label
x:Name="labelpetname"
Grid.Row="0"
Grid.Column="1"
FontAttributes="Bold"
HorizontalTextAlignment="Center"
Text="{Binding PetName}"
VerticalTextAlignment="Center" />
<CheckBox
Grid.Row="0"
Grid.Column="2"
HorizontalOptions="End"
IsChecked="{Binding Selected, Mode=TwoWay}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
public partial class Page17 : ContentPage
{
public ObservableCollection<Petclass> EmptyPetInfo { get; set; }
public Page17()
{
InitializeComponent();
EmptyPetInfo = new ObservableCollection<Petclass>()
{
new Petclass(){imageUrl="check.png",PetName="pet 1"},
new Petclass(){imageUrl="delete.png",PetName="pet 2"},
new Petclass(){imageUrl="favorite.png",PetName="pet 3"},
new Petclass(){imageUrl="flag.png",PetName="pet 4"}
};
this.BindingContext = this;
}
private void btn1_Clicked(object sender, EventArgs e)
{
var stringBuilder = new StringBuilder();
foreach (Petclass pet in EmptyPetInfo)
{
if(pet.Selected)
{
stringBuilder.Append(pet.PetName + " ");
}
}
string str = stringBuilder.ToString();
}
}
Using ObservableCollection Class, represent a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
you need to initialize selectedPets before you use it
public List<PetProfile> selectedPets = new List<PetProfile>();
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; }
}
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);
I want to take multiple photos and it is shown in a list, but I must use the mvvm structure if I put this code in the CS of the XAML it works but I cannot use it that way ...
The view model works and everything but does not show the image on the screen
What can be ??
You can tell me that I am doing wrong he tried everything but he does not show me the image.
Reference XAML (view) SEE MODEL and MODEL
public class CapturePhotoViewModel : BaseViewModel
{
ObservableCollection Photos = new ObservableCollection();
public ICommand CapturePhotoCommand => new Command(async (s) => await CaptureFoto());
private async Task CaptureFoto()
{
var IniciandoEvento = await CrossMedia.Current.Initialize();
if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsPickPhotoSupported || !CrossMedia.IsSupported || !IniciandoEvento)
{
await DialogService.DisplayAlertAsync("No se puede Acceder a la camara", "¡Error!", "Aceptar");
return;
}
var newPhoto = Guid.NewGuid();
using (var photo = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions()
{
Name = newPhoto.ToString(),
SaveToAlbum = true,
DefaultCamera = CameraDevice.Rear,
Directory = "Demo",
CustomPhotoSize = 50
}))
{
if (string.IsNullOrWhiteSpace(photo?.Path))
{
return;
}
var newphotomedia = new MediaModel()
{
MediaID = newPhoto,
Path = photo.Path,
LocalDateTime = DateTime.Now
};
Photos = new ObservableCollection<MediaModel>();
Photos.Add(newphotomedia);
}
}
}
THIS IS MY XAML CODE which goes the list of image that is captured, but I do not know what I am doing wrong and does not show me the image
<Button Text="Tomar Foto" x:Name="photobutton" Command="{Binding CapturePhotoCommand}"/>
<ListView x:Name="ListPhotos" ItemsSource="{Binding Photos.Source}" RowHeight="400"
HeightRequest="300">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Frame OutlineColor="LightGray" HasShadow="true" Margin="4" WidthRequest="200" HeightRequest="250">
<Frame.Content>
<StackLayout>
<Image Source="{Binding Source}" VerticalOptions="StartAndExpand" HeightRequest="280"/>
<Button Text="Delete" />
</StackLayout>
</Frame.Content>
</Frame>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Y Este es mi model
public class MediaModel
{
public Guid MediaID { get; set; }
public string Path { get; set; }
public DateTime LocalDateTime { get; set; }
private FileImageSource source = null;
public FileImageSource Source => source ?? (source = new FileImageSource() { File = Path });
}
first, get rid of this line
Photos = new ObservableCollection<MediaModel>();
then fix your ItemsSource binding
<ListView x:Name="ListPhotos" ItemsSource="{Binding Photos}" RowHeight="400"
then fix your Image binding
<Image Source="{Binding Path}" VerticalOptions="StartAndExpand" HeightRequest="280"/>