I have a problem in refreshing the picker's UI in my Android.
I have two picker Province and Town.
If I select an item in province picker the town picker should refresh it content base on the selected province.
Therefore to do that I need to refresh my List of Town in my view model. which I successfully did each time I select a province.
The problem is the changes in my List of Town doesn't update or reflect on my Town's Picker when it run to my android emulator.
Any Idea?
One more thing, if I press the back button so to navigate to previous page. Then load the registration page again. The content of List of Town is successfully reflected to Town's Picker. It seems that it only bind my town picker in initialisation of my registration page. But once initialised it does not care whether you update the content of town in view model.
the problem is the changes in my List of Town doesn't update or reflect on my Town's Picker
What method did you use to update the picker? Try to detect the SelectedIndexChanged event of the province Picker, update the ItemsSource of town Picker in this method.
Check the code:
<StackLayout>
<Picker Title="select the Province" x:Name="ProvincePicker" ItemsSource="{Binding provinces}" ItemDisplayBinding="{Binding ProvinceName}" SelectedIndexChanged="ProvincePicker_SelectedIndexChanged"></Picker>
<Picker Title="select the Town" x:Name="TownPicker" ItemDisplayBinding="{Binding TownName}"></Picker>
</StackLayout>
Page.xaml.cs
public Page5()
{
InitializeComponent();
var province_data = new ObservableCollection<_Province>()
{
//add the data
};
CityViewModel cityViewModel = new CityViewModel();
cityViewModel.provinces = province_data;
BindingContext = cityViewModel;
}
private void ProvincePicker_SelectedIndexChanged(object sender, EventArgs e)
{
CityViewModel viewModel = BindingContext as CityViewModel;
var picker = sender as Picker;
TownPicker.ItemsSource = viewModel.provinces[(picker).SelectedIndex].towns;
}
The code of model class
public class _Town
{
public string TownName { get; set; }
}
public class _Province
{
public string ProvinceName { get; set; }
public ObservableCollection<_Town> towns { set; get; }
public _Province()
{
towns = new ObservableCollection<_Town>();
}
}
public class CityViewModel
{
public ObservableCollection<_Province> provinces { get; set; }
public CityViewModel()
{
provinces = new ObservableCollection<_Province>();
}
}
Related
I have a carouselview binded to a viewmodel, on the previous page (call it first page) user can select 2 arguments and with the help of those, the next view (call it second page) is generated accordingly. However, I can't wrap my head around why my view won't load asynchronously.
So my problem: When I click the button on the first page the UI would freeze for like a solid 2-3 seconds, and then start load (asynchronously?) and once it's done it's all good.
Also I couldn't really figure out a better way to inherit values from first page to second so if someone has an idea please let me know.
Any help on how can I fix this I would really appreciate.
The viewmodel for second page
public class DataSelectionViewModel : BaseViewModel
{
public ObservableCollection<Items> FilteredData { get; set; }
public UserSelectionViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
LoadData= new AsyncAwaitBestPractices.MVVM.AsyncCommand(FilterData);
FilteredData = new ObservableRangeCollection<Items>();
}
public async Task FilterData()
{
FilteredData.Clear();
var filtereddata = await _dataStore.SearchData(Hard, Subject).ConfigureAwait(false);
foreach (var data in filtereddata)
{
FilteredData.Add(data);
}
}
}
The carouselview in second page
<ContentPage.BindingContext>
<db:DataSelectionViewModel/>
</ContentPage.BindingContext>
...
<!-- DataTemplate for carouselview has radiobuttons, label and button all in a grid -->
<CarouselView ItemsSource="{Binding FilteredData}">
Second Page c#
public partial class SecondPage : ContentPage
{
public Coll(bool hard, string subject)
{
InitializeComponent();
var vm = (BaseViewModel)BindingContext;
vm.Hard = hard;
vm.Subject = subject;
/* had to set "hard" and "subject" here again, otherwise data won't load */
}
protected override async void OnAppearing()
{
var vm = (DataSelectionViewModel)BindingContext;
base.OnAppearing();
await vm.LoadData.ExecuteAsync().ConfigureAwait(false);
}
}
The first page view containing the button
<Button x:Name="Start" Pressed="ButtonClick"/>
First page c# --> Here I also tried doing it with a command and a pressed at the same time, because I couldn't come up with a way to save variables to second page viewmodel, that's why I use pressed here
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (BaseViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
vm.Subject = vm.Subject.ToLower();
await Navigation.PushAsync(new SecondPage(vm.Hard, vm.Subject));
}
I have tried not using the OnAppearing method to get my data, but then it wouldn't bind to the page and it would not show, if I were to previously fill the ObservableCollection with my data and then load the page although I would love to be able to do this because it would allow me to create a loading popup also.
I am using a GridView with item source property which is the list of objects. Initially I assign the list of items on page appearing which is also appearing on UI. But when i perform some operation on button click and update the items in the list, it doesn't updates on UI, I need to go back and come again.
<grial:GridView
x:Name="ItemList"
Padding="10"
ColumnSpacing="10"
RowSpacing="10"
ColumnCount="{
grial:OnOrientationInt
PortraitPhone=2,
LandscapePhone=2,
PortraitTablet=2,
LandscapeTablet=2,
PortraitDesktop=2,
LandscapeDesktop=2
}"
VerticalOptions="Start"
ItemsSource="{ Binding Items ,Mode=TwoWay}"
Style="{ StaticResource ResponsiveLandscapeMarginStyle }">
<grial:GridView.ItemTemplate>
<DataTemplate>
<local:RegisterItemGridV2 />
</DataTemplate>
</grial:GridView.ItemTemplate>
</grial:GridView>
My View model
public class LoadDistinctItemsViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private IList<Items> items;
public IList<Items> Items
{
get => items;
set
{
items = value;
NotifyPropertyChanged();
}
}
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void LoadData(string _loadId, int _mode = 0)
{
Items = App.dataManager.GetLoadDistinctItems(_loadId, _mode);
}
}
I am calling loadData which is updating everytime, the count in Items list is increasing but not updating on UI.
You can use ObservableCollection instead of IList . It works fine on my side .
public ObservableCollection<string> Items { get; set; }
Since you are modifiyng the List every often, you should not use List.
If you use a List and add or remove items at runtime, your ListView /Grid will not reflect those changes.
If you use an ObservableCollection, changes will be reflected
(also, remove the space in { Binding}
With the Xamarin Forms ListView, the ItemSelected event is fired each time an element is selected in the list.
Is there a way to cancel out of this event so that the new item isn't selected and the old item remains selected?
The use case is a master/detail type of view where selecting an item in the list changes the detail. But the detail view from the previous selection might have been altered and the user needs to decide to discard or save the previous changes before changing the List's SelectedItem.
#SushiHangover's suggestion to control the SelectionMode property and disable/enable the selection of the ListView is a good one. However, I have an alternate solution that will revert the ListView's selected item to the previous item for anyone who might have a similar need.
I will only post snippets of the solution, but they should be complete enough for someone else to learn and implement.
First, I am using FreshMVVM which provides (amongst many things), essentially, syntactic sugar over binding the View to the ViewModel. Also, the PropertyChanged nuget package creates the INotifyPropertyChanged boilerplate code at compile time. That is why you don't see the familiar XF patterns you normally see with that interface. AddINotifyPropertyChanged handles all that.
The solution to my problem is a dedicated, generic ListViewModel that can be bound to any ListView that needs the ability "roll back" a selection changed event. It binds to the Items collection. Additionally the SelectedItem property is bound to the control as well.
The constructor takes a Func which is called to determine if it's ok to move the selection or not.
[AddINotifyPropertyChangedInterface]
public class ListViewModel<T>
{
private Func<bool> _beforeChangeValidator;
private Action _afterChange;
public ListViewModel(Func<bool> beforeChangeValidator, Action afterChange)
{
_beforeChangeValidator = beforeChangeValidator;
_afterChange = afterChange;
_changing = false;
}
public int SelectedIndex { get; set; }
public T SelectedItem { get; set; }
public ObservableCollection<T> Items { get; set; }
private bool _changing;
public Command SelectedItemChanged
{
get
{
return new Command((args) =>
{
if (!_changing)
{
if (_beforeChangeValidator())
{
SelectedIndex = ((SelectedItemChangedEventArgs)args).SelectedItemIndex;
}
}
_changing = false;
});
}
}
public void RevertSelectedItemChanged()
{
_changing = true;
SelectedItem = Items[SelectedIndex];
}
}
And the code in the parent ViewModel has the Func (TagListBeforeChange) that determines if it's ok to move the selection or not. In this case I am checking if the last selected item has been changed, and if it has, prompt the user for what to do.
public override void Init()
{
TagListViewModel = new ListViewModel<Tag>(TagListBeforeChange, null);
}
private bool TagListBeforeChange()
{
if (ActiveTag.HasChanged)
{
var confirmConfig = new ConfirmConfig()
{
Message = "Current tag has changed. Discard changes and continue?",
OkText = "Discard Changes",
CancelText = "Cancel",
OnAction = (result) =>
{
if (result)
{
_mapper.Map(TagListViewModel.SelectedItem, ActiveTag);
}
else
{
TagListViewModel.RevertSelectedItemChanged();
}
}
};
_userDialogs.Confirm(confirmConfig);
return false;
}
_mapper.Map(TagListViewModel.SelectedItem, ActiveTag);
return true;
}
And finally, here is the ListView control declaration...
<ListView ItemsSource="{Binding TagListViewModel.Items}"
SelectedItem="{Binding TagListViewModel.SelectedItem, Mode=TwoWay}">
<ListView.Behaviors>
<behaviors:EventHandlerBehavior EventName="ItemSelected">
<behaviors:InvokeCommandAction Command="{Binding TagListViewModel.SelectedItemChanged}" />
</behaviors:EventHandlerBehavior>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView Padding="8">
<Label Text="{Binding DisplayValue}" />
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have a Xamarin.Forms Application based on a Listview populated by an ObservableCollection<Item> and bound to a SQLite model exposed as List<Item>.
Now I have difficulties figuring out how I can update the data via the web.
The whole process is supposed to run in the background. If everything runs as desired, the update process would be based on async ... await tasks and new items would appear one by one on the screen as they are pulled in.
Would anyone please guide me how to lay out my application and how to implement such a background update task?
Notes:
More experienced colleagues warned me that such a concept cannot be done in Xamarin at all, since, so they say, ObservableCollection "does not support to be updated by a background task". I did some research on this, and found indeed some indication that this could be true, but the infos were long outdated (from 2008), things have very likely changed since then.
For performance reasons I cannot simply pull in the complete list and throw away the existing list, but I need to implement a record based update looking at the items one by one. To accomplish this records have an unique Id, and are timestamped. My app knows when it has last seen the web service, and can query all items which have changed since then. I already have a REST service pulling in the changed Items data from the backend as a List, but cannot figure out how to refresh the ObservableCollection and the underlying database.
I do all my updates on change to the ListView. Here I have a button in the a list view which when clicked updates a property which persists by saving it to the sql database. It assumes you have your database set up.
Database:
Function which updates item if exist or saves new. This is a Task so can be called asynchronously.
public Task<int> SaveItemAsync(Item item)
{
if(item.ItemId != 0)
{
return database.UpdateAsync(item);
}
else
{
return database.InsertAsync(itme);
}
}
Xaml
List View which binds to an Observable collection created from the item database.
GestureRecognizers is set up on the image and is bound to a tapCommand in the ViewModel - The code behind the Xaml defines the binding context
Code behind
ItemViewModel viewModel;
public MessagePage ()
{
InitializeComponent ();
viewModel = new ItemViewModel();
BindingContext = viewModel;
}
And then the Xaml
Bind to the ObsevableCollection "ItemsPassed" and this set as the binding context within it. As a result you need to go back to the
page BindingContext so note the binding path for the TapCommand.
Pass the ItemId through as a parameter
<ListView ItemsSource="{Binding ItemsPassed}"
HasUnevenRows="True"
x:Name="ItemListView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView>
<StackLayout Padding="20">
<StackLayout Orientation="Horizontal">
<StackLayout Orientation="Vertical" HorizontalOptions="Start">
<Label Text="{Binding ItemText}"
FontAttributes="Bold"/>
</StackLayout>
<Image Source="{Binding Favourited, HeightRequest="12" HorizontalOptions="EndAndExpand">
<Image.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Path=BindingContext.TapCommand, Source={x:Reference ItemListView}}"
CommandParameter="{Binding ItemId}"/>
</Image.GestureRecognizers>
</Image>
</StackLayout>
</StackLayout>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel
Define the ICommand and assign a function to it
Find the item in the observable collection and change the property
This also needs to be changed in the database using the await App.Database.SaveItemAsync(item) - because of this the function called of the command must be async
public class ItemsViewModel : INotifyPropertyChanged
{
public ObservableCollection<Item> ItemsPassed { get; set; } = new ObservableCollection<Item>();
// Get items out of yourdatabase
public async void GetItems()
{
List<Item> itemsOut = await App.Database.GetItemsAsync();
foreach (Item i in itemsOut)
{
ItemsPassed.Add(i);
}
}
ICommand tapCommand;
public ItemsViewModel()
{
GetItems();
tapCommand = new Command(ExecuteTap);
}
public ICommand TapCommand
{
get { return tapCommand; }
}
// Find the item selected and change the property
private async void ExecuteTap(object obj)
{
var item = ItemsPassed.FirstOrDefault(i => i.ItemId.ToString() == obj.ToString());
if (item != null)
{
if (item.Favourited == true)
{
item.Favourited = false;
}
else
{
item.Favourited = true;
}
await App.Database.SaveItemAsync(item);
Console.WriteLine("update the database");
}
}
}
You then want to make sure the changes occur in the view - this is done through extending INotifyPropertyChange in the Model and calling it when the property changes.
Model
When the item is changed in the viewmodel the OnPropertyChanged is fired which cause the view to update.
public class Item : INotifyPropertyChanged
{
[PrimaryKey, AutoIncrement]
public int ItemId { get; set; }
public string ItemText { get; set; }
public string Author { get; set; }
private bool _favourited;
public bool Favourited
{
get { return _favourited; }
set
{
_favourited = value;
OnPropertyChanged("Favourited");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Hope this helps. Sorry I had to put things in snippets the code section wasn't working properly probably because some of the code is wrong, but its just for example.
I have two pages.
i) HomePage -- which contains one button to display user selected city(by defalut Delhi)
ii) ListPage -- Which contains List box (all cities show here)
So when user clicks on button in the HomePage, then i'll navigate to ListPage to select city.
When user clicks on any city from the list, then i have to dismiss that ListPage and then immediately the selected city should update as button's content in the HomePage.
Please help me how to do the above task.
if you are using "MVVM Light" library then you can use Messenger service Like this....
in second page after selection is changed send a message
Messenger.Default.Send<type>(message,token);
and then in the consructor of page 1 viewmodel
Messenger.Default.Register<type>(this,token,Method);
here token should be same as sender token....
then
void Method(type message)
{
button.content = message;
}
The basic way is to implement the PropertyChanged subscription.
//In your HomePage class
HomePage()
{
ListPage.PropertyChanged +=this.CitySelected //Subscribe to the property changed event
}
void CitySelected(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "NewCity")
{
City selectedCity= (sender as ListPage).NewCity;
}
}
//In your ListPage class
//You should implement INotifyPropertyChanged interface:
ListPage:INotifyPropertyChanged
private city newCity;
public City NewCity
{
get{return newCity;}
set{
newCity=value;
NotifyChange("NewCity");
}
}
void CitySelected(City selected)
{
NewCity=selected;
}
public event PropertyChangedEventHandler PropertyChanged;
public delegate void PropertyChangeHandler(object sender, EventArgs data);
protected void NotifyChange(string args)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(args));
}
}
I would recommend you change your workflow to use the Windows Phone Toolkit ListPicker control. This control matches the style you see in the Settings app of the phone. The ListPicker allows users to tap the selected item and be navigated to another page that offers them a list of available options. Here is an example
<StackPanel>
<TextBlock Text="City"/>
<toolkit:ListPicker ExpansionMode="FullScreenOnly"
FullModeHeader="CITY"
ItemsSource="{Binding Cities}"
SelectedItem="{Binding SelectedCity}">
<toolkit:ListPicker.FullModeItemTemplate>
<DataTemplate>
<TextBlock Margin="0,20" Text="{Binding Name}" TextWrapping="Wrap"/>
</DataTemplate>
</toolkit:ListPicker.FullModeItemTemplate>
</toolkit:ListPicker>
</StackPanel>
Your DataContext would look something like
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
Cities = new List<City> { new City("Denver"), new City("New York") };
}
public City SelectedCity
{
{ get return _city; }
{ _city = value; OnpropertyChanged("SelectedCity"); }
}
public IEnumerable<City> Cities { get; private set; }
}
This is gonna be very simple. What you have to do is, use NavigationService.Navigate(), and then append the selected city along with the HomePage page uri and in the HomePage page, retrieve the selected city using NavigationContext.QueryString.TryGetValue() and assign the value in your button.
For ex,
In your ListPage.cs,
NavigationService.Navigate(new Uri("/HomePage.xaml?selectedCity=" + "Coimbatore", UriKind.Relative));
In your HomePage.cs,
string selectedCity = "";
NavigationContext.QueryString.TryGetValue("selectedCity", out selectedCity);
button.Content = selectedCity;
Thanks