I'm getting two collections of data from a web service - the full collection and then a subset of items. The subset should be selected on load. I cannot get the SelectedItems to show as selected in the CollectionView, event though that property is set. I've looked over the Monkeys example and all over the web but can't find an example just like this
View:
<RefreshView IsRefreshing="{Binding IsBusy, Mode=TwoWay}" Command="{Binding LoadItemsCommand}">
<CollectionView x:Name="ExperiencesCollectionView"
ItemsSource="{Binding Experiences}"
SelectedItems="{Binding ProfileExperiences, Mode=TwoWay}"
SelectionMode="Multiple">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label
Text="{Binding Description}"/>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
</StackLayout>
Code Behind:
public partial class ExperiencesPage : ContentPage
{
ExperiencesViewModel viewModel;
public ExperiencesPage()
{
InitializeComponent();
BindingContext = viewModel = new ExperiencesViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
if (viewModel.Experiences.Count == 0)
viewModel.IsBusy = true;
}
ViewModel:
public class ExperiencesViewModel : BaseViewModel
{
public ObservableCollection<Experience> Experiences { get; set; }
public ObservableCollection<object> ProfileExperiences { get; set; }
public Command LoadItemsCommand { get; set; }
public string InterestedIn { get; set; }
public ExperiencesViewModel()
{
Title = "My Experiences";
Experiences = new ObservableCollection<Experience>();
LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());
}
async Task ExecuteLoadItemsCommand()
{
IsBusy = true;
try
{
Experiences.Clear();
//Get these from the REST service
var experiences = await App.ExperienceManager.GetExperiencesAsync();
var serviceExperiences = await App.ExperienceManager.GetProfileExperiencesAsync();
foreach (var exp in experiences)
{
Experiences.Add(exp);
}
foreach (var profileExp in serviceExperiences)
{
ProfileExperiences.Add(profileExp);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
Related
I have a Xamarin Forms CollectionView, contained within a RefreshView. The binding source is populated but its not showing anything in the CollectionView. I know the binding source is populated as I show a count in the toolbar. Can anyone spot why the list is empty?
Here is my Content Page with my RefreshView and CollectionView:
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding AddDogCommand}" />
<ToolbarItem Text="{Binding Dogs.Count}"></ToolbarItem>
</ContentPage.ToolbarItems>
<RefreshView x:DataType="local:MyDogsViewModel" Command="{Binding LoadDogsCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
<CollectionView ItemsLayout="HorizontalList" ItemsSource="{Binding Dogs}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid WidthRequest="100">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="1"
Text="{Binding DogName}"
FontAttributes="Bold" TextColor="Red"/>
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Nickname}"
FontAttributes="Italic" TextColor="Green"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
Here is my ViewModel
public class MyDogsViewModel : BaseViewModel
{
private DogModel _selectedItem;
private DogService dogService = new DogService();
private string _dogName;
private string _nickname;
public ObservableCollection<DogModel> Dogs { get; }
public Command LoadDogsCommand { get; }
public Command<DogModel> ItemTapped { get; }
public Command AddDogCommand { get; }
public Command SaveDogCommand { get; }
public string DogName
{
get => _dogName;
set => SetProperty(ref _dogName, value);
}
public string Nickname
{
get => _nickname;
set => SetProperty(ref _nickname, value);
}
public MyDogsViewModel()
{
Title = "My Dogs";
Dogs = new ObservableCollection<DogModel>();
LoadDogsCommand = new Command(async () => await ExecuteLoadDogsCommand());
ItemTapped = new Command<DogModel>(OnItemSelected);
AddDogCommand = new Command(OnAddDog);
SaveDogCommand = new Command(OnSaveDog);
}
async Task ExecuteLoadDogsCommand()
{
IsBusy = true;
try
{
Dogs.Clear();
var dogs = await dogService.GetDogsAsync();
foreach (var d in dogs)
{
Dogs.Add(d);
}
}
catch (Exception ex)
{
Debug.WriteLine("Exception: " + ex);
}
finally
{
IsBusy = false;
}
}
public void OnAppearing()
{
IsBusy = true;
}
private async void OnAddDog(object obj)
{
await Shell.Current.GoToAsync(nameof(AddDogPage));
}
private async void OnSaveDog(object obj)
{
AddDogModel model = new AddDogModel
{
DogName = DogName,
Nickname = Nickname
};
await dogService.AddDog(model);
await Shell.Current.GoToAsync("..");
}
async void OnItemSelected(DogModel dog)
{
if (dog == null)
return;
}
}
DogModel class
public class DogModel
{
public int Id { get; set; }
public string DogName { get; set; }
public string Nickname { get; set; }
}
keep your xaml the same and i will edit your ViewModel and take out the properties that is causing you the issue ;
public class MyDogsViewModel : BaseViewModel
{
private DogModel _selectedItem;
private DogService dogService = new DogService();
private string _dogName;
private string _nickname;
private ObservableCollection<DogModel> dogs;
public ObservableCollection<DogModel> Dogs
{
get{return dogs;}
set{dogs=value;}
}
public Command LoadDogsCommand { get; set; }
public Command<DogModel> ItemTapped { get; set;}
public Command AddDogCommand { get; set;}
public Command SaveDogCommand { get; set;}
public MyDogsViewModel()
{
Title = "My Dogs";
Dogs = new ObservableCollection<DogModel>();
LoadDogsCommand = new Command(async () => await ExecuteLoadDogsCommand());
ItemTapped = new Command<DogModel>(OnItemSelected);
AddDogCommand = new Command(OnAddDog);
SaveDogCommand = new Command<object>(OnSaveDog);
}
async Task ExecuteLoadDogsCommand()
{
IsBusy = true;
try
{
Dogs.Clear();
var dogs = await dogService.GetDogsAsync();
foreach (var d in dogs)
{
Dogs.Add(d);
}
}
catch (Exception ex)
{
Debug.WriteLine("Exception: " + ex);
}
finally
{
IsBusy = false;
}
}
public void OnAppearing()
{
IsBusy = true;
}
private async void OnAddDog(object obj)
{
await Shell.Current.GoToAsync(nameof(AddDogPage));
}
private async void OnSaveDog(object obj)
{
DogModel newdog = obj as DogModel;
await dogService.AddDog(newdog);
await Shell.Current.GoToAsync("..");
}
async void OnItemSelected(DogModel dog)
{
if (dog == null)
return;
}
}
dont forget to bind to the viewmodel. in the xaml.cs add this in the constuctor.
this.BindingContext = new MyDogsViewModel();
I have a collectionview that is bound to an ObservableRangeCollectionin my ViewModel.
In my ViewModel there is a Method that runs onAppearing and I want my ColletionViewto be filled from there, but when I do so the collectionveiw dose not display the content only when i reload the content is shown.
View:
<RefreshView Grid.Row="1"
Grid.RowSpan="2"
Command="{Binding RefreshCommand}"
IsRefreshing="{Binding IsBusy, Mode=OneWay}">
<RefreshView.RefreshColor>
<OnPlatform x:TypeArguments="Color">
<On Platform="iOS" Value="White"/>
</OnPlatform>
</RefreshView.RefreshColor>
<CollectionView x:Name="Collection"
ItemsSource="{Binding Locations, Mode=OneWay}"
ItemTemplate="{StaticResource ListDataTemplate}"
RemainingItemsThresholdReachedCommand="{Binding LoadMoreCommand}"
RemainingItemsThreshold="10"
SelectionMode="Single"
BackgroundColor="Transparent"
ItemsLayout="VerticalList"
SelectedItem="{Binding SelectedItem}"
SelectionChangedCommand="{Binding SelectedCommand}">
<CollectionView.EmptyView>
<StackLayout Padding="12">
<Label HorizontalOptions="Center" Text="Keine Daten vorhanden!" TextColor="White"/>
</StackLayout>
</CollectionView.EmptyView>
</CollectionView>
</RefreshView>
ViewModel:
namespace YourPartys.ViewModels
{
public class ListViewModel : ViewModelBase
{
#region Variables
#endregion
#region Propertys
LocationModel selectedItem;
public LocationModel SelectedItem
{
get => selectedItem;
set => SetProperty(ref selectedItem, value);
}
public ObservableRangeCollection<LocationModel> Locations { get;set; } = new ObservableRangeCollection<LocationModel>();
double distance;
public double Distance
{
get => distance;
set => SetProperty(ref distance, value);
}
#endregion
#region Commands
public ICommand FilterButtonCommand { get; }
public ICommand RefreshCommand { get; }
public ICommand SelectedCommand { get; }
public ICommand LoadMoreCommand { get; }
#endregion
//Constructor
public ListViewModel()
{
FilterButtonCommand = new Command(OpenFilter);
RefreshCommand = new AsyncCommand(Refresh);
SelectedCommand = new AsyncCommand(Select);
}
public override async void VModelActive(Page sender, EventArgs eventArgs)
{
base.VModelActive(sender, eventArgs);
var locs = await FirestoreService.GetLocations("Locations");
Locations.AddRange(locs);
}
private void OpenFilter(object obj)
{
PopupNavigation.Instance.PushAsync(new ListFilterPage());
}
private async Task Refresh()
{
IsBusy = true;
var locs = await FirestoreService.GetLocations("Locations");
Locations.AddRange(locs);
IsBusy = false;
}
private async Task Select()
{
if (SelectedItem == null)
return;
var route = $"{nameof(DetailPage)}?Locationid={SelectedItem.Locationid}";
SelectedItem = null;
await AppShell.Current.GoToAsync(route);
}
}
}
There are several problems in your demo.
1.Since you set the BindingContext for your page in xaml as follows:
<ContentPage.BindingContext>
<viewmodels:MainViewModel/>
</ContentPage.BindingContext>
you didn't need to recreate another object MainViewModel in a CS file and reference it. These are two different objects.
MainViewModel viewModel;
viewModel = new MainViewModel();
protected override void OnAppearing()
{
base.OnAppearing();
viewModel.VModelActive(this, EventArgs.Empty);
}
So, you can get the BindingContext in MainPage.xaml.cs in function OnAppearing as follows:
protected override void OnAppearing()
{
base.OnAppearing();
viewModel = (MainViewModel)this.BindingContext;
viewModel.VModelActive(this, EventArgs.Empty);
}
The whole code is
public partial class MainPage : ContentPage
{
MainViewModel viewModel;
public MainPage()
{
InitializeComponent();
// viewModel = new MainViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
viewModel = (MainViewModel)this.BindingContext;
viewModel.VModelActive(this, EventArgs.Empty);
}
}
2.when we set the text color of the Label to White,this makes it hard to see the text,so you can reset it to another color,for example Black:
<Label Text="{Binding Name}"
FontSize="30"
TextColor="White"/>
I do not load a small data from the API, in C# code, they are loaded in advance and everything seems to be fine, but as soon as I open the page where ItemsSource = "{Binding BigData}", my UI is blocked for 10 seconds.
Are there any ideas to open the page first, then start loading data without blocking the UI?
I would to suggest you can kick off a task in your view models constructor that loads the data. Using Async and await to load bid data.
I do one sample that using ListView to display 100000 records.
<StackLayout>
<Label Text="test ui in xamarin.forms asyn" />
<ActivityIndicator IsRunning="{Binding isBusy}" IsVisible="{Binding isBusy}" />
<ListView x:Name="listview1" ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding name}" />
<Label HorizontalOptions="CenterAndExpand" Text="{Binding age}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
public partial class Page19 : ContentPage
{
public Page19()
{
InitializeComponent();
this.BindingContext = new ItemsViewModel();
}
}
public class ItemsViewModel:ViewModelBase
{
private bool _isBusy;
public bool isBusy
{
get { return _isBusy; }
set
{
_isBusy = value;
RaisePropertyChanged("isBusy");
}
}
public ObservableCollection<people> Items { get; set; }
public ItemsViewModel()
{
Items = new ObservableCollection<people>();
isBusy = true;
Task.Run(async () => await LoadItems());
}
public async Task LoadItems()
{
var items = new ObservableCollection<people>(); // new collection
if (isBusy)
{
await Task.Delay(10000);
// var loadedItems = ItemsService.LoadItemsDirectory();
//foreach (var item in loadedItems)
// items.Add(item);
for (int i = 0; i < 100000; i++)
{
people p = new people();
p.name = "people " + i;
p.age = i;
items.Add(p); // items are added to the new collection
}
Items = items; // swap the collection for the new one
RaisePropertyChanged(nameof(Items)); // raise a property change in whatever way is right for your VM
isBusy = false;
}
}
}
public class people
{
public string name { get; set; }
public int age { get; set; }
}
ViewModelBase is one class that implementing INotifyPropertyChanged
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I followed this example https://medium.com/swlh/xamarin-forms-mvvm-how-to-work-with-sqlite-db-c-xaml-26fcae303edd
I put a breakpoint in my OnRouteSelected event handler, and e.SelectedItem has the selected object, even though the ListView doesn't display it.
Am I missing something glaring below?
Here is my code:
RoutesPage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<ContentPage.Content>
<ListView ItemsSource="{Binding Routes}" SelectedItem="{Binding SelectedRoute, Mode=TwoWay}" HasUnevenRows="False" SeparatorColor="Gray" ItemSelected="OnRouteSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Label TextColor="Black" Text="{Binding ROName}"/>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
RoutesPage.xaml.cs
public partial class RoutesPage : ContentPage
{
public RoutesPage()
{
InitializeComponent();
var routeStore = new RouteStore(DependencyService.Get<ISQLiteDb>());
var pageService = new PageService();
ViewModel = new RoutesPageViewModel(routeStore, pageService);
}
protected override void OnAppearing()
{
base.OnAppearing();
ViewModel.LoadDataCommand.Execute(null);
}
void OnRouteSelected(object sender, SelectedItemChangedEventArgs e)
{
ViewModel.SelectRouteCommand.Execute(e.SelectedItem);
}
public RoutesPageViewModel ViewModel
{
get { return BindingContext as RoutesPageViewModel; }
set { BindingContext = value; }
}
}
RoutesPageViewModel.cs The LoadData() method gets the data and adds it to the Routes Collection successfully.
public class RoutesPageViewModel : BaseViewModel
{
private RouteViewModel _selectedRoute;
private IRouteStore _routeStore;
private IPageService _pageService;
private bool _isDataLoaded;
public ObservableCollection<RouteViewModel> Routes { get; private set; }
= new ObservableCollection<RouteViewModel>();
public RouteViewModel SelectedRoute
{
get { return _selectedRoute; }
set { SetValue(ref _selectedRoute, value); }
}
public ICommand LoadDataCommand { get; private set; }
public ICommand AddRouteCommand { get; private set; }
public ICommand SelectRouteCommand { get; private set; }
public ICommand DeleteRouteCommand { get; private set; }
public ICommand CallRouteCommand { get; private set; }
public RoutesPageViewModel(IRouteStore routeStore, IPageService pageService)
{
_routeStore = routeStore;
_pageService = pageService;
LoadDataCommand = new Command(async () => await LoadData());
AddRouteCommand = new Command(async () => await AddRoute());
SelectRouteCommand = new Command<RouteViewModel>(async c => await SelectRoute(c));
}
private async Task LoadData()
{
if (_isDataLoaded)
return;
_isDataLoaded = true;
var routes = await _routeStore.GetRoutesAsync();
foreach (var route in routes)
Routes.Add(new RouteViewModel(route));
}
private async Task AddRoute()
{
// await _pageService.PushAsync(new RoutesDetailPage(new RouteViewModel()));
}
private async Task SelectRoute(RouteViewModel route)
{
if (route == null)
return;
SelectedRoute = null;
// await _pageService.PushAsync(new RoutesDetailPage(route));
}
}
The property in the viewmodels are being set like this:
private string _roName;
public string ROName
{
get { return _roName; }
set
{
SetValue(ref _roName, value);
OnPropertyChanged(nameof(ROName));
}
}
The constructor:
public RouteViewModel(Route route)
{
//other properties
ROName = route.ROName;
}
I need to pass data from a ListView to a TodoDetail page where I have a Telerik DataForm, but I don't know how to make it work. If I use normal Xamarin Forms controls it works fine, but need it to work with the Telerik DataForm control.
Here is my code:
Todo.xaml
list item tapped handler
private async void ToDoTaskTap(object sender, ItemTappedEventArgs e)
{
var user = ToDoTask.SelectedItem as tblEmpTask;
if (user != null)
{
var mainViewModel = BindingContext as MainViewModel;
if (mainViewModel != null)
{
mainViewModel.Selected = user;
await Navigation.PushAsync(new ToDoDetail(mainViewModel));
}
}
}
tblEmpTask.cs
public class tblEmpTask
{
public string strTaskName { get; set; }
}
TodoDetail.xaml
<telerikInput:RadDataForm x:Name="dataForm">
<telerikInput:RadDataForm.Source>
<local1:MainViewModel />
</telerikInput:RadDataForm.Source>
</telerikInput:RadDataForm>
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
public tblEmpTask Selected
{
get { return _Selected; }
set
{
_Selected = value;
OnPropertChanged();
}
}
[DisplayOptions(Header = "Name")]
public string Name
{
get { return this.Selected.strTaskName; }
set
{
if (value != this.Selected.strTaskName)
{
this.Selected.strTaskName = value;
OnPropertChanged();
}
}
}
}
You must add a binding between the SelectedItem and the ListView
Here's an example:
ViewModel:
public List<object> ItemsSource { get; set; }
public object SelectedItem {
set { SelectedItemChanged(value); }
}
async void SelectedItemChanged(object value) {
await App.Current.MainPage.Navigation.PushAsync(new AboutPage(SelectedItem));
}
Page:
<ListView
ItemsSource="{Binding ItemsSource}"
SelectedItem="{Binding SelectedItem, Mode=OneWayToSource}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding .}"></Label>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>